import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.UserDefinedFileAttributeView;
import java.time.Instant;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import java.util.StringJoiner;
+import java.util.concurrent.CompletableFuture;
+import javax.xml.XMLConstants;
import javax.xml.namespace.QName;
+import javax.xml.transform.Source;
+import javax.xml.transform.TransformerException;
+import javax.xml.transform.TransformerFactory;
+import javax.xml.transform.stream.StreamResult;
import org.argeo.api.acr.Content;
import org.argeo.api.acr.ContentName;
import org.argeo.api.acr.ContentResourceException;
+import org.argeo.api.acr.CrAttributeType;
import org.argeo.api.acr.CrName;
+import org.argeo.api.acr.DName;
import org.argeo.api.acr.NamespaceUtils;
import org.argeo.api.acr.spi.ContentProvider;
import org.argeo.api.acr.spi.ProvidedContent;
import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.api.cms.CmsLog;
import org.argeo.cms.acr.AbstractContent;
import org.argeo.cms.acr.ContentUtils;
-import org.argeo.util.FsUtils;
+import org.argeo.cms.util.FsUtils;
/** Content persisted as a filesystem {@link Path}. */
public class FsContent extends AbstractContent implements ProvidedContent {
+ private CmsLog log = CmsLog.getLog(FsContent.class);
+
final static String USER_ = "user:";
private static final Map<QName, String> BASIC_KEYS;
private static final Map<QName, String> POSIX_KEYS;
static {
BASIC_KEYS = new HashMap<>();
- BASIC_KEYS.put(CrName.creationTime.qName(), "basic:creationTime");
- BASIC_KEYS.put(CrName.lastModifiedTime.qName(), "basic:lastModifiedTime");
- BASIC_KEYS.put(CrName.size.qName(), "basic:size");
+ BASIC_KEYS.put(DName.creationdate.qName(), "basic:creationTime");
+ BASIC_KEYS.put(DName.getlastmodified.qName(), "basic:lastModifiedTime");
+ BASIC_KEYS.put(DName.getcontentlength.qName(), "basic:size");
+
BASIC_KEYS.put(CrName.fileKey.qName(), "basic:fileKey");
POSIX_KEYS = new HashMap<>(BASIC_KEYS);
- POSIX_KEYS.put(CrName.owner.qName(), "owner:owner");
- POSIX_KEYS.put(CrName.group.qName(), "posix:group");
+ POSIX_KEYS.put(DName.owner.qName(), "owner:owner");
+ POSIX_KEYS.put(DName.group.qName(), "posix:group");
POSIX_KEYS.put(CrName.permissions.qName(), "posix:permissions");
}
// TODO check file names with ':' ?
if (isMountBase) {
String mountPath = provider.getMountPath();
- if (mountPath != null && !mountPath.equals("/")) {
+ if (mountPath != null && !mountPath.equals(ContentUtils.ROOT_SLASH)) {
Content mountPoint = session.getMountPoint(mountPath);
this.name = mountPoint.getName();
} else {
} else {
// TODO should we support prefixed name for known types?
- // QName providerName = NamespaceUtils.parsePrefixedName(provider,
- // path.getFileName().toString());
- QName providerName = new QName(path.getFileName().toString());
+ QName providerName = NamespaceUtils.parsePrefixedName(provider, path.getFileName().toString());
+// QName providerName = new QName(path.getFileName().toString());
// TODO remove extension if mounted?
this.name = new ContentName(providerName, session);
}
}
// TODO perform trivial file conversion to other formats
}
+
+ // TODO better deal with multiple types
if (value instanceof byte[]) {
- res = (A) new String((byte[]) value, StandardCharsets.UTF_8);
- }
- if (res == null)
- try {
- res = (A) value;
- } catch (ClassCastException e) {
- return Optional.empty();
+ String str = new String((byte[]) value, StandardCharsets.UTF_8);
+ String[] arr = str.split("\n");
+
+ if (arr.length == 1) {
+ if (clss.isAssignableFrom(String.class)) {
+ res = (A) arr[0];
+ } else {
+ res = (A) CrAttributeType.parse(arr[0]);
+ }
+ } else {
+ List<Object> lst = new ArrayList<>();
+ for (String s : arr) {
+ lst.add(CrAttributeType.parse(s));
+ }
+ res = (A) lst;
}
+ }
+ if (res == null) {
+ if (isDefaultAttrTypeRequested(clss))
+ return Optional.of((A) CrAttributeType.parse(value.toString()));
+ if (clss.isAssignableFrom(value.getClass()))
+ return Optional.of((A) value);
+ if (clss.isAssignableFrom(String.class))
+ return Optional.of((A) value.toString());
+ log.warn("Cannot interpret " + key + " in " + this);
+ return Optional.empty();
+// try {
+// res = (A) value;
+// } catch (ClassCastException e) {
+// return Optional.empty();
+// }
+ }
return Optional.of(res);
}
try {
for (String name : udfav.list()) {
QName providerName = NamespaceUtils.parsePrefixedName(provider, name);
+ if (providerName.getNamespaceURI().equals(XMLConstants.XMLNS_ATTRIBUTE_NS_URI))
+ continue; // skip prefix mapping
QName sessionName = new ContentName(providerName, getSession());
result.add(sessionName);
}
@Override
public Object put(QName key, Object value) {
Object previous = get(key);
+
+ String toWrite;
+ if (value instanceof List) {
+ StringJoiner sj = new StringJoiner("\n");
+ for (Object obj : (List<?>) value) {
+ sj.add(obj.toString());
+ }
+ toWrite = sj.toString();
+ } else {
+ toWrite = value.toString();
+ }
+
UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
- ByteBuffer bb = ByteBuffer.wrap(value.toString().getBytes(StandardCharsets.UTF_8));
+ ByteBuffer bb = ByteBuffer.wrap(toWrite.getBytes(StandardCharsets.UTF_8));
try {
udfav.write(NamespaceUtils.toPrefixedName(provider, key), bb);
} catch (IOException e) {
FsContent fsContent;
try {
Path newPath = path.resolve(NamespaceUtils.toPrefixedName(provider, name));
- if (ContentName.contains(classes, CrName.collection.qName()))
+ if (ContentName.contains(classes, DName.collection.qName()))
Files.createDirectory(newPath);
else
Files.createFile(newPath);
throw new ContentResourceException("Cannot create new content", e);
}
+ if (classes.length > 0)
+ fsContent.addContentClasses(classes);
if (getSession().getRepository().shouldMount(classes)) {
ContentProvider contentProvider = getSession().getRepository().getMountContentProvider(fsContent, true,
classes);
@Override
public List<QName> getContentClasses() {
List<QName> res = new ArrayList<>();
+ List<String> value = getMultiple(DName.resourcetype.qName(), String.class);
+ for (String s : value) {
+ QName name = NamespaceUtils.parsePrefixedName(provider, s);
+ res.add(name);
+ }
if (Files.isDirectory(path))
- res.add(CrName.collection.qName());
- // TODO add other types
+ res.add(DName.collection.qName());
return res;
}
+ @Override
+ public void addContentClasses(QName... contentClass) {
+ List<String> toWrite = new ArrayList<>();
+ for (QName cc : getContentClasses()) {
+ if (cc.equals(DName.collection.qName()))
+ continue; // skip
+ toWrite.add(NamespaceUtils.toPrefixedName(provider, cc));
+ }
+ for (QName cc : contentClass) {
+ toWrite.add(NamespaceUtils.toPrefixedName(provider, cc));
+ }
+ put(DName.resourcetype.qName(), toWrite);
+ }
+
/*
* ACCESSORS
*/
return provider;
}
+ /*
+ * READ / WRITE
+ */
+ @SuppressWarnings("unchecked")
+ public <A> CompletableFuture<A> write(Class<A> clss) {
+ if (isContentClass(DName.collection.qName())) {
+ throw new IllegalStateException("Cannot directly write to a collection");
+ }
+ if (InputStream.class.isAssignableFrom(clss)) {
+ CompletableFuture<InputStream> res = new CompletableFuture<>();
+ res.thenAccept((in) -> {
+ try {
+ Files.copy(in, path, StandardCopyOption.REPLACE_EXISTING);
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot write to " + path, e);
+ }
+ });
+ return (CompletableFuture<A>) res;
+ } else if (Source.class.isAssignableFrom(clss)) {
+ CompletableFuture<Source> res = new CompletableFuture<Source>();
+ res.thenAccept((source) -> {
+// Path targetPath = path.getParent().resolve(path.getFileName()+".xml");
+ Path targetPath = path;
+ try (OutputStream out = Files.newOutputStream(targetPath)) {
+ StreamResult result = new StreamResult(out);
+ TransformerFactory.newDefaultInstance().newTransformer().transform(source, result);
+ } catch (IOException | TransformerException e) {
+ throw new RuntimeException("Cannot write to " + path, e);
+ }
+ });
+ return (CompletableFuture<A>) res;
+ } else {
+ return super.write(clss);
+ }
+ }
}