From: Mathieu Baudier Date: Wed, 22 Dec 2021 10:04:24 +0000 (+0100) Subject: Start integrating GCR and JCR (not yet working) X-Git-Tag: argeo-commons-2.3.5~125 X-Git-Url: https://git.argeo.org/?a=commitdiff_plain;h=8767858f1fca02ea7c53ca244f6c1fa745d91ac4;p=lgpl%2Fargeo-commons.git Start integrating GCR and JCR (not yet working) --- diff --git a/org.argeo.api/src/org/argeo/api/gcr/AbstractContent.java b/org.argeo.api/src/org/argeo/api/gcr/AbstractContent.java deleted file mode 100644 index f318b99be..000000000 --- a/org.argeo.api/src/org/argeo/api/gcr/AbstractContent.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.argeo.api.gcr; - -import java.util.AbstractMap; -import java.util.HashSet; -import java.util.Set; - -public abstract class AbstractContent extends AbstractMap implements Content { - - @Override - public Set> entrySet() { - Set> result = new HashSet<>(); - for (String key : keys()) { - Entry entry = new Entry() { - - @Override - public String getKey() { - return key; - } - - @Override - public Object getValue() { - // TODO check type - return get(key, Object.class); - } - - @Override - public Object setValue(Object value) { - throw new UnsupportedOperationException(); - } - - }; - result.add(entry); - } - return result; - } - - protected abstract Iterable keys(); -} diff --git a/org.argeo.api/src/org/argeo/api/gcr/AttributeFormatter.java b/org.argeo.api/src/org/argeo/api/gcr/AttributeFormatter.java new file mode 100644 index 000000000..a628cda78 --- /dev/null +++ b/org.argeo.api/src/org/argeo/api/gcr/AttributeFormatter.java @@ -0,0 +1,19 @@ +package org.argeo.api.gcr; + +/** + * An attribute type MUST consistently parse a string to an object so that + * parse(obj.toString()).equals(obj) is verified. + * {@link #format(Object)} can be overridden to provide more efficient + * implementations but the returned + * String MUST be the same, that is format(obj).equals(obj.toString()) + * is verified. + */ +public interface AttributeFormatter { + /** Parses a String to a Java object. */ + T parse(String str) throws IllegalArgumentException; + + /** Default implementation returns {@link Object#toString()} on the argument. */ + default String format(T obj) { + return obj.toString(); + } +} diff --git a/org.argeo.api/src/org/argeo/api/gcr/Content.java b/org.argeo.api/src/org/argeo/api/gcr/Content.java index 34f11758a..b032aa7b8 100644 --- a/org.argeo.api/src/org/argeo/api/gcr/Content.java +++ b/org.argeo.api/src/org/argeo/api/gcr/Content.java @@ -13,7 +13,7 @@ public interface Content extends Iterable, Map { A get(String key, Class clss); - ContentSession getSession(); +// ContentSession getSession(); /* * DEFAULT METHODS @@ -29,12 +29,12 @@ public interface Content extends Iterable, Map { return get(key, String.class); } - default String attr(Enum key) { - return attr(key.name()); + default String attr(Object key) { + return key != null ? attr(key.toString()) : attr(null); } - default A get(Enum key, Class clss) { - return get(key.name(), clss); + default A get(Object key, Class clss) { + return key != null ? get(key.toString(), clss) : get(null, clss); } /* diff --git a/org.argeo.api/src/org/argeo/api/gcr/ContentRepository.java b/org.argeo.api/src/org/argeo/api/gcr/ContentRepository.java index 3349b30a1..0807075ce 100644 --- a/org.argeo.api/src/org/argeo/api/gcr/ContentRepository.java +++ b/org.argeo.api/src/org/argeo/api/gcr/ContentRepository.java @@ -1,6 +1,12 @@ package org.argeo.api.gcr; +import java.util.Locale; import java.util.function.Supplier; +/** + * A content repository is an actually running implementation of various kind of + * content system. It allows a pre-authenticated caller to open a session. + */ public interface ContentRepository extends Supplier { + ContentSession get(Locale locale); } diff --git a/org.argeo.api/src/org/argeo/api/gcr/ContentSession.java b/org.argeo.api/src/org/argeo/api/gcr/ContentSession.java index 78325b135..b7ffcd043 100644 --- a/org.argeo.api/src/org/argeo/api/gcr/ContentSession.java +++ b/org.argeo.api/src/org/argeo/api/gcr/ContentSession.java @@ -1,7 +1,11 @@ package org.argeo.api.gcr; -import java.util.function.Supplier; +import java.util.Locale; -public interface ContentSession extends Supplier { +import javax.security.auth.Subject; +public interface ContentSession { + Subject getSubject(); + + Locale getLocale(); } diff --git a/org.argeo.api/src/org/argeo/api/gcr/ContentStore.java b/org.argeo.api/src/org/argeo/api/gcr/ContentStore.java new file mode 100644 index 000000000..c9c90bc38 --- /dev/null +++ b/org.argeo.api/src/org/argeo/api/gcr/ContentStore.java @@ -0,0 +1,5 @@ +package org.argeo.api.gcr; + +public interface ContentStore { + +} diff --git a/org.argeo.api/src/org/argeo/api/gcr/ContentSystem.java b/org.argeo.api/src/org/argeo/api/gcr/ContentSystem.java new file mode 100644 index 000000000..04ac11a5a --- /dev/null +++ b/org.argeo.api/src/org/argeo/api/gcr/ContentSystem.java @@ -0,0 +1,5 @@ +package org.argeo.api.gcr; + +public interface ContentSystem { + +} diff --git a/org.argeo.api/src/org/argeo/api/gcr/ContentSystemProvider.java b/org.argeo.api/src/org/argeo/api/gcr/ContentSystemProvider.java new file mode 100644 index 000000000..4f0d66dcc --- /dev/null +++ b/org.argeo.api/src/org/argeo/api/gcr/ContentSystemProvider.java @@ -0,0 +1,7 @@ +package org.argeo.api.gcr; + +import java.util.function.Supplier; + +public interface ContentSystemProvider extends Supplier { + +} diff --git a/org.argeo.api/src/org/argeo/api/gcr/ContentUtils.java b/org.argeo.api/src/org/argeo/api/gcr/ContentUtils.java new file mode 100644 index 000000000..285669cb6 --- /dev/null +++ b/org.argeo.api/src/org/argeo/api/gcr/ContentUtils.java @@ -0,0 +1,75 @@ +package org.argeo.api.gcr; + +import java.io.PrintStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; +import java.util.Base64; +import java.util.List; +import java.util.function.BiConsumer; + +public class ContentUtils { + public static void traverse(Content content, BiConsumer doIt) { + traverse(content, doIt, 0); + } + + public static void traverse(Content content, BiConsumer doIt, int currentDepth) { + doIt.accept(content, currentDepth); + int nextDepth = currentDepth + 1; + for (Content child : content) { + traverse(child, doIt, nextDepth); + } + } + + public static void print(Content content, PrintStream out, int depth, boolean printText) { + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < depth; i++) { + sb.append(" "); + } + String prefix = sb.toString(); + out.println(prefix + content.getName()); + for (String key : content.keySet()) { + out.println(prefix + " " + key + "=" + content.get(key)); + } + if (printText) { + if (content.hasText()) { + out.println(""); + } + } + } + + public static URI bytesToDataURI(byte[] arr) { + String base64Str = Base64.getEncoder().encodeToString(arr); + try { + final String PREFIX = "data:application/octet-stream;base64,"; + return new URI(PREFIX + base64Str); + } catch (URISyntaxException e) { + throw new IllegalStateException("Cannot serialize bytes a Base64 data URI", e); + } + + } + + public static byte[] bytesFromDataURI(URI uri) { + if (!"data".equals(uri.getScheme())) + throw new IllegalArgumentException("URI must have 'data' as a scheme"); + String schemeSpecificPart = uri.getSchemeSpecificPart(); + int commaIndex = schemeSpecificPart.indexOf(','); + String prefix = schemeSpecificPart.substring(0, commaIndex); + List info = Arrays.asList(prefix.split(";")); + if (!info.contains("base64")) + throw new IllegalArgumentException("URI must specify base64"); + + String base64Str = uri.toString().substring(commaIndex); + return Base64.getDecoder().decode(base64Str); + + } + + public static boolean isString(T t) { + return t instanceof String; + } + + /** Singleton. */ + private ContentUtils() { + + } +} diff --git a/org.argeo.api/src/org/argeo/api/gcr/Contents.java b/org.argeo.api/src/org/argeo/api/gcr/Contents.java deleted file mode 100644 index 62b1e9b45..000000000 --- a/org.argeo.api/src/org/argeo/api/gcr/Contents.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.argeo.api.gcr; - -import java.io.PrintStream; -import java.util.function.BiConsumer; - -public class Contents { - public static void traverse(Content content, BiConsumer doIt) { - traverse(content, doIt, 0); - } - - public static void traverse(Content content, BiConsumer doIt, int currentDepth) { - doIt.accept(content, currentDepth); - int nextDepth = currentDepth + 1; - for (Content child : content) { - traverse(child, doIt, nextDepth); - } - } - - public static void print(Content content, PrintStream out, int depth, boolean printText) { - StringBuilder sb = new StringBuilder(); - for (int i = 0; i < depth; i++) { - sb.append(" "); - } - String prefix = sb.toString(); - out.println(prefix + content.getName()); - for (String key : content.keySet()) { - out.println(prefix + " " + key + "=" + content.get(key)); - } - if (printText) { - if (content.hasText()) { - out.println(""); - } - } - } - - public static boolean isString(T t) { - return t instanceof String; - } - - /** Singleton. */ - private Contents() { - - } -} diff --git a/org.argeo.api/src/org/argeo/api/gcr/StandardAttributeType.java b/org.argeo.api/src/org/argeo/api/gcr/StandardAttributeType.java new file mode 100644 index 000000000..b079aa552 --- /dev/null +++ b/org.argeo.api/src/org/argeo/api/gcr/StandardAttributeType.java @@ -0,0 +1,168 @@ +package org.argeo.api.gcr; + +import java.net.URI; +import java.net.URISyntaxException; +import java.time.Instant; +import java.time.format.DateTimeParseException; +import java.util.UUID; + +/** + * Minimal standard attribute types that MUST be supported. All related classes + * belong to java.base and can be implicitly derived form a given + * String. + */ +public enum StandardAttributeType { + BOOLEAN(Boolean.class, new BooleanFormatter()), // + // TODO Also support INTEGER ? + LONG(Long.class, new LongFormatter()), // + DOUBLE(Double.class, new DoubleFormatter()), // + INSTANT(Instant.class, new InstantFormatter()), // + UUID(UUID.class, new UuidFormatter()), // + URI(URI.class, new UriFormatter()), // + STRING(String.class, new StringFormatter()), // + ; + + private StandardAttributeType(Class clss, AttributeFormatter formatter) { + this.clss = clss; + this.formatter = formatter; + } + + private final Class clss; + private final AttributeFormatter formatter; + + public Class getClss() { + return clss; + } + + public AttributeFormatter getFormatter() { + return formatter; + } + + static Object parse(String str) { + if (str == null) + return null; + // order IS important + try { + if (str.length() == 4 || str.length() == 5) + return BOOLEAN.getFormatter().parse(str); + } catch (IllegalArgumentException e) { + // silent + } + try { + return LONG.getFormatter().parse(str); + } catch (IllegalArgumentException e) { + // silent + } + try { + return DOUBLE.getFormatter().parse(str); + } catch (IllegalArgumentException e) { + // silent + } + try { + return INSTANT.getFormatter().parse(str); + } catch (IllegalArgumentException e) { + // silent + } + try { + if (str.length() == 36) + return UUID.getFormatter().parse(str); + } catch (IllegalArgumentException e) { + // silent + } + try { + java.net.URI uri = (java.net.URI) URI.getFormatter().parse(str); + if (uri.getScheme() != null) + return uri; + String path = uri.getPath(); + if (path.indexOf('/') >= 0) + return uri; + // if it is not clearly a path, we will consider it as a string + // because their is no way to distinguish between 'any_string' + // and 'any_file_name'. + // Note that providing ./any_file_name would result in an equivalent URI + } catch (IllegalArgumentException e) { + // silent + } + + // default + return STRING.getFormatter().parse(str); + } + + static class StringFormatter implements AttributeFormatter { + + @Override + public String parse(String str) { + return str; + } + + @Override + public String format(String obj) { + return obj; + } + + } + + static class BooleanFormatter implements AttributeFormatter { + + /** + * @param str must be exactly equals to either 'true' or 'false' (different + * contract than {@link Boolean#parseBoolean(String)}. + */ + @Override + public Boolean parse(String str) throws IllegalArgumentException { + if ("true".equals(str)) + return Boolean.TRUE; + if ("false".equals(str)) + return Boolean.FALSE; + throw new IllegalArgumentException("Argument is neither 'true' or 'false' : " + str); + } + } + + static class LongFormatter implements AttributeFormatter { + @Override + public Long parse(String str) throws NumberFormatException { + return Long.parseLong(str); + } + } + + static class DoubleFormatter implements AttributeFormatter { + + @Override + public Double parse(String str) throws NumberFormatException { + return Double.parseDouble(str); + } + } + + static class InstantFormatter implements AttributeFormatter { + + @Override + public Instant parse(String str) throws IllegalArgumentException { + try { + return Instant.parse(str); + } catch (DateTimeParseException e) { + throw new IllegalArgumentException("Cannot parse '" + str + "' as an instant", e); + } + } + } + + static class UuidFormatter implements AttributeFormatter { + + @Override + public UUID parse(String str) throws IllegalArgumentException { + return java.util.UUID.fromString(str); + } + } + + static class UriFormatter implements AttributeFormatter { + + @Override + public URI parse(String str) throws IllegalArgumentException { + try { + return new URI(str); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Cannot parse " + str + " as an URI.", e); + } + } + + } +} diff --git a/org.argeo.api/src/org/argeo/api/gcr/fs/AbstractFsPath.java b/org.argeo.api/src/org/argeo/api/gcr/fs/AbstractFsPath.java new file mode 100644 index 000000000..c83fe385b --- /dev/null +++ b/org.argeo.api/src/org/argeo/api/gcr/fs/AbstractFsPath.java @@ -0,0 +1,387 @@ +package org.argeo.api.gcr.fs; + +import java.io.File; +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.LinkOption; +import java.nio.file.Path; +import java.nio.file.WatchEvent.Kind; +import java.nio.file.WatchEvent.Modifier; +import java.nio.file.WatchKey; +import java.nio.file.WatchService; +import java.util.Arrays; +import java.util.Iterator; +import java.util.NoSuchElementException; + +public abstract class AbstractFsPath, ST extends AbstractFsStore> implements Path { + private final FS fs; + /** null for non absolute paths */ + private final ST fileStore; + + private final String[] segments;// null means root + private final boolean absolute; + + private final String separator; + + // optim + private final int hashCode; + + public AbstractFsPath(FS filesSystem, String path) { + if (path == null) + throw new IllegalArgumentException("Path cannot be null"); + this.fs = filesSystem; + this.separator = fs.getSeparator(); + // TODO deal with both path and separator being empty strings + if (path.equals(separator)) {// root + this.segments = null; + this.absolute = true; + this.hashCode = 0; + this.fileStore = fs.getBaseFileStore(); + return; + } else if (path.equals("")) {// empty path + this.segments = new String[] { "" }; + this.absolute = false; + this.hashCode = "".hashCode(); + this.fileStore = null; + return; + } + + this.absolute = path.startsWith(toStringRoot()); + + String trimmedPath = path.substring(absolute ? toStringRoot().length() : 0, + path.endsWith(separator) ? path.length() - separator.length() : path.length()); + this.segments = trimmedPath.split(separator); + // clean up + for (int i = 0; i < this.segments.length; i++) { + this.segments[i] = cleanUpSegment(this.segments[i]); + } + this.hashCode = this.segments[this.segments.length - 1].hashCode(); + + this.fileStore = isAbsolute() ? fs.getFileStore(path) : null; + } + + protected AbstractFsPath(FS filesSystem, ST fileStore, String[] segments, boolean absolute) { + this.segments = segments; + this.absolute = absolute; + this.hashCode = segments == null ? 0 : segments[segments.length - 1].hashCode(); + this.separator = filesSystem.getSeparator(); +// super(path, path == null ? true : absolute, filesSystem.getSeparator()); +// assert path == null ? absolute == true : true; + this.fs = filesSystem; +// this.path = path; +// this.absolute = path == null ? true : absolute; + if (isAbsolute() && fileStore == null) + throw new IllegalArgumentException("Absolute path requires a file store"); + if (!isAbsolute() && fileStore != null) + throw new IllegalArgumentException("A file store should not be provided for a relative path"); + this.fileStore = fileStore; + assert !(absolute && fileStore == null); + } + + protected Path retrieve(String path) { + return getFileSystem().getPath(path); + } + + @Override + public FS getFileSystem() { + return fs; + } + + public ST getFileStore() { + return fileStore; + } + + @Override + public boolean isAbsolute() { + return absolute; + } + + @Override + public URI toUri() { + try { + return new URI(fs.provider().getScheme(), toString(), null); + } catch (URISyntaxException e) { + throw new IllegalStateException("Cannot create URI for " + toString(), e); + } + } + + @Override + public Path toAbsolutePath() { + if (isAbsolute()) + return this; + // FIXME it doesn't seem right + return newInstance(getSegments(), true); + } + + @Override + public Path toRealPath(LinkOption... options) throws IOException { + return this; + } + + @Override + public File toFile() { + throw new UnsupportedOperationException(); + } + + /* + * PATH OPERATIONS + */ + public final Path resolveSibling(Path other) { + if (other == null) + throw new NullPointerException(); + Path parent = getParent(); + return (parent == null) ? other : parent.resolve(other); + } + + @Override + public final Path resolveSibling(String other) { + return resolveSibling(getFileSystem().getPath(other)); + } + + public final Path resolve(String other) { + return resolve(retrieve(other)); + } + + public boolean startsWith(Path other) { + return toString().startsWith(other.toString()); + } + + public boolean endsWith(Path other) { + return toString().endsWith(other.toString()); + } + + @Override + public Path normalize() { + // always normalized + return this; + } + + @Override + public final Iterator iterator() { + return new Iterator() { + private int i = 0; + + @Override + public boolean hasNext() { + return (i < getNameCount()); + } + + @Override + public Path next() { + if (i < getNameCount()) { + Path result = getName(i); + i++; + return result; + } else { + throw new NoSuchElementException(); + } + } + + @Override + public void remove() { + throw new UnsupportedOperationException(); + } + }; + } + + @Override + public int compareTo(Path other) { + return toString().compareTo(other.toString()); + } + + public Path resolve(Path other) { + AbstractFsPath otherPath = (AbstractFsPath) other; + if (otherPath.isAbsolute()) + return other; + String[] newPath; + if (isRoot()) { + newPath = new String[otherPath.segments.length]; + System.arraycopy(otherPath.segments, 0, newPath, 0, otherPath.segments.length); + } else { + newPath = new String[segments.length + otherPath.segments.length]; + System.arraycopy(segments, 0, newPath, 0, segments.length); + System.arraycopy(otherPath.segments, 0, newPath, segments.length, otherPath.segments.length); + } + if (!absolute) + return newInstance(newPath, absolute); + else { + return newInstance(toString(newPath)); + } + } + + public Path relativize(Path other) { + if (equals(other)) + return newInstance(""); + if (other.toString().startsWith(this.toString())) { + String p1 = toString(); + String p2 = other.toString(); + String relative = p2.substring(p1.length(), p2.length()); + if (relative.charAt(0) == '/') + relative = relative.substring(1); + return newInstance(relative); + } + throw new IllegalArgumentException(other + " cannot be relativized against " + this); + } + + /* + * FACTORIES + */ + protected abstract AbstractFsPath newInstance(String path); + + protected abstract AbstractFsPath newInstance(String[] segments, boolean absolute); + + /* + * CUSTOMISATIONS + */ + protected String toStringRoot() { + return separator; + } + + protected String cleanUpSegment(String segment) { + return segment; + } + + protected boolean isRoot() { + return segments == null; + } + + protected boolean isEmpty() { + return segments.length == 1 && "".equals(segments[0]); + } + + /* + * PATH OPERATIONS + */ + public AbstractFsPath getRoot() { + return newInstance(toStringRoot()); + } + + public AbstractFsPath getParent() { + if (isRoot()) + return null; + // FIXME empty path? + if (segments.length == 1)// first level + return newInstance(toStringRoot()); + String[] parentPath = Arrays.copyOfRange(segments, 0, segments.length - 1); + if (!absolute) + return newInstance(parentPath, absolute); + else + return newInstance(toString(parentPath)); + } + + public AbstractFsPath getFileName() { + if (isRoot()) + return null; + return newInstance(segments[segments.length - 1]); + } + + public int getNameCount() { + if (isRoot()) + return 0; + return segments.length; + } + + public AbstractFsPath getName(int index) { + if (isRoot()) + return null; + return newInstance(segments[index]); + } + + public AbstractFsPath subpath(int beginIndex, int endIndex) { + if (isRoot()) + return null; + String[] parentPath = Arrays.copyOfRange(segments, beginIndex, endIndex); + return newInstance(parentPath, false); + } + + public boolean startsWith(String other) { + return toString().startsWith(other); + } + + public boolean endsWith(String other) { + return toString().endsWith(other); + } + + /* + * UTILITIES + */ + protected String toString(String[] path) { + if (isRoot()) + return toStringRoot(); + StringBuilder sb = new StringBuilder(); + if (isAbsolute()) + sb.append(separator); + for (int i = 0; i < path.length; i++) { + if (i != 0) + sb.append(separator); + sb.append(path[i]); + } + return sb.toString(); + } + + @Override + public String toString() { + return toString(segments); + } + + @Override + public int hashCode() { + return hashCode; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof AbstractFsPath)) + return false; + AbstractFsPath other = (AbstractFsPath) obj; + + if (isRoot()) {// root + if (other.isRoot())// root + return true; + else + return false; + } else { + if (other.isRoot())// root + return false; + } + // non root + if (segments.length != other.segments.length) + return false; + for (int i = 0; i < segments.length; i++) { + if (!segments[i].equals(other.segments[i])) + return false; + } + return true; + } + + @Override + protected Object clone() throws CloneNotSupportedException { + return newInstance(toString()); + } + + /* + * GETTERS / SETTERS + */ + protected String[] getSegments() { + return segments; + } + + protected String getSeparator() { + return separator; + } + + /* + * UNSUPPORTED + */ + @Override + public WatchKey register(WatchService watcher, Kind[] events, Modifier... modifiers) throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public WatchKey register(WatchService watcher, Kind... events) throws IOException { + throw new UnsupportedOperationException(); + } + +} diff --git a/org.argeo.api/src/org/argeo/api/gcr/fs/AbstractFsStore.java b/org.argeo.api/src/org/argeo/api/gcr/fs/AbstractFsStore.java new file mode 100644 index 000000000..00887346d --- /dev/null +++ b/org.argeo.api/src/org/argeo/api/gcr/fs/AbstractFsStore.java @@ -0,0 +1,7 @@ +package org.argeo.api.gcr.fs; + +import java.nio.file.FileStore; + +public abstract class AbstractFsStore extends FileStore { + +} diff --git a/org.argeo.api/src/org/argeo/api/gcr/fs/AbstractFsSystem.java b/org.argeo.api/src/org/argeo/api/gcr/fs/AbstractFsSystem.java new file mode 100644 index 000000000..36369ca6e --- /dev/null +++ b/org.argeo.api/src/org/argeo/api/gcr/fs/AbstractFsSystem.java @@ -0,0 +1,9 @@ +package org.argeo.api.gcr.fs; + +import java.nio.file.FileSystem; + +public abstract class AbstractFsSystem extends FileSystem { + public abstract ST getBaseFileStore(); + + public abstract ST getFileStore(String path); +} diff --git a/org.argeo.api/src/org/argeo/api/gcr/spi/AbstractContent.java b/org.argeo.api/src/org/argeo/api/gcr/spi/AbstractContent.java new file mode 100644 index 000000000..2da186ef2 --- /dev/null +++ b/org.argeo.api/src/org/argeo/api/gcr/spi/AbstractContent.java @@ -0,0 +1,48 @@ +package org.argeo.api.gcr.spi; + +import java.util.AbstractMap; +import java.util.HashSet; +import java.util.Set; + +import org.argeo.api.gcr.Content; + +public abstract class AbstractContent extends AbstractMap implements Content { + + @Override + public Set> entrySet() { + Set> result = new HashSet<>(); + for (String key : keys()) { + Entry entry = new Entry() { + + @Override + public String getKey() { + return key; + } + + @Override + public Object getValue() { + // TODO check type + return get(key, Object.class); + } + + @Override + public Object setValue(Object value) { + throw new UnsupportedOperationException(); + } + + }; + result.add(entry); + } + return result; + } + + protected abstract Iterable keys(); + + /* + * UTILITIES + */ + protected boolean isDefaultAttrTypeRequested(Class clss) { + // check whether clss is Object.class + return clss.isAssignableFrom(Object.class); + } +} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContent.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContent.java new file mode 100644 index 000000000..b136a0105 --- /dev/null +++ b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContent.java @@ -0,0 +1,155 @@ +package org.argeo.cms.jcr.gcr; + +import java.util.Calendar; +import java.util.Iterator; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.Property; +import javax.jcr.PropertyIterator; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; +import javax.jcr.Value; + +import org.argeo.api.gcr.Content; +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 Node jcrNode; + + protected JcrContent(JcrContentSession contentSession, Node node) { + this.contentSession = contentSession; + this.jcrNode = node; + } + + @Override + public String getName() { + return Jcr.getName(jcrNode); + } + + @Override + public A get(String key, Class clss) { + if (isDefaultAttrTypeRequested(clss)) { + return (A) get(jcrNode, key); + } + return (A) Jcr.get(jcrNode, key); + } + + @Override + public Iterator iterator() { + try { + return new JcrContentIterator(contentSession, jcrNode.getNodes()); + } catch (RepositoryException e) { + throw new JcrException("Cannot list children of " + jcrNode, e); + } + } + + @Override + protected Iterable keys() { + return new Iterable() { + + @Override + public Iterator iterator() { + try { + PropertyIterator propertyIterator = jcrNode.getProperties(); + return new JcrKeyIterator(contentSession, propertyIterator); + } catch (RepositoryException e) { + throw new JcrException("Cannot retrive properties from " + jcrNode, e); + } + } + }; + } + + public Node getJcrNode() { + return jcrNode; + } + + /** Cast to a standard Java object. */ + static Object get(Node node, String property) { + try { + Value value = node.getProperty(property).getValue(); + switch (value.getType()) { + case PropertyType.STRING: + return value.getString(); + case PropertyType.DOUBLE: + return (Double) value.getDouble(); + case PropertyType.LONG: + return (Long) value.getLong(); + case PropertyType.BOOLEAN: + return (Boolean) value.getBoolean(); + case PropertyType.DATE: + Calendar calendar = value.getDate(); + return calendar.toInstant(); + case PropertyType.BINARY: + throw new IllegalArgumentException("Binary is not supported as an attribute"); + default: + return value.getString(); + } + } catch (RepositoryException e) { + throw new JcrException("Cannot cast value from " + property + " of node " + node, e); + } + } + + static class JcrContentIterator implements Iterator { + private final JcrContentSession 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) { + this.contentSession = contentSession; + this.nodeIterator = nodeIterator; + } + + @Override + public boolean hasNext() { + return nodeIterator.hasNext(); + } + + @Override + public Content next() { + current = new JcrContent(contentSession, nodeIterator.nextNode()); + return current; + } + + @Override + public void remove() { + if (current != null) { + // current.getJcrNode().remove(); + } + throw new UnsupportedOperationException(); + } + + } + + static class JcrKeyIterator implements Iterator { + private final JcrContentSession contentSession; + private final PropertyIterator propertyIterator; + + protected JcrKeyIterator(JcrContentSession contentSession, PropertyIterator propertyIterator) { + this.contentSession = contentSession; + this.propertyIterator = propertyIterator; + } + + @Override + public boolean hasNext() { + return propertyIterator.hasNext(); + } + + @Override + public String next() { + Property property = null; + try { + property = propertyIterator.nextProperty(); + // TODO map standard property names + return property.getName(); + } catch (RepositoryException e) { + throw new JcrException("Cannot retrieve property " + property, null); + } + } + + } +} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContentRepository.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContentRepository.java new file mode 100644 index 000000000..057ed3089 --- /dev/null +++ b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContentRepository.java @@ -0,0 +1,35 @@ +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; + } + +} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContentSession.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContentSession.java new file mode 100644 index 000000000..98ecbc887 --- /dev/null +++ b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/gcr/JcrContentSession.java @@ -0,0 +1,47 @@ +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) () -> { + 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; + } + +} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/Jcr.java b/org.argeo.cms.jcr/src/org/argeo/jcr/Jcr.java index 72e325d35..bf5de1260 100644 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/Jcr.java +++ b/org.argeo.cms.jcr/src/org/argeo/jcr/Jcr.java @@ -598,6 +598,16 @@ public class Jcr { throw new JcrException("Cannot retrieve property " + property + " from " + node, e); } } + + public static T getAs(Node node, String property, Class clss) { + if(String.class.isAssignableFrom(clss)) { + return (T)get(node,property); + } else if(Long.class.isAssignableFrom(clss)) { + return (T)get(node,property); + }else { + throw new IllegalArgumentException("Unsupported format "+clss); + } + } /** * Get a multiple property as a list, doing a best effort to cast it as the diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystem.java b/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystem.java index 3d538e8bd..3d0fcfe66 100644 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystem.java +++ b/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystem.java @@ -23,10 +23,12 @@ import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.nodetype.NodeType; +import org.argeo.api.gcr.fs.AbstractFsStore; +import org.argeo.api.gcr.fs.AbstractFsSystem; import org.argeo.jcr.Jcr; import org.argeo.jcr.JcrUtils; -public class JcrFileSystem extends FileSystem { +public class JcrFileSystem extends AbstractFsSystem { private final JcrFileSystemProvider provider; private final Repository repository; diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrPath.java b/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrPath.java index 1a4d74706..8782be9ee 100644 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrPath.java +++ b/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrPath.java @@ -1,73 +1,63 @@ package org.argeo.jcr.fs; -import java.io.File; -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.file.FileSystem; -import java.nio.file.LinkOption; import java.nio.file.Path; -import java.nio.file.WatchEvent.Kind; -import java.nio.file.WatchEvent.Modifier; -import java.nio.file.WatchKey; -import java.nio.file.WatchService; -import java.util.Arrays; -import java.util.Iterator; -import java.util.NoSuchElementException; import javax.jcr.Node; import javax.jcr.RepositoryException; +import org.argeo.api.gcr.fs.AbstractFsPath; + /** A {@link Path} which contains a reference to a JCR {@link Node}. */ -public class JcrPath implements Path { +public class JcrPath extends AbstractFsPath { final static String separator = "/"; final static char separatorChar = '/'; - private final JcrFileSystem fs; - /** null for non absolute paths */ - private final WorkspaceFileStore fileStore; - private final String[] path;// null means root - private final boolean absolute; - - // optim - private final int hashCode; +// private final JcrFileSystem fs; +// /** null for non absolute paths */ +// private final WorkspaceFileStore fileStore; +// private final String[] path;// null means root +// private final boolean absolute; +// +// // optim +// private final int hashCode; public JcrPath(JcrFileSystem filesSystem, String path) { - this.fs = filesSystem; - if (path == null) - throw new JcrFsException("Path cannot be null"); - if (path.equals(separator)) {// root - this.path = null; - this.absolute = true; - this.hashCode = 0; - this.fileStore = fs.getBaseFileStore(); - return; - } else if (path.equals("")) {// empty path - this.path = new String[] { "" }; - this.absolute = false; - this.fileStore = null; - this.hashCode = "".hashCode(); - return; - } - - if (path.equals("~")) {// home - path = filesSystem.getUserHomePath(); - if (path == null) - throw new JcrFsException("No home directory available"); - } - - this.absolute = path.charAt(0) == separatorChar ? true : false; - - this.fileStore = absolute ? fs.getFileStore(path) : null; - - String trimmedPath = path.substring(absolute ? 1 : 0, - path.charAt(path.length() - 1) == separatorChar ? path.length() - 1 : path.length()); - this.path = trimmedPath.split(separator); - for (int i = 0; i < this.path.length; i++) { - this.path[i] = Text.unescapeIllegalJcrChars(this.path[i]); - } - this.hashCode = this.path[this.path.length - 1].hashCode(); - assert !(absolute && fileStore == null); + super(filesSystem, path); +// this.fs = filesSystem; +// if (path == null) +// throw new JcrFsException("Path cannot be null"); +// if (path.equals(separator)) {// root +// this.path = null; +// this.absolute = true; +// this.hashCode = 0; +// this.fileStore = fs.getBaseFileStore(); +// return; +// } else if (path.equals("")) {// empty path +// this.path = new String[] { "" }; +// this.absolute = false; +// this.fileStore = null; +// this.hashCode = "".hashCode(); +// return; +// } +// +// if (path.equals("~")) {// home +// path = filesSystem.getUserHomePath(); +// if (path == null) +// throw new JcrFsException("No home directory available"); +// } +// +// this.absolute = path.charAt(0) == separatorChar ? true : false; +// +// this.fileStore = absolute ? fs.getFileStore(path) : null; +// +// String trimmedPath = path.substring(absolute ? 1 : 0, +// path.charAt(path.length() - 1) == separatorChar ? path.length() - 1 : path.length()); +// this.path = trimmedPath.split(separator); +// for (int i = 0; i < this.path.length; i++) { +// this.path[i] = Text.unescapeIllegalJcrChars(this.path[i]); +// } +// this.hashCode = this.path[this.path.length - 1].hashCode(); +// assert !(absolute && fileStore == null); } public JcrPath(JcrFileSystem filesSystem, Node node) throws RepositoryException { @@ -76,53 +66,69 @@ public class JcrPath implements Path { /** Internal optimisation */ private JcrPath(JcrFileSystem filesSystem, WorkspaceFileStore fileStore, String[] path, boolean absolute) { - this.fs = filesSystem; - this.path = path; - this.absolute = path == null ? true : absolute; - if (this.absolute && fileStore == null) - throw new IllegalArgumentException("Absolute path requires a file store"); - if (!this.absolute && fileStore != null) - throw new IllegalArgumentException("A file store should not be provided for a relative path"); - this.fileStore = fileStore; - this.hashCode = path == null ? 0 : path[path.length - 1].hashCode(); - assert !(absolute && fileStore == null); + super(filesSystem, fileStore, path, absolute); +// this.fs = filesSystem; +// this.path = path; +// this.absolute = path == null ? true : absolute; +// if (this.absolute && fileStore == null) +// throw new IllegalArgumentException("Absolute path requires a file store"); +// if (!this.absolute && fileStore != null) +// throw new IllegalArgumentException("A file store should not be provided for a relative path"); +// this.fileStore = fileStore; +// this.hashCode = path == null ? 0 : path[path.length - 1].hashCode(); +// assert !(absolute && fileStore == null); } - @Override - public FileSystem getFileSystem() { - return fs; + protected String cleanUpSegment(String segment) { + return Text.unescapeIllegalJcrChars(segment); } @Override - public boolean isAbsolute() { - return absolute; + protected JcrPath newInstance(String path) { + return new JcrPath(getFileSystem(), path); } @Override - public Path getRoot() { - if (path == null) - return this; - return new JcrPath(fs, separator); - } + protected JcrPath newInstance(String[] segments, boolean absolute) { + return new JcrPath(getFileSystem(), getFileStore(), segments, absolute); - @Override - public String toString() { - return toFsPath(path); } - private String toFsPath(String[] path) { - if (path == null) - return "/"; - StringBuilder sb = new StringBuilder(); - if (isAbsolute()) - sb.append('/'); - for (int i = 0; i < path.length; i++) { - if (i != 0) - sb.append('/'); - sb.append(path[i]); - } - return sb.toString(); - } +// @Override +// public FileSystem getFileSystem() { +// return fs; +// } +// +// @Override +// public boolean isAbsolute() { +// return absolute; +// } +// +// @Override +// public Path getRoot() { +// if (path == null) +// return this; +// return new JcrPath(fs, separator); +// } +// +// @Override +// public String toString() { +// return toFsPath(path); +// } +// +// private String toFsPath(String[] path) { +// if (path == null) +// return "/"; +// StringBuilder sb = new StringBuilder(); +// if (isAbsolute()) +// sb.append('/'); +// for (int i = 0; i < path.length; i++) { +// if (i != 0) +// sb.append('/'); +// sb.append(path[i]); +// } +// return sb.toString(); +// } // @Deprecated // private String toJcrPath() { @@ -144,204 +150,187 @@ public class JcrPath implements Path { // return sb.toString(); // } - @Override - public Path getFileName() { - if (path == null) - return null; - return new JcrPath(fs, path[path.length - 1]); - } - - @Override - public Path getParent() { - if (path == null) - return null; - if (path.length == 1)// root - return new JcrPath(fs, separator); - String[] parentPath = Arrays.copyOfRange(path, 0, path.length - 1); - if (!absolute) - return new JcrPath(fs, null, parentPath, absolute); - else - return new JcrPath(fs, toFsPath(parentPath)); - } - - @Override - public int getNameCount() { - if (path == null) - return 0; - return path.length; - } - - @Override - public Path getName(int index) { - if (path == null) - return null; - return new JcrPath(fs, path[index]); - } - - @Override - public Path subpath(int beginIndex, int endIndex) { - if (path == null) - return null; - String[] parentPath = Arrays.copyOfRange(path, beginIndex, endIndex); - return new JcrPath(fs, null, parentPath, false); - } - - @Override - public boolean startsWith(Path other) { - return toString().startsWith(other.toString()); - } - - @Override - public boolean startsWith(String other) { - return toString().startsWith(other); - } - - @Override - public boolean endsWith(Path other) { - return toString().endsWith(other.toString()); - } - - @Override - public boolean endsWith(String other) { - return toString().endsWith(other); - } - - @Override - public Path normalize() { - // always normalized - return this; - } - - @Override - public Path resolve(Path other) { - JcrPath otherPath = (JcrPath) other; - if (otherPath.isAbsolute()) - return other; - String[] newPath; - if (path == null) { - newPath = new String[otherPath.path.length]; - System.arraycopy(otherPath.path, 0, newPath, 0, otherPath.path.length); - } else { - newPath = new String[path.length + otherPath.path.length]; - System.arraycopy(path, 0, newPath, 0, path.length); - System.arraycopy(otherPath.path, 0, newPath, path.length, otherPath.path.length); - } - if (!absolute) - return new JcrPath(fs, null, newPath, absolute); - else { - return new JcrPath(fs, toFsPath(newPath)); - } - } - - @Override - public final Path resolve(String other) { - return resolve(getFileSystem().getPath(other)); - } - - @Override - public final Path resolveSibling(Path other) { - if (other == null) - throw new NullPointerException(); - Path parent = getParent(); - return (parent == null) ? other : parent.resolve(other); - } - - @Override - public final Path resolveSibling(String other) { - return resolveSibling(getFileSystem().getPath(other)); - } - - @Override - public final Iterator iterator() { - return new Iterator() { - private int i = 0; - - @Override - public boolean hasNext() { - return (i < getNameCount()); - } - - @Override - public Path next() { - if (i < getNameCount()) { - Path result = getName(i); - i++; - return result; - } else { - throw new NoSuchElementException(); - } - } - - @Override - public void remove() { - throw new UnsupportedOperationException(); - } - }; - } - - @Override - public Path relativize(Path other) { - if (equals(other)) - return new JcrPath(fs, ""); - if (other.startsWith(this)) { - String p1 = toString(); - String p2 = other.toString(); - String relative = p2.substring(p1.length(), p2.length()); - if (relative.charAt(0) == '/') - relative = relative.substring(1); - return new JcrPath(fs, relative); - } - throw new IllegalArgumentException(other + " cannot be relativized against " + this); - } - - @Override - public URI toUri() { - try { - return new URI(fs.provider().getScheme(), toString(), null); - } catch (URISyntaxException e) { - throw new JcrFsException("Cannot create URI for " + toString(), e); - } - } - - @Override - public Path toAbsolutePath() { - if (isAbsolute()) - return this; - return new JcrPath(fs, fileStore, path, true); - } - - @Override - public Path toRealPath(LinkOption... options) throws IOException { - return this; - } - - @Override - public File toFile() { - throw new UnsupportedOperationException(); - } +// @Override +// public Path getFileName() { +// if (path == null) +// return null; +// return new JcrPath(fs, path[path.length - 1]); +// } +// +// @Override +// public Path getParent() { +// if (path == null) +// return null; +// if (path.length == 1)// root +// return new JcrPath(fs, separator); +// String[] parentPath = Arrays.copyOfRange(path, 0, path.length - 1); +// if (!absolute) +// return new JcrPath(fs, null, parentPath, absolute); +// else +// return new JcrPath(fs, toFsPath(parentPath)); +// } +// +// @Override +// public int getNameCount() { +// if (path == null) +// return 0; +// return path.length; +// } +// +// @Override +// public Path getName(int index) { +// if (path == null) +// return null; +// return new JcrPath(fs, path[index]); +// } +// +// @Override +// public Path subpath(int beginIndex, int endIndex) { +// if (path == null) +// return null; +// String[] parentPath = Arrays.copyOfRange(path, beginIndex, endIndex); +// return new JcrPath(fs, null, parentPath, false); +// } +// +// @Override +// public boolean startsWith(Path other) { +// return toString().startsWith(other.toString()); +// } +// +// @Override +// public boolean startsWith(String other) { +// return toString().startsWith(other); +// } +// +// @Override +// public boolean endsWith(Path other) { +// return toString().endsWith(other.toString()); +// } +// +// @Override +// public boolean endsWith(String other) { +// return toString().endsWith(other); +// } - @Override - public WatchKey register(WatchService watcher, Kind[] events, Modifier... modifiers) throws IOException { - // TODO Auto-generated method stub - return null; - } +// @Override +// public Path normalize() { +// // always normalized +// return this; +// } - @Override - public WatchKey register(WatchService watcher, Kind... events) throws IOException { - // TODO Auto-generated method stub - return null; - } +// @Override +// public Path resolve(Path other) { +// JcrPath otherPath = (JcrPath) other; +// if (otherPath.isAbsolute()) +// return other; +// String[] newPath; +// if (path == null) { +// newPath = new String[otherPath.path.length]; +// System.arraycopy(otherPath.path, 0, newPath, 0, otherPath.path.length); +// } else { +// newPath = new String[path.length + otherPath.path.length]; +// System.arraycopy(path, 0, newPath, 0, path.length); +// System.arraycopy(otherPath.path, 0, newPath, path.length, otherPath.path.length); +// } +// if (!absolute) +// return new JcrPath(fs, null, newPath, absolute); +// else { +// return new JcrPath(fs, toFsPath(newPath)); +// } +// } +// +// @Override +// public final Path resolve(String other) { +// return resolve(getFileSystem().getPath(other)); +// } +// +// @Override +// public final Path resolveSibling(Path other) { +// if (other == null) +// throw new NullPointerException(); +// Path parent = getParent(); +// return (parent == null) ? other : parent.resolve(other); +// } +// +// @Override +// public final Path resolveSibling(String other) { +// return resolveSibling(getFileSystem().getPath(other)); +// } +// +// @Override +// public final Iterator iterator() { +// return new Iterator() { +// private int i = 0; +// +// @Override +// public boolean hasNext() { +// return (i < getNameCount()); +// } +// +// @Override +// public Path next() { +// if (i < getNameCount()) { +// Path result = getName(i); +// i++; +// return result; +// } else { +// throw new NoSuchElementException(); +// } +// } +// +// @Override +// public void remove() { +// throw new UnsupportedOperationException(); +// } +// }; +// } +// +// @Override +// public Path relativize(Path other) { +// if (equals(other)) +// return new JcrPath(fs, ""); +// if (other.startsWith(this)) { +// String p1 = toString(); +// String p2 = other.toString(); +// String relative = p2.substring(p1.length(), p2.length()); +// if (relative.charAt(0) == '/') +// relative = relative.substring(1); +// return new JcrPath(fs, relative); +// } +// throw new IllegalArgumentException(other + " cannot be relativized against " + this); +// } - @Override - public int compareTo(Path other) { - return toString().compareTo(other.toString()); - } +// @Override +// public URI toUri() { +// try { +// return new URI(fs.provider().getScheme(), toString(), null); +// } catch (URISyntaxException e) { +// throw new JcrFsException("Cannot create URI for " + toString(), e); +// } +// } +// +// @Override +// public Path toAbsolutePath() { +// if (isAbsolute()) +// return this; +// return new JcrPath(fs, fileStore, path, true); +// } +// +// @Override +// public Path toRealPath(LinkOption... options) throws IOException { +// return this; +// } +// +// @Override +// public File toFile() { +// throw new UnsupportedOperationException(); +// } public Node getNode() throws RepositoryException { if (!isAbsolute())// TODO default dir throw new JcrFsException("Cannot get a JCR node from a relative path"); - assert fileStore != null; - return fileStore.toNode(path); + assert getFileStore() != null; + return getFileStore().toNode(getSegments()); // String pathStr = toJcrPath(); // Session session = fs.getSession(); // // TODO synchronize on the session ? @@ -349,45 +338,47 @@ public class JcrPath implements Path { // return null; // return session.getNode(pathStr); } +// +// @Override +// public boolean equals(Object obj) { +// if (!(obj instanceof JcrPath)) +// return false; +// JcrPath other = (JcrPath) obj; +// +// if (path == null) {// root +// if (other.path == null)// root +// return true; +// else +// return false; +// } else { +// if (other.path == null)// root +// return false; +// } +// // non root +// if (path.length != other.path.length) +// return false; +// for (int i = 0; i < path.length; i++) { +// if (!path[i].equals(other.path[i])) +// return false; +// } +// return true; +// } - @Override - public boolean equals(Object obj) { - if (!(obj instanceof JcrPath)) - return false; - JcrPath other = (JcrPath) obj; - - if (path == null) {// root - if (other.path == null)// root - return true; - else - return false; - } else { - if (other.path == null)// root - return false; - } - // non root - if (path.length != other.path.length) - return false; - for (int i = 0; i < path.length; i++) { - if (!path[i].equals(other.path[i])) - return false; - } - return true; - } - - @Override - public int hashCode() { - return hashCode; - } +// @Override +// public int hashCode() { +// return hashCode; +// } - @Override - protected Object clone() throws CloneNotSupportedException { - return new JcrPath(fs, toString()); - } +// @Override +// protected Object clone() throws CloneNotSupportedException { +// return new JcrPath(fs, toString()); +// } - @Override - protected void finalize() throws Throwable { - Arrays.fill(path, null); - } +// @Override +// protected void finalize() throws Throwable { +// Arrays.fill(path, null); +// } + + } diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/fs/WorkspaceFileStore.java b/org.argeo.cms.jcr/src/org/argeo/jcr/fs/WorkspaceFileStore.java index 6d9d05c2a..e8f24c9de 100644 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/fs/WorkspaceFileStore.java +++ b/org.argeo.cms.jcr/src/org/argeo/jcr/fs/WorkspaceFileStore.java @@ -11,10 +11,11 @@ import javax.jcr.RepositoryException; import javax.jcr.Session; import javax.jcr.Workspace; +import org.argeo.api.gcr.fs.AbstractFsStore; import org.argeo.jcr.JcrUtils; /** A {@link FileStore} implementation based on JCR {@link Workspace}. */ -public class WorkspaceFileStore extends FileStore { +public class WorkspaceFileStore extends AbstractFsStore { private final String mountPath; private final Workspace workspace; private final String workspaceName; diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/GcrContentTreeView.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/GcrContentTreeView.java new file mode 100644 index 000000000..354be5353 --- /dev/null +++ b/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/GcrContentTreeView.java @@ -0,0 +1,128 @@ +package org.argeo.cms.swt.gcr; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import org.argeo.api.gcr.Content; +import org.argeo.cms.gcr.fs.FsContentSession; +import org.argeo.cms.swt.CmsSwtUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.swt.widgets.TableItem; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.TreeItem; + +public class GcrContentTreeView extends Composite { + private Tree tree; + private Table table; + private Content rootContent; + + private Content selected; + + public GcrContentTreeView(Composite parent, int style, Content content) { + super(parent, style); + this.rootContent = content; + this.selected = rootContent; + setLayout(new GridLayout(2, false)); + initTree(); + GridData treeGd = CmsSwtUtils.fillHeight(); + treeGd.widthHint = 300; + tree.setLayoutData(treeGd); + initTable(); + + table.setLayoutData(CmsSwtUtils.fillAll()); + } + + protected void initTree() { + tree = new Tree(this, 0); + for (Content c : rootContent) { + TreeItem root = new TreeItem(tree, 0); + root.setText(c.getName()); + root.setData(c); + new TreeItem(root, 0); + } + tree.addListener(SWT.Expand, event -> { + final TreeItem root = (TreeItem) event.item; + TreeItem[] items = root.getItems(); + for (TreeItem item : items) { + if (item.getData() != null) + return; + item.dispose(); + } + Content content = (Content) root.getData(); + for (Content c : content) { + TreeItem item = new TreeItem(root, 0); + item.setText(c.getName()); + item.setData(c); + boolean hasChildren = true; + if (hasChildren) { + new TreeItem(item, 0); + } + } + }); + tree.addListener(SWT.Selection, event -> { + TreeItem item = (TreeItem) event.item; + selected = (Content) item.getData(); + refreshTable(); + }); + } + + protected void initTable() { + table = new Table(this, 0); + table.setLinesVisible(true); + table.setHeaderVisible(true); + TableColumn keyCol = new TableColumn(table, SWT.NONE); + keyCol.setText("Attribute"); + keyCol.setWidth(200); + TableColumn valueCol = new TableColumn(table, SWT.NONE); + valueCol.setText("Value"); + keyCol.setWidth(300); + refreshTable(); + } + + protected void refreshTable() { + for (TableItem item : table.getItems()) { + item.dispose(); + } + for (String key : selected.keySet()) { + TableItem item = new TableItem(table, 0); + item.setText(0, key); + Object value = selected.get(key); + item.setText(1, value.toString()); + } + table.getColumn(0).pack(); + table.getColumn(1).pack(); + } + + public static void main(String[] args) { + Path basePath; + if (args.length > 0) { + basePath = Paths.get(args[0]); + } else { + basePath = Paths.get(System.getProperty("user.home")); + } + + final Display display = new Display(); + final Shell shell = new Shell(display); + shell.setText(basePath.toString()); + shell.setLayout(new FillLayout()); + + FsContentSession contentSession = new FsContentSession(basePath); + GcrContentTreeView treeView = new GcrContentTreeView(shell, 0, contentSession.get()); + + shell.setSize(shell.computeSize(800, 600)); + shell.open(); + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) + display.sleep(); + } + display.dispose(); + } +} diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/SwtUiProvider.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/SwtUiProvider.java new file mode 100644 index 000000000..63b29c963 --- /dev/null +++ b/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/SwtUiProvider.java @@ -0,0 +1,11 @@ +package org.argeo.cms.swt.gcr; + +import org.argeo.api.cms.MvcProvider; +import org.argeo.api.gcr.Content; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + +@FunctionalInterface +public interface SwtUiProvider extends MvcProvider { + +} diff --git a/org.argeo.cms/src/org/argeo/cms/gcr/fs/FsContent.java b/org.argeo.cms/src/org/argeo/cms/gcr/fs/FsContent.java index 2766d0e3e..bb131f5aa 100644 --- a/org.argeo.cms/src/org/argeo/cms/gcr/fs/FsContent.java +++ b/org.argeo.cms/src/org/argeo/cms/gcr/fs/FsContent.java @@ -13,10 +13,10 @@ import java.util.HashSet; import java.util.Iterator; import java.util.Set; -import org.argeo.api.gcr.AbstractContent; import org.argeo.api.gcr.Content; import org.argeo.api.gcr.ContentResourceException; -import org.argeo.api.gcr.ContentSession; +import org.argeo.api.gcr.ContentSystemProvider; +import org.argeo.api.gcr.spi.AbstractContent; public class FsContent extends AbstractContent implements Content { private static final Set BASIC_KEYS = new HashSet<>( @@ -83,10 +83,6 @@ public class FsContent extends AbstractContent implements Content { return (A) value; } - @Override - public ContentSession getSession() { - return contentSession; - } @Override protected Iterable keys() { diff --git a/org.argeo.cms/src/org/argeo/cms/gcr/fs/FsContentSession.java b/org.argeo.cms/src/org/argeo/cms/gcr/fs/FsContentSession.java index 68cea4f93..75ec19172 100644 --- a/org.argeo.cms/src/org/argeo/cms/gcr/fs/FsContentSession.java +++ b/org.argeo.cms/src/org/argeo/cms/gcr/fs/FsContentSession.java @@ -6,10 +6,10 @@ import java.nio.file.Path; import java.nio.file.Paths; import org.argeo.api.gcr.Content; -import org.argeo.api.gcr.ContentSession; -import org.argeo.api.gcr.Contents; +import org.argeo.api.gcr.ContentSystemProvider; +import org.argeo.api.gcr.ContentUtils; -public class FsContentSession implements ContentSession { +public class FsContentSession implements ContentSystemProvider { private final Path rootPath; public FsContentSession(Path rootPath) { @@ -26,7 +26,7 @@ public class FsContentSession implements ContentSession { Path path = Paths.get("/home/mbaudier/tmp"); System.out.println(FileSystems.getDefault().supportedFileAttributeViews()); FsContentSession contentSession = new FsContentSession(path); - Contents.traverse(contentSession.get(), (c, d) -> Contents.print(c, System.out, d, true)); + ContentUtils.traverse(contentSession.get(), (c, d) -> ContentUtils.print(c, System.out, d, true)); } } diff --git a/org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContent.java b/org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContent.java index 19bae2d87..b42e0cf0d 100644 --- a/org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContent.java +++ b/org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContent.java @@ -4,9 +4,9 @@ import java.util.HashSet; import java.util.Iterator; import java.util.Set; -import org.argeo.api.gcr.AbstractContent; import org.argeo.api.gcr.Content; -import org.argeo.api.gcr.ContentSession; +import org.argeo.api.gcr.ContentSystemProvider; +import org.argeo.api.gcr.spi.AbstractContent; import org.w3c.dom.Attr; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; @@ -63,10 +63,6 @@ public class DomContent extends AbstractContent implements Content { return null; } - @Override - public ContentSession getSession() { - return contentSession; - } @Override public boolean hasText() { diff --git a/org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContentSession.java b/org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContentSession.java index ededa6cca..1f8c0e90c 100644 --- a/org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContentSession.java +++ b/org.argeo.cms/src/org/argeo/cms/gcr/xml/DomContentSession.java @@ -11,11 +11,11 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import org.argeo.api.gcr.Content; -import org.argeo.api.gcr.ContentSession; -import org.argeo.api.gcr.Contents; +import org.argeo.api.gcr.ContentSystemProvider; +import org.argeo.api.gcr.ContentUtils; import org.w3c.dom.Document; -public class DomContentSession implements ContentSession { +public class DomContentSession implements ContentSystemProvider { private Document document; public DomContentSession(Document document) { @@ -44,7 +44,7 @@ public class DomContentSession implements ContentSession { Document doc = dBuilder.parse(Files.newInputStream(testFile)); DomContentSession contentSession = new DomContentSession(doc); - Contents.traverse(contentSession.get(), (c, d) -> Contents.print(c, System.out, d, true)); + ContentUtils.traverse(contentSession.get(), (c, d) -> ContentUtils.print(c, System.out, d, true)); } }