Make ACR content providers more configurable
[lgpl/argeo-commons.git] / org.argeo.cms / src / org / argeo / cms / acr / fs / FsContent.java
index a2cc52c42850f54bd2d7e25258b3cab866f10993..76dc9da71d65bf95053679e2d99cf6e969623f69 100644 (file)
@@ -11,10 +11,12 @@ import java.nio.file.Path;
 import java.nio.file.attribute.FileTime;
 import java.nio.file.attribute.UserDefinedFileAttributeView;
 import java.time.Instant;
+import java.util.ArrayList;
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
+import java.util.List;
 import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
@@ -24,15 +26,16 @@ import javax.xml.namespace.QName;
 import org.argeo.api.acr.Content;
 import org.argeo.api.acr.ContentName;
 import org.argeo.api.acr.ContentResourceException;
-import org.argeo.api.acr.ContentUtils;
 import org.argeo.api.acr.CrName;
 import org.argeo.api.acr.NamespaceUtils;
-import org.argeo.api.acr.spi.AbstractContent;
 import org.argeo.api.acr.spi.ContentProvider;
 import org.argeo.api.acr.spi.ProvidedContent;
 import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.cms.acr.AbstractContent;
+import org.argeo.cms.acr.ContentUtils;
 import org.argeo.util.FsUtils;
 
+/** Content persisted as a filesystem {@link Path}. */
 public class FsContent extends AbstractContent implements ProvidedContent {
        final static String USER_ = "user:";
 
@@ -40,39 +43,43 @@ public class FsContent extends AbstractContent implements ProvidedContent {
        private static final Map<QName, String> POSIX_KEYS;
        static {
                BASIC_KEYS = new HashMap<>();
-               BASIC_KEYS.put(CrName.CREATION_TIME.get(), "basic:creationTime");
-               BASIC_KEYS.put(CrName.LAST_MODIFIED_TIME.get(), "basic:lastModifiedTime");
-               BASIC_KEYS.put(CrName.SIZE.get(), "basic:size");
-               BASIC_KEYS.put(CrName.FILE_KEY.get(), "basic:fileKey");
+               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(CrName.fileKey.qName(), "basic:fileKey");
 
                POSIX_KEYS = new HashMap<>(BASIC_KEYS);
-               POSIX_KEYS.put(CrName.OWNER.get(), "owner:owner");
-               POSIX_KEYS.put(CrName.GROUP.get(), "posix:group");
-               POSIX_KEYS.put(CrName.PERMISSIONS.get(), "posix:permissions");
+               POSIX_KEYS.put(CrName.owner.qName(), "owner:owner");
+               POSIX_KEYS.put(CrName.group.qName(), "posix:group");
+               POSIX_KEYS.put(CrName.permissions.qName(), "posix:permissions");
        }
 
-       private final ProvidedSession session;
        private final FsContentProvider provider;
        private final Path path;
-       private final boolean isRoot;
+       private final boolean isMountBase;
        private final QName name;
 
        protected FsContent(ProvidedSession session, FsContentProvider contentProvider, Path path) {
-               this.session = session;
+               super(session);
                this.provider = contentProvider;
                this.path = path;
-               this.isRoot = contentProvider.isRoot(path);
+               this.isMountBase = contentProvider.isMountBase(path);
                // TODO check file names with ':' ?
-               if (isRoot) {
+               if (isMountBase) {
                        String mountPath = provider.getMountPath();
-                       if (mountPath != null) {
+                       if (mountPath != null && !mountPath.equals("/")) {
                                Content mountPoint = session.getMountPoint(mountPath);
                                this.name = mountPoint.getName();
                        } else {
-                               this.name = CrName.ROOT.get();
+                               this.name = CrName.root.qName();
                        }
                } else {
-                       QName providerName = NamespaceUtils.parsePrefixedName(provider, path.getFileName().toString());
+
+                       // TODO should we support prefixed name for known types?
+                       // 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);
                }
        }
@@ -94,12 +101,32 @@ public class FsContent extends AbstractContent implements ProvidedContent {
         * ATTRIBUTES
         */
 
+       @SuppressWarnings("unchecked")
        @Override
        public <A> Optional<A> get(QName key, Class<A> clss) {
                Object value;
                try {
                        // We need to add user: when accessing via Files#getAttribute
-                       value = Files.getAttribute(path, toFsAttributeKey(key));
+
+                       if (POSIX_KEYS.containsKey(key)) {
+                               value = Files.getAttribute(path, toFsAttributeKey(key));
+                       } else {
+                               UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path,
+                                               UserDefinedFileAttributeView.class);
+                               String prefixedName = NamespaceUtils.toPrefixedName(provider, key);
+                               if (!udfav.list().contains(prefixedName))
+                                       return Optional.empty();
+                               ByteBuffer buf = ByteBuffer.allocate(udfav.size(prefixedName));
+                               udfav.read(prefixedName, buf);
+                               buf.flip();
+                               if (buf.hasArray())
+                                       value = buf.array();
+                               else {
+                                       byte[] arr = new byte[buf.remaining()];
+                                       buf.get(arr);
+                                       value = arr;
+                               }
+                       }
                } catch (IOException e) {
                        throw new ContentResourceException("Cannot retrieve attribute " + key + " for " + path, e);
                }
@@ -133,7 +160,7 @@ public class FsContent extends AbstractContent implements ProvidedContent {
                        try {
                                for (String name : udfav.list()) {
                                        QName providerName = NamespaceUtils.parsePrefixedName(provider, name);
-                                       QName sessionName = new ContentName(providerName, session);
+                                       QName sessionName = new ContentName(providerName, getSession());
                                        result.add(sessionName);
                                }
                        } catch (IOException e) {
@@ -159,7 +186,7 @@ public class FsContent extends AbstractContent implements ProvidedContent {
                UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
                ByteBuffer bb = ByteBuffer.wrap(value.toString().getBytes(StandardCharsets.UTF_8));
                try {
-                       int size = udfav.write(NamespaceUtils.toPrefixedName(provider, key), bb);
+                       udfav.write(NamespaceUtils.toPrefixedName(provider, key), bb);
                } catch (IOException e) {
                        throw new ContentResourceException("Cannot delete attribute " + key + " for " + path, e);
                }
@@ -182,12 +209,12 @@ public class FsContent extends AbstractContent implements ProvidedContent {
                        try {
                                return Files.list(path).map((p) -> {
                                        FsContent fsContent = new FsContent(this, p);
-                                       Optional<String> isMount = fsContent.get(CrName.MOUNT.get(), String.class);
+                                       Optional<String> isMount = fsContent.get(CrName.mount.qName(), String.class);
                                        if (isMount.orElse("false").equals("true")) {
                                                QName[] classes = null;
-                                               ContentProvider contentProvider = session.getRepository().getMountContentProvider(fsContent,
-                                                               false, classes);
-                                               Content mountedContent = contentProvider.get(session, fsContent.getPath(), "");
+                                               ContentProvider contentProvider = getSession().getRepository()
+                                                               .getMountContentProvider(fsContent, false, classes);
+                                               Content mountedContent = contentProvider.get(getSession(), "");
                                                return mountedContent;
                                        } else {
                                                return (Content) fsContent;
@@ -206,7 +233,7 @@ public class FsContent extends AbstractContent implements ProvidedContent {
                FsContent fsContent;
                try {
                        Path newPath = path.resolve(NamespaceUtils.toPrefixedName(provider, name));
-                       if (ContentName.contains(classes, CrName.COLLECTION.get()))
+                       if (ContentName.contains(classes, CrName.collection.qName()))
                                Files.createDirectory(newPath);
                        else
                                Files.createFile(newPath);
@@ -219,10 +246,11 @@ public class FsContent extends AbstractContent implements ProvidedContent {
                        throw new ContentResourceException("Cannot create new content", e);
                }
 
-               if (session.getRepository().shouldMount(classes)) {
-                       ContentProvider contentProvider = session.getRepository().getMountContentProvider(fsContent, true, classes);
-                       Content mountedContent = contentProvider.get(session, fsContent.getPath(), "");
-                       fsContent.put(CrName.MOUNT.get(), "true");
+               if (getSession().getRepository().shouldMount(classes)) {
+                       ContentProvider contentProvider = getSession().getRepository().getMountContentProvider(fsContent, true,
+                                       classes);
+                       Content mountedContent = contentProvider.get(getSession(), "");
+                       fsContent.put(CrName.mount.qName(), "true");
                        return mountedContent;
 
                } else {
@@ -237,16 +265,17 @@ public class FsContent extends AbstractContent implements ProvidedContent {
 
        @Override
        public Content getParent() {
-               if (isRoot) {
+               if (isMountBase) {
                        String mountPath = provider.getMountPath();
-                       if (mountPath == null)
+                       if (mountPath == null || mountPath.equals("/"))
                                return null;
                        String[] parent = ContentUtils.getParentPath(mountPath);
-                       return session.get(parent[0]);
+                       return getSession().get(parent[0]);
                }
                return new FsContent(this, path.getParent());
        }
 
+       @SuppressWarnings("unchecked")
        @Override
        public <C extends Closeable> C open(Class<C> clss) throws IOException, IllegalArgumentException {
                if (InputStream.class.isAssignableFrom(clss)) {
@@ -272,13 +301,22 @@ public class FsContent extends AbstractContent implements ProvidedContent {
        }
 
        /*
-        * ACCESSORS
+        * TYPING
         */
+
        @Override
-       public ProvidedSession getSession() {
-               return session;
+       public List<QName> getContentClasses() {
+               List<QName> res = new ArrayList<>();
+               if (Files.isDirectory(path))
+                       res.add(CrName.collection.qName());
+               // TODO add other types
+               return res;
        }
 
+       /*
+        * ACCESSORS
+        */
+
        @Override
        public FsContentProvider getProvider() {
                return provider;