String getName();
-// Iterable<String> keys();
+ String getPath();
+
+ Content getParent();
+
+ /*
+ * ATTRIBUTES OPERATIONS
+ */
<A> A get(String key, Class<A> clss) throws IllegalArgumentException;
-// ContentSession getSession();
+ /*
+ * CONTENT OPERATIONS
+ */
+ Content add(String name, ContentName... classes);
+
+ void remove();
/*
* DEFAULT METHODS
--- /dev/null
+package org.argeo.api.gcr;
+
+import java.util.UUID;
+
+public interface ContentName {
+ UUID getUuid();
+
+ ContentNamespace getNamespace();
+
+ String getName();
+
+ static boolean contains(ContentName[] classes, ContentName name) {
+ for (ContentName clss : classes) {
+ if (clss.getUuid().equals(name.getUuid()))
+ return true;
+ }
+ return false;
+ }
+}
--- /dev/null
+package org.argeo.api.gcr;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.nio.charset.StandardCharsets;
+import java.util.Objects;
+import java.util.UUID;
+
+/**
+ * An XML-style namespace with a related UUID v3.
+ *
+ * @see https://www.w3.org/TR/xml-names/
+ */
+public class ContentNamespace {
+ private final UUID uuid;
+ private final URI uri;
+
+ public ContentNamespace(String uri) {
+ try {
+ this.uri = new URI(uri);
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("Cannot interpret " + uri + " as URI", e);
+ }
+ this.uuid = namespaceUuid(this.uri);
+ }
+
+ public ContentNamespace(URI uri) {
+ this.uri = uri;
+ this.uuid = namespaceUuid(uri);
+ }
+
+ ContentNamespace(URI uri, UUID uuid) {
+ this.uri = uri;
+ assert uuid.equals(namespaceUuid(uri));
+ this.uuid = uuid;
+ }
+
+ /** Empty namespace */
+ private ContentNamespace() {
+ try {
+ this.uri = new URI("");
+ } catch (URISyntaxException e) {
+ throw new IllegalStateException("Cannot create empty URI");
+ }
+ this.uuid = NIL_UUID;
+ }
+
+ public UUID getUuid() {
+ return uuid;
+ }
+
+ public URI getUri() {
+ return uri;
+ }
+
+ public UUID nameUuid(String name) {
+ return nameUuid(getUuid(), name);
+ }
+
+
+ public final static UUID NIL_UUID = UUID.fromString("00000000-0000-0000-0000-000000000000");
+ public final static ContentNamespace EMPTY_NS = new ContentNamespace();
+
+ /**
+ * The UUID v3 of http://www.w3.org/2000/xmlns/ within the standard DNS
+ * namespace, to be used as a base for the namespaces.
+ *
+ * @see https://www.w3.org/TR/xml-names/#ns-decl
+ */
+ // uuidgen --md5 --namespace @dns --name http://www.w3.org/2000/xmlns/
+ // NOTE : must be declared before default namespaces
+ public final static UUID XMLNS_UUID = UUID.fromString("4b352aad-ba1c-3139-b9d3-41e5816f6088");
+ public final static ContentNamespace CR_NS = new ContentNamespace("http://argeo.org/ns/cr");
+
+
+ public static UUID namespaceUuid(URI namespaceUri) {
+ Objects.requireNonNull(namespaceUri, "Namespace URI cannot be null");
+ return nameUUIDv3(XMLNS_UUID, namespaceUri.toString().getBytes(StandardCharsets.UTF_8));
+ }
+
+ public static UUID nameUuid(UUID namespace, String name) {
+ Objects.requireNonNull(namespace, "Namespace cannot be null");
+ Objects.requireNonNull(namespace, "Name cannot be null");
+ return nameUUIDv3(namespace, name.getBytes(StandardCharsets.UTF_8));
+ }
+
+ /*
+ * CANONICAL IMPLEMENTATION based on java.util.UUID.nameUUIDFromBytes(byte[])
+ */
+ private static UUID nameUUIDv3(UUID namespace, byte[] name) {
+ byte[] arr = new byte[name.length + 16];
+ copyUuidBytes(namespace, arr, 0);
+ System.arraycopy(name, 0, arr, 16, name.length);
+ return UUID.nameUUIDFromBytes(arr);
+ }
+
+ private static void copyUuidBytes(UUID uuid, byte[] arr, int offset) {
+ long msb = uuid.getMostSignificantBits();
+ long lsb = uuid.getLeastSignificantBits();
+ assert arr.length >= 16 + offset;
+ for (int i = offset; i < 8 + offset; i++)
+ arr[i] = (byte) ((msb >> ((7 - i) * 8)) & 0xff);
+ for (int i = 8 + offset; i < 16 + offset; i++)
+ arr[i] = (byte) ((lsb >> ((15 - i) * 8)) & 0xff);
+ }
+}
Subject getSubject();
Locale getLocale();
+
+ Content get(String path);
}
+++ /dev/null
-package org.argeo.api.gcr;
-
-public interface ContentSystem {
-
-}
+++ /dev/null
-package org.argeo.api.gcr;
-
-import java.util.function.Supplier;
-
-public interface ContentSystemProvider extends Supplier<Content> {
-
-}
--- /dev/null
+package org.argeo.api.gcr;
+
+import java.util.UUID;
+
+/** Standard names. */
+public enum CrName implements ContentName {
+ /*
+ * TYPES
+ */
+ COLLECTION("collection"), // a collection type
+
+ /*
+ * ATTRIBUTES
+ */
+ UUID("uuid"), // the UUID of a content
+ //
+ ;
+
+ private String name;
+ private UUID uuid;
+
+ CrName(String name) {
+ this.name = name;
+ this.uuid = ContentNamespace.CR_NS.nameUuid(name);
+ }
+
+ @Override
+ public UUID getUuid() {
+ return uuid;
+ }
+
+ @Override
+ public ContentNamespace getNamespace() {
+ return ContentNamespace.CR_NS;
+ }
+
+ @Override
+ public String getName() {
+ return name;
+ }
+
+}
package org.argeo.api.gcr.spi;
import java.util.AbstractMap;
-import java.util.HashSet;
+import java.util.AbstractSet;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
import java.util.Set;
import org.argeo.api.gcr.Content;
@Override
public Set<Entry<String, Object>> entrySet() {
- Set<Entry<String, Object>> result = new HashSet<>();
- for (String key : keys()) {
- Entry<String, Object> entry = new Entry<String, Object>() {
+// Set<Entry<String, Object>> result = new HashSet<>();
+// for (String key : keys()) {
+// Entry<String, Object> entry = new Entry<String, Object>() {
+//
+// @Override
+// public String getKey() {
+// return key;
+// }
+//
+// @Override
+// public Object getValue() {
+// return get(key, Object.class);
+// }
+//
+// @Override
+// public Object setValue(Object value) {
+// throw new UnsupportedOperationException();
+// }
+//
+// };
+// result.add(entry);
+// }
+ Set<Entry<String, Object>> result = new AttrSet();
+ return result;
+ }
+
+ protected abstract Iterable<String> keys();
+
+ protected abstract void removeAttr(String key);
+
+ @Override
+ public String getPath() {
+ List<Content> ancestors = new ArrayList<>();
+ collectAncestors(ancestors, this);
+ StringBuilder path = new StringBuilder();
+ for (Content c : ancestors) {
+ String name = c.getName();
+ if (!"".equals(name))
+ path.append('/').append(name);
+ }
+ return path.toString();
+ }
+
+ private void collectAncestors(List<Content> ancestors, Content content) {
+ if (content == null)
+ return;
+ ancestors.add(0, content);
+ collectAncestors(ancestors, content.getParent());
+ }
+
+ /*
+ * UTILITIES
+ */
+ protected boolean isDefaultAttrTypeRequested(Class<?> clss) {
+ // check whether clss is Object.class
+ return clss.isAssignableFrom(Object.class);
+ }
+
+ @Override
+ public String toString() {
+ return "content "+getPath();
+ }
+
+ /*
+ * SUB CLASSES
+ */
+
+ class AttrSet extends AbstractSet<Entry<String, Object>> {
+
+ @Override
+ public Iterator<Entry<String, Object>> iterator() {
+ final Iterator<String> keys = keys().iterator();
+ Iterator<Entry<String, Object>> it = new Iterator<Map.Entry<String, Object>>() {
+
+ String key = null;
@Override
- public String getKey() {
- return key;
+ public boolean hasNext() {
+ return keys.hasNext();
}
@Override
- public Object getValue() {
+ public Entry<String, Object> next() {
+ key = keys.next();
// TODO check type
- return get(key, Object.class);
+ Object value = get(key, Object.class);
+ AbstractMap.SimpleEntry<String, Object> entry = new SimpleEntry<>(key, value);
+ return entry;
}
@Override
- public Object setValue(Object value) {
- throw new UnsupportedOperationException();
+ public void remove() {
+ if (key != null) {
+ AbstractContent.this.removeAttr(key);
+ } else {
+ throw new IllegalStateException("Iteration has not started");
+ }
}
};
- result.add(entry);
+ return it;
+ }
+
+ @Override
+ public int size() {
+ int count = 0;
+ for (String key : keys()) {
+ count++;
+ }
+ return count;
}
- return result;
- }
- protected abstract Iterable<String> keys();
-
- /*
- * UTILITIES
- */
- protected boolean isDefaultAttrTypeRequested(Class<?> clss) {
- // check whether clss is Object.class
- return clss.isAssignableFrom(Object.class);
}
}
--- /dev/null
+package org.argeo.api.gcr.spi;
+
+import java.util.function.Supplier;
+
+import org.argeo.api.gcr.Content;
+
+public interface ContentProvider extends Supplier<Content> {
+
+ Content get(String relativePath);
+
+}
import javax.jcr.PropertyType;
import javax.jcr.RepositoryException;
import javax.jcr.Value;
+import javax.jcr.nodetype.NodeType;
import org.argeo.api.gcr.Content;
+import org.argeo.api.gcr.ContentName;
import org.argeo.api.gcr.spi.AbstractContent;
import org.argeo.jcr.Jcr;
import org.argeo.jcr.JcrException;
public class JcrContent extends AbstractContent {
- private JcrContentSession contentSession;
+ private JcrContentProvider contentProvider;
private Node jcrNode;
- protected JcrContent(JcrContentSession contentSession, Node node) {
- this.contentSession = contentSession;
+ protected JcrContent(JcrContentProvider contentSession, Node node) {
+ this.contentProvider = contentSession;
this.jcrNode = node;
}
@Override
public Iterator<Content> iterator() {
try {
- return new JcrContentIterator(contentSession, jcrNode.getNodes());
+ return new JcrContentIterator(contentProvider, jcrNode.getNodes());
} catch (RepositoryException e) {
throw new JcrException("Cannot list children of " + jcrNode, e);
}
public Iterator<String> iterator() {
try {
PropertyIterator propertyIterator = jcrNode.getProperties();
- return new JcrKeyIterator(contentSession, propertyIterator);
+ return new JcrKeyIterator(contentProvider, propertyIterator);
} catch (RepositoryException e) {
throw new JcrException("Cannot retrive properties from " + jcrNode, e);
}
}
static class JcrContentIterator implements Iterator<Content> {
- private final JcrContentSession contentSession;
+ private final JcrContentProvider contentSession;
private final NodeIterator nodeIterator;
// we keep track in order to be able to delete it
private JcrContent current = null;
- protected JcrContentIterator(JcrContentSession contentSession, NodeIterator nodeIterator) {
+ protected JcrContentIterator(JcrContentProvider contentSession, NodeIterator nodeIterator) {
this.contentSession = contentSession;
this.nodeIterator = nodeIterator;
}
@Override
public void remove() {
if (current != null) {
- // current.getJcrNode().remove();
+ Jcr.remove(current.getJcrNode());
+ }
+ }
+
+ }
+
+ @Override
+ public Content getParent() {
+ return new JcrContent(contentProvider, Jcr.getParent(getJcrNode()));
+ }
+
+ @Override
+ public Content add(String name, ContentName... classes) {
+ if (classes.length > 0) {
+ ContentName primaryType = classes[0];
+ Node child = Jcr.addNode(getJcrNode(), name, primaryType.toString());
+ for (int i = 1; i < classes.length; i++) {
+ try {
+ child.addMixin(classes[i].toString());
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot add child to " + getJcrNode(), e);
+ }
+ }
+
+ } else {
+ Jcr.addNode(getJcrNode(), name, NodeType.NT_UNSTRUCTURED);
+ }
+ return null;
+ }
+
+ @Override
+ public void remove() {
+ Jcr.remove(getJcrNode());
+ }
+
+ @Override
+ protected void removeAttr(String key) {
+ Property property = Jcr.getProperty(getJcrNode(), key);
+ if (property != null) {
+ try {
+ property.remove();
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot remove property " + key + " from " + getJcrNode(), e);
}
- throw new UnsupportedOperationException();
}
}
static class JcrKeyIterator implements Iterator<String> {
- private final JcrContentSession contentSession;
+ private final JcrContentProvider contentSession;
private final PropertyIterator propertyIterator;
- protected JcrKeyIterator(JcrContentSession contentSession, PropertyIterator propertyIterator) {
+ protected JcrKeyIterator(JcrContentProvider contentSession, PropertyIterator propertyIterator) {
this.contentSession = contentSession;
this.propertyIterator = propertyIterator;
}
--- /dev/null
+package org.argeo.cms.jcr.gcr;
+
+import javax.jcr.Repository;
+import javax.jcr.Session;
+
+import org.argeo.api.gcr.Content;
+import org.argeo.api.gcr.spi.ContentProvider;
+import org.argeo.cms.jcr.CmsJcrUtils;
+
+public class JcrContentProvider implements ContentProvider {
+ private Repository jcrRepository;
+ private Session jcrSession;
+
+ public void init() {
+ jcrSession = CmsJcrUtils.openDataAdminSession(jcrRepository, null);
+ }
+
+ public void setJcrRepository(Repository jcrRepository) {
+ this.jcrRepository = jcrRepository;
+ }
+
+ @Override
+ public Content get() {
+ return null;
+ }
+
+ @Override
+ public Content get(String relativePath) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+
+}
+++ /dev/null
-package org.argeo.cms.jcr.gcr;
-
-import java.security.AccessController;
-import java.util.Locale;
-
-import javax.jcr.Repository;
-import javax.security.auth.Subject;
-
-import org.argeo.api.gcr.ContentRepository;
-import org.argeo.api.gcr.ContentSession;
-
-public class JcrContentRepository implements ContentRepository {
- private Repository jcrRepository;
-
- @Override
- public ContentSession get() {
- // TODO retrieve locale from Subject?
- return get(Locale.getDefault());
- }
-
- @Override
- public ContentSession get(Locale locale) {
- Subject subject = Subject.getSubject(AccessController.getContext());
- return new JcrContentSession(jcrRepository, subject, locale);
- }
-
- public Repository getJcrRepository() {
- return jcrRepository;
- }
-
- public void setJcrRepository(Repository jcrRepository) {
- this.jcrRepository = jcrRepository;
- }
-
-}
+++ /dev/null
-package org.argeo.cms.jcr.gcr;
-
-import java.security.PrivilegedAction;
-import java.util.Locale;
-
-import javax.jcr.Repository;
-import javax.jcr.RepositoryException;
-import javax.jcr.Session;
-import javax.security.auth.Subject;
-
-import org.argeo.api.gcr.ContentSession;
-import org.argeo.jcr.JcrException;
-
-public class JcrContentSession implements ContentSession {
- private Repository jcrRepository;
- private Subject subject;
- private Locale locale;
- private Session jcrSession;
-
- protected JcrContentSession(Repository jcrRepository, Subject subject, Locale locale) {
- this.jcrRepository = jcrRepository;
- this.subject = subject;
- this.locale = locale;
- this.jcrSession = Subject.doAs(this.subject, (PrivilegedAction<Session>) () -> {
- try {
- return jcrRepository.login();
- } catch (RepositoryException e) {
- throw new JcrException("Cannot log in to repository", e);
- }
- });
- }
-
- @Override
- public Subject getSubject() {
- return subject;
- }
-
- @Override
- public Locale getLocale() {
- return locale;
- }
-
- public Session getJcrSession() {
- return jcrSession;
- }
-
-}
import java.nio.file.Paths;
import org.argeo.api.gcr.Content;
-import org.argeo.cms.gcr.fs.FsContentSession;
+import org.argeo.cms.gcr.fs.FsContentProvider;
import org.argeo.cms.swt.CmsSwtUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.layout.FillLayout;
shell.setText(basePath.toString());
shell.setLayout(new FillLayout());
- FsContentSession contentSession = new FsContentSession(basePath);
+ FsContentProvider contentSession = new FsContentProvider(basePath);
GcrContentTreeView treeView = new GcrContentTreeView(shell, 0, contentSession.get());
shell.setSize(shell.computeSize(800, 600));
--- /dev/null
+package org.argeo.cms.gcr;
+
+import java.security.AccessController;
+import java.util.Locale;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.TreeMap;
+
+import javax.security.auth.Subject;
+
+import org.argeo.api.gcr.Content;
+import org.argeo.api.gcr.ContentRepository;
+import org.argeo.api.gcr.ContentSession;
+import org.argeo.api.gcr.spi.ContentProvider;
+import org.argeo.cms.internal.runtime.CmsContextImpl;
+
+public class CmsContentRepository implements ContentRepository {
+ private NavigableMap<String, ContentProvider> partitions = new TreeMap<>();
+
+ @Override
+ public ContentSession get() {
+ return get(CmsContextImpl.getCmsContext().getDefaultLocale());
+ }
+
+ @Override
+ public ContentSession get(Locale locale) {
+ Subject subject = Subject.getSubject(AccessController.getContext());
+ return new CmsContentSession(subject, locale);
+ }
+
+ public void addProvider(String base, ContentProvider provider) {
+ partitions.put(base, provider);
+ }
+
+ class CmsContentSession implements ContentSession {
+ private Subject subject;
+ private Locale locale;
+
+ public CmsContentSession(Subject subject, Locale locale) {
+ this.subject = subject;
+ this.locale = locale;
+ }
+
+ @Override
+ public Content get(String path) {
+ Map.Entry<String, ContentProvider> provider = partitions.floorEntry(path);
+ String relativePath = path.substring(provider.getKey().length());
+ return provider.getValue().get(relativePath);
+ }
+
+ @Override
+ public Subject getSubject() {
+ return subject;
+ }
+
+ @Override
+ public Locale getLocale() {
+ return locale;
+ }
+
+ }
+
+}
package org.argeo.cms.gcr.fs;
import java.io.IOException;
+import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import org.argeo.api.gcr.Content;
import org.argeo.api.gcr.ContentResourceException;
-import org.argeo.api.gcr.ContentSystemProvider;
+import org.argeo.api.gcr.CrName;
+import org.argeo.api.gcr.ContentName;
import org.argeo.api.gcr.spi.AbstractContent;
+import org.argeo.util.FsUtils;
public class FsContent extends AbstractContent implements Content {
+ private final static String USER_ = "user:";
+
private static final Set<String> BASIC_KEYS = new HashSet<>(
Arrays.asList("basic:creationTime", "basic:lastModifiedTime", "basic:size", "basic:fileKey"));
private static final Set<String> POSIX_KEYS;
POSIX_KEYS.add("posix:permissions");
}
- private FsContentSession contentSession;
+ private final FsContentProvider contentProvider;
private final Path path;
+ private final boolean isRoot;
- public FsContent(FsContentSession contentSession, Path path) {
+ public FsContent(FsContentProvider contentProvider, Path path) {
super();
- this.contentSession = contentSession;
+ this.contentProvider = contentProvider;
this.path = path;
+ this.isRoot = contentProvider.isRoot(path);
}
private boolean isPosix() {
return path.getFileSystem().supportedFileAttributeViews().contains("posix");
}
- @Override
- public Iterator<Content> iterator() {
- if (Files.isDirectory(path)) {
- try {
- return Files.list(path).map((p) -> (Content) new FsContent(contentSession, p)).iterator();
- } catch (IOException e) {
- throw new ContentResourceException("Cannot list " + path, e);
- }
- } else {
- return Collections.emptyIterator();
- }
- }
-
@Override
public String getName() {
+ if (isRoot)
+ return "";
return path.getFileName().toString();
}
+ /*
+ * ATTRIBUTES
+ */
+
@Override
public <A> A get(String key, Class<A> clss) {
Object value;
try {
- value = Files.getAttribute(path, key);
+ // We need to add user: when accessing via Files#getAttribute
+ value = Files.getAttribute(path, toFsAttributeKey(key));
} catch (IOException e) {
throw new ContentResourceException("Cannot retrieve attribute " + key + " for " + path, e);
}
return (A) value;
}
-
@Override
protected Iterable<String> keys() {
Set<String> result = new HashSet<>(isPosix() ? POSIX_KEYS : BASIC_KEYS);
if (udfav != null) {
try {
for (String name : udfav.list()) {
- result.add("user:" + name);
+ result.add(name);
}
} catch (IOException e) {
- throw new ContentResourceException("Cannot liast attributes for " + path, e);
+ throw new ContentResourceException("Cannot list attributes for " + path, e);
}
}
return result;
}
+ @Override
+ protected void removeAttr(String key) {
+ UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
+ try {
+ udfav.delete(key);
+ } catch (IOException e) {
+ throw new ContentResourceException("Cannot delete attribute " + key + " for " + path, e);
+ }
+ }
+
+ @Override
+ public Object put(String key, Object value) {
+ Object previous = get(key);
+ UserDefinedFileAttributeView udfav = Files.getFileAttributeView(path, UserDefinedFileAttributeView.class);
+ ByteBuffer bb = ByteBuffer.wrap(value.toString().getBytes(StandardCharsets.UTF_8));
+ try {
+ int size = udfav.write(key, bb);
+ } catch (IOException e) {
+ throw new ContentResourceException("Cannot delete attribute " + key + " for " + path, e);
+ }
+ return previous;
+ }
+
+ protected String toFsAttributeKey(String key) {
+ if (POSIX_KEYS.contains(key))
+ return key;
+ else
+ return USER_ + key;
+ }
+
+ /*
+ * CONTENT OPERATIONS
+ */
+ @Override
+ public Iterator<Content> iterator() {
+ if (Files.isDirectory(path)) {
+ try {
+ return Files.list(path).map((p) -> (Content) new FsContent(contentProvider, p)).iterator();
+ } catch (IOException e) {
+ throw new ContentResourceException("Cannot list " + path, e);
+ }
+ } else {
+ return Collections.emptyIterator();
+ }
+ }
+
+ @Override
+ public Content add(String name, ContentName... classes) {
+ try {
+ Path newPath = path.resolve(name);
+ if (ContentName.contains(classes, CrName.COLLECTION))
+ Files.createDirectory(newPath);
+ else
+ Files.createFile(newPath);
+
+// for(ContentClass clss:classes) {
+// Files.setAttribute(newPath, name, newPath, null)
+// }
+ return new FsContent(contentProvider, newPath);
+ } catch (IOException e) {
+ throw new ContentResourceException("Cannot create new content", e);
+ }
+ }
+
+ @Override
+ public void remove() {
+ FsUtils.delete(path);
+ }
+
+ @Override
+ public Content getParent() {
+ if (isRoot)
+ return null;// TODO deal with mounts
+ return new FsContent(contentProvider, path.getParent());
+ }
+
}
--- /dev/null
+package org.argeo.cms.gcr.fs;
+
+import java.io.IOException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.argeo.api.gcr.Content;
+import org.argeo.api.gcr.ContentResourceException;
+import org.argeo.api.gcr.spi.ContentProvider;
+
+public class FsContentProvider implements ContentProvider {
+ private final Path rootPath;
+
+ public FsContentProvider(Path rootPath) {
+ super();
+ this.rootPath = rootPath;
+ }
+
+ boolean isRoot(Path path) {
+ try {
+ return Files.isSameFile(rootPath, path);
+ } catch (IOException e) {
+ throw new ContentResourceException(e);
+ }
+ }
+
+ @Override
+ public Content get() {
+ return new FsContent(this, rootPath);
+ }
+
+ @Override
+ public Content get(String relativePath) {
+ return new FsContent(this, rootPath.resolve(relativePath));
+ }
+}
+++ /dev/null
-package org.argeo.cms.gcr.fs;
-
-import java.nio.file.FileSystems;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-
-import org.argeo.api.gcr.Content;
-import org.argeo.api.gcr.ContentSystemProvider;
-import org.argeo.api.gcr.ContentUtils;
-
-public class FsContentSession implements ContentSystemProvider {
- private final Path rootPath;
-
- public FsContentSession(Path rootPath) {
- super();
- this.rootPath = rootPath;
- }
-
- @Override
- public Content get() {
- return new FsContent(this, rootPath);
- }
-
- public static void main(String[] args) {
- Path path = Paths.get("/home/mbaudier/tmp");
- System.out.println(FileSystems.getDefault().supportedFileAttributeViews());
- FsContentSession contentSession = new FsContentSession(path);
- ContentUtils.traverse(contentSession.get(), (c, d) -> ContentUtils.print(c, System.out, d, true));
-
- }
-}
import java.util.Set;
import org.argeo.api.gcr.Content;
-import org.argeo.api.gcr.ContentSystemProvider;
+import org.argeo.api.gcr.ContentName;
import org.argeo.api.gcr.spi.AbstractContent;
import org.w3c.dom.Attr;
import org.w3c.dom.Element;
public class DomContent extends AbstractContent implements Content {
- private final DomContentSession contentSession;
+ private final DomContentProvider contentProvider;
private final Element element;
// private String text = null;
private Boolean hasText = null;
- public DomContent(DomContentSession contentSession, Element element) {
- this.contentSession = contentSession;
+ public DomContent(DomContentProvider contentProvider, Element element) {
+ this.contentProvider = contentProvider;
this.element = element;
}
@Override
public Iterator<Content> iterator() {
NodeList nodeList = element.getChildNodes();
- return new ElementIterator(contentSession, nodeList);
+ return new ElementIterator(contentProvider, nodeList);
}
@Override
return null;
}
-
@Override
public boolean hasText() {
// return element instanceof Text;
nodes: for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (node instanceof Text) {
- Text text =(Text) node;
+ Text text = (Text) node;
if (!text.isElementContentWhitespace()) {
hasText = true;
break nodes;
return null;
}
+ @Override
+ public Content getParent() {
+ Node parent = element.getParentNode();
+ if (parent == null)
+ return null;
+ if (!(parent instanceof Element))
+ throw new IllegalStateException("Parent is not an element");
+ return new DomContent(contentProvider, (Element) parent);
+ }
+
+ @Override
+ public Content add(String name, ContentName... classes) {
+ // TODO consider classes
+ Element child = contentProvider.createElement(name);
+ return new DomContent(contentProvider, child);
+ }
+
+ @Override
+ public void remove() {
+ // TODO make it more robust
+ element.getParentNode().removeChild(element);
+
+ }
+
+ @Override
+ protected void removeAttr(String key) {
+ element.removeAttribute(key);
+
+ }
+
}
--- /dev/null
+package org.argeo.cms.gcr.xml;
+
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Set;
+
+import javax.xml.parsers.DocumentBuilder;
+import javax.xml.parsers.DocumentBuilderFactory;
+
+import org.argeo.api.gcr.Content;
+import org.argeo.api.gcr.ContentUtils;
+import org.argeo.api.gcr.spi.ContentProvider;
+import org.w3c.dom.Document;
+import org.w3c.dom.Element;
+
+public class DomContentProvider implements ContentProvider {
+ private Document document;
+
+ public DomContentProvider(Document document) {
+ this.document = document;
+ this.document.normalizeDocument();
+ }
+
+ @Override
+ public Content get() {
+ return new DomContent(this, document.getDocumentElement());
+ }
+
+ public Element createElement(String name) {
+ return document.createElement(name);
+ }
+
+ @Override
+ public Content get(String relativePath) {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ public static void main(String args[]) throws Exception {
+ HashMap<String, Object> map = new HashMap<>();
+ map.put(null, "test");
+ System.out.println(map.get(null));
+
+ Set<String> set = new HashSet<>();
+ set.add(null);
+
+ DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
+ DocumentBuilder dBuilder = factory.newDocumentBuilder();
+ Path testFile;
+ testFile = Paths.get(System.getProperty("user.home") + "/dev/git/unstable/argeo-commons/pom.xml");
+ testFile = Paths.get(System.getProperty("user.home") + "/tmp/test.xml");
+ Document doc = dBuilder.parse(Files.newInputStream(testFile));
+
+ DomContentProvider contentSession = new DomContentProvider(doc);
+ ContentUtils.traverse(contentSession.get(), (c, d) -> ContentUtils.print(c, System.out, d, true));
+
+ }
+}
+++ /dev/null
-package org.argeo.cms.gcr.xml;
-
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.nio.file.Paths;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Set;
-
-import javax.xml.parsers.DocumentBuilder;
-import javax.xml.parsers.DocumentBuilderFactory;
-
-import org.argeo.api.gcr.Content;
-import org.argeo.api.gcr.ContentSystemProvider;
-import org.argeo.api.gcr.ContentUtils;
-import org.w3c.dom.Document;
-
-public class DomContentSession implements ContentSystemProvider {
- private Document document;
-
- public DomContentSession(Document document) {
- this.document = document;
- this.document.normalizeDocument();
- }
-
- @Override
- public Content get() {
- return new DomContent(this, document.getDocumentElement());
- }
-
- public static void main(String args[]) throws Exception {
- HashMap<String, Object> map = new HashMap<>();
- map.put(null, "test");
- System.out.println(map.get(null));
-
- Set<String> set = new HashSet<>();
- set.add(null);
-
- DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
- DocumentBuilder dBuilder = factory.newDocumentBuilder();
- Path testFile;
- testFile = Paths.get(System.getProperty("user.home") + "/dev/git/unstable/argeo-commons/pom.xml");
- testFile = Paths.get(System.getProperty("user.home") + "/tmp/test.xml");
- Document doc = dBuilder.parse(Files.newInputStream(testFile));
-
- DomContentSession contentSession = new DomContentSession(doc);
- ContentUtils.traverse(contentSession.get(), (c, d) -> ContentUtils.print(c, System.out, d, true));
-
- }
-}
import org.w3c.dom.NodeList;
public class ElementIterator implements Iterator<Content> {
- private final DomContentSession contentSession;
+ private final DomContentProvider contentSession;
private final NodeList nodeList;
private int currentIndex;
private final int length;
private Element nextElement = null;
- public ElementIterator(DomContentSession contentSession, NodeList nodeList) {
+ public ElementIterator(DomContentProvider contentSession, NodeList nodeList) {
this.contentSession = contentSession;
this.nodeList = nodeList;
--- /dev/null
+package org.argeo.util;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.StandardCopyOption;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
+
+/** Synchronises two directory structures. */
+public class BasicSyncFileVisitor extends SimpleFileVisitor<Path> {
+ // TODO make it configurable
+ private boolean trace = false;
+
+ private final Path sourceBasePath;
+ private final Path targetBasePath;
+ private final boolean delete;
+ private final boolean recursive;
+
+ private SyncResult<Path> syncResult = new SyncResult<>();
+
+ public BasicSyncFileVisitor(Path sourceBasePath, Path targetBasePath, boolean delete, boolean recursive) {
+ this.sourceBasePath = sourceBasePath;
+ this.targetBasePath = targetBasePath;
+ this.delete = delete;
+ this.recursive = recursive;
+ }
+
+ @Override
+ public FileVisitResult preVisitDirectory(Path sourceDir, BasicFileAttributes attrs) throws IOException {
+ if (!recursive && !sourceDir.equals(sourceBasePath))
+ return FileVisitResult.SKIP_SUBTREE;
+ Path targetDir = toTargetPath(sourceDir);
+ Files.createDirectories(targetDir);
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult postVisitDirectory(Path sourceDir, IOException exc) throws IOException {
+ if (delete) {
+ Path targetDir = toTargetPath(sourceDir);
+ for (Path targetPath : Files.newDirectoryStream(targetDir)) {
+ Path sourcePath = sourceDir.resolve(targetPath.getFileName());
+ if (!Files.exists(sourcePath)) {
+ try {
+ FsUtils.delete(targetPath);
+ deleted(targetPath);
+ } catch (Exception e) {
+ deleteFailed(targetPath, exc);
+ }
+ }
+ }
+ }
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult visitFile(Path sourceFile, BasicFileAttributes attrs) throws IOException {
+ Path targetFile = toTargetPath(sourceFile);
+ try {
+ if (!Files.exists(targetFile)) {
+ Files.copy(sourceFile, targetFile);
+ added(sourceFile, targetFile);
+ } else {
+ if (shouldOverwrite(sourceFile, targetFile)) {
+ Files.copy(sourceFile, targetFile, StandardCopyOption.REPLACE_EXISTING);
+ }
+ }
+ } catch (Exception e) {
+ copyFailed(sourceFile, targetFile, e);
+ }
+ return FileVisitResult.CONTINUE;
+ }
+
+ protected boolean shouldOverwrite(Path sourceFile, Path targetFile) throws IOException {
+ long sourceSize = Files.size(sourceFile);
+ long targetSize = Files.size(targetFile);
+ if (sourceSize != targetSize) {
+ return true;
+ }
+ FileTime sourceLastModif = Files.getLastModifiedTime(sourceFile);
+ FileTime targetLastModif = Files.getLastModifiedTime(targetFile);
+ if (sourceLastModif.compareTo(targetLastModif) > 0)
+ return true;
+ return shouldOverwriteLaterSameSize(sourceFile, targetFile);
+ }
+
+ protected boolean shouldOverwriteLaterSameSize(Path sourceFile, Path targetFile) {
+ return false;
+ }
+
+// @Override
+// public FileVisitResult visitFileFailed(Path sourceFile, IOException exc) throws IOException {
+// error("Cannot sync " + sourceFile, exc);
+// return FileVisitResult.CONTINUE;
+// }
+
+ private Path toTargetPath(Path sourcePath) {
+ Path relativePath = sourceBasePath.relativize(sourcePath);
+ Path targetPath = targetBasePath.resolve(relativePath.toString());
+ return targetPath;
+ }
+
+ public Path getSourceBasePath() {
+ return sourceBasePath;
+ }
+
+ public Path getTargetBasePath() {
+ return targetBasePath;
+ }
+
+ protected void added(Path sourcePath, Path targetPath) {
+ syncResult.getAdded().add(targetPath);
+ if (isTraceEnabled())
+ trace("Added " + sourcePath + " as " + targetPath);
+ }
+
+ protected void modified(Path sourcePath, Path targetPath) {
+ syncResult.getModified().add(targetPath);
+ if (isTraceEnabled())
+ trace("Overwritten from " + sourcePath + " to " + targetPath);
+ }
+
+ protected void copyFailed(Path sourcePath, Path targetPath, Exception e) {
+ syncResult.addError(sourcePath, targetPath, e);
+ if (isTraceEnabled())
+ error("Cannot copy " + sourcePath + " to " + targetPath, e);
+ }
+
+ protected void deleted(Path targetPath) {
+ syncResult.getDeleted().add(targetPath);
+ if (isTraceEnabled())
+ trace("Deleted " + targetPath);
+ }
+
+ protected void deleteFailed(Path targetPath, Exception e) {
+ syncResult.addError(null, targetPath, e);
+ if (isTraceEnabled())
+ error("Cannot delete " + targetPath, e);
+ }
+
+ /** Log error. */
+ protected void error(Object obj, Throwable e) {
+ System.err.println(obj);
+ e.printStackTrace();
+ }
+
+ protected boolean isTraceEnabled() {
+ return trace;
+ }
+
+ protected void trace(Object obj) {
+ System.out.println(obj);
+ }
+
+ public SyncResult<Path> getSyncResult() {
+ return syncResult;
+ }
+
+}
--- /dev/null
+package org.argeo.util;
+
+import java.io.IOException;
+import java.nio.file.FileVisitResult;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.SimpleFileVisitor;
+import java.nio.file.attribute.BasicFileAttributes;
+
+/** Utilities around the standard Java file abstractions. */
+public class FsUtils {
+ /** Sync a source path with a target path. */
+ public static void sync(Path sourceBasePath, Path targetBasePath) {
+ sync(sourceBasePath, targetBasePath, false);
+ }
+
+ /** Sync a source path with a target path. */
+ public static void sync(Path sourceBasePath, Path targetBasePath, boolean delete) {
+ sync(new BasicSyncFileVisitor(sourceBasePath, targetBasePath, delete, true));
+ }
+
+ public static void sync(BasicSyncFileVisitor syncFileVisitor) {
+ try {
+ Files.walkFileTree(syncFileVisitor.getSourceBasePath(), syncFileVisitor);
+ } catch (Exception e) {
+ throw new RuntimeException("Cannot sync " + syncFileVisitor.getSourceBasePath() + " with "
+ + syncFileVisitor.getTargetBasePath(), e);
+ }
+ }
+
+ /**
+ * Deletes this path, recursively if needed. Does nothing if the path does not
+ * exist.
+ */
+ public static void delete(Path path) {
+ try {
+ if (!Files.exists(path))
+ return;
+ Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
+ @Override
+ public FileVisitResult postVisitDirectory(Path directory, IOException e) throws IOException {
+ if (e != null)
+ throw e;
+ Files.delete(directory);
+ return FileVisitResult.CONTINUE;
+ }
+
+ @Override
+ public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
+ Files.delete(file);
+ return FileVisitResult.CONTINUE;
+ }
+ });
+ } catch (IOException e) {
+ throw new RuntimeException("Cannot delete " + path, e);
+ }
+ }
+
+ /** Singleton. */
+ private FsUtils() {
+ }
+
+}
--- /dev/null
+package org.argeo.util;
+
+import java.time.Instant;
+import java.util.Set;
+import java.util.TreeSet;
+
+/** Describes what happendend during a sync operation. */
+public class SyncResult<T> {
+ private final Set<T> added = new TreeSet<>();
+ private final Set<T> modified = new TreeSet<>();
+ private final Set<T> deleted = new TreeSet<>();
+ private final Set<Error> errors = new TreeSet<>();
+
+ public Set<T> getAdded() {
+ return added;
+ }
+
+ public Set<T> getModified() {
+ return modified;
+ }
+
+ public Set<T> getDeleted() {
+ return deleted;
+ }
+
+ public Set<Error> getErrors() {
+ return errors;
+ }
+
+ public void addError(T sourcePath, T targetPath, Exception e) {
+ Error error = new Error(sourcePath, targetPath, e);
+ errors.add(error);
+ }
+
+ public boolean noModification() {
+ return modified.isEmpty() && deleted.isEmpty() && added.isEmpty();
+ }
+
+ @Override
+ public String toString() {
+ if (noModification())
+ return "No modification.";
+ StringBuffer sb = new StringBuffer();
+ for (T p : modified)
+ sb.append("MOD ").append(p).append('\n');
+ for (T p : deleted)
+ sb.append("DEL ").append(p).append('\n');
+ for (T p : added)
+ sb.append("ADD ").append(p).append('\n');
+ for (Error error : errors)
+ sb.append(error).append('\n');
+ return sb.toString();
+ }
+
+ public class Error implements Comparable<Error> {
+ private final T sourcePath;// if null this is a failed delete
+ private final T targetPath;
+ private final Exception exception;
+ private final Instant timestamp = Instant.now();
+
+ public Error(T sourcePath, T targetPath, Exception e) {
+ super();
+ this.sourcePath = sourcePath;
+ this.targetPath = targetPath;
+ this.exception = e;
+ }
+
+ public T getSourcePath() {
+ return sourcePath;
+ }
+
+ public T getTargetPath() {
+ return targetPath;
+ }
+
+ public Exception getException() {
+ return exception;
+ }
+
+ public Instant getTimestamp() {
+ return timestamp;
+ }
+
+ @Override
+ public int compareTo(Error o) {
+ return timestamp.compareTo(o.timestamp);
+ }
+
+ @Override
+ public int hashCode() {
+ return timestamp.hashCode();
+ }
+
+ @Override
+ public String toString() {
+ return "ERR " + timestamp + (sourcePath == null ? "Deletion failed" : "Copy failed " + sourcePath) + " "
+ + targetPath + " " + exception.getMessage();
+ }
+
+ }
+}
interface NodeOID {
String BASE = "1.3.6.1.4.1" + ".48308" + ".1";
+ // uuidgen --md5 --namespace @oid --name 1.3.6.1.4.1.48308
+ String BASE_UUID_V3 = "6869e86b-96b7-3d49-b6ab-ffffc5ad95fb";
+
+ // uuidgen --sha1 --namespace @oid --name 1.3.6.1.4.1.48308
+ String BASE_UUID_V5 = "58873947-460c-59a6-a7b4-28a97def5f27";
+
// ATTRIBUTE TYPES
String ATTRIBUTE_TYPES = BASE + ".4";