org.argeo.api.cms \
org.argeo.cms \
org.argeo.cms.ux \
-org.argeo.cms.ee \
+org.argeo.cms.jshell \
+org.argeo.cms.lib.json \
org.argeo.cms.lib.jetty \
org.argeo.cms.lib.dbus \
org.argeo.cms.lib.sshd \
-org.argeo.cms.jshell \
+org.argeo.cms.ee \
org.argeo.cms.cli \
osgi/equinox/org.argeo.cms.lib.equinox \
swt/org.argeo.swt.minidesktop \
modified version without this exception; this exception also makes it possible
to release a modified version which carries forward this exception.
+# Apache License Permission
+
+Linking Argeo Commons statically or dynamically with other modules is making a
+combined work based on Argeo Commons. Thus, the terms and conditions of the GNU
+General Public License cover the whole combination when this license becomes
+applicable.
+
+In addition, as a special exception, the copyright holders of Argeo Commons give
+you permission to combine Argeo Commons with any program released under the
+terms and conditions of the Apache License v2.0 or any later version of this
+license. You may copy and distribute such a system following the terms of
+the GNU GPL for Argeo Commons and the licenses of the other code concerned,
+provided that you include the source code of that other code when and as
+the GNU GPL requires distribution of source code.
+
+Note that people who make modified versions of Argeo Commons are not obligated
+to grant this special exception for their modified versions; it is their choice
+whether to do so. The GNU General Public License gives permission to release a
+modified version without this exception; this exception also makes it possible
+to release a modified version which carries forward this exception.
+
# Java Content Repository API version 2.0 Permission
Linking Argeo Commons statically or dynamically with other modules is making a
/*
* CONTENT OPERATIONS
*/
- Content add(QName name, QName... classes);
+ /** Adds a new empty {@link Content} to this {@link Content}. */
+ Content add(QName name, QName... contentClass);
+
+ default Content add(QName name, QNamed... contentClass) {
+ return add(name, toQNames(contentClass));
+ }
+
+ /**
+ * Adds a new {@link Content} to this {@link Content}, setting the provided
+ * attributes. The provided attributes can be used as hints by the
+ * implementation. In particular, setting {@link DName#getcontenttype} will
+ * imply that this content has a file semantic.
+ */
+ default Content add(QName name, Map<QName, Object> attrs, QName... classes) {
+ Content child = add(name, classes);
+ putAll(attrs);
+ return child;
+ }
default Content add(String name, QName... classes) {
return add(unqualified(name), classes);
}
+ default Content add(String name, Map<QName, Object> attrs, QName... classes) {
+ return add(unqualified(name), attrs, classes);
+ }
+
void remove();
/*
/** AND */
default boolean isContentClass(QNamed... contentClass) {
- List<QName> lst = new ArrayList<>();
- for (QNamed qNamed : contentClass)
- lst.add(qNamed.qName());
- return isContentClass(lst.toArray(new QName[lst.size()]));
+ return isContentClass(toQNames(contentClass));
}
/** OR */
/** OR */
default boolean hasContentClass(QNamed... contentClass) {
- List<QName> lst = new ArrayList<>();
- for (QNamed qNamed : contentClass)
- lst.add(qNamed.qName());
- return hasContentClass(lst.toArray(new QName[lst.size()]));
+ return hasContentClass(toQNames(contentClass));
+ }
+
+ static QName[] toQNames(QNamed... names) {
+ QName[] res = new QName[names.length];
+ for (int i = 0; i < names.length; i++)
+ res[i] = names[i].qName();
+ return res;
}
/*
return Optional.of(res.get(0));
}
+ default Content soleOrAddChild(QName name, QName... classes) {
+ return soleChild(name).orElseGet(() -> this.add(name, classes));
+ }
+
default Content child(QName name) {
return soleChild(name).orElseThrow();
}
if (String.class.isAssignableFrom(clss)) {
return Optional.of((T) strValue);
}
+ if (java.util.UUID.class.isAssignableFrom(clss)) {
+ return Optional.of((T) java.util.UUID.fromString(strValue));
+ }
if (QName.class.isAssignableFrom(clss)) {
return Optional.of((T) NamespaceUtils.parsePrefixedName(namespaceContext, strValue));
}
* ATTRIBUTES
*/
uuid, // the UUID of a content
+ path, // the path to a content
mount, // a mount point
// cc, // content class
// RFC4918 (WebDav) value used as CR class
collection, //
- // RFC3744 (ACL) properties uase as CR attr
+ // RFC3744 (ACL) properties used as CR attr
owner, //
group, //
+
+ // RFC3253 (versioning) properties used as CR attr
+ checkedOut("checked-out"), //
+ checkedIn("checked-in"), //
//
;
public final static String WEBDAV_NAMESPACE_URI = "DAV:";
public final static String WEBDAV_DEFAULT_PREFIX = "D";
+ private final String localName;
+
+ private DName(String localName) {
+ assert localName != null;
+ this.localName = localName;
+ }
+
+ private DName() {
+ this.localName = null;
+ }
+
+ @Override
+ public String localName() {
+ if (localName != null)
+ return localName;
+ else
+ return name();
+ }
+
@Override
public String getNamespace() {
return WEBDAV_NAMESPACE_URI;
return name();
}
+ /**
+ * A {@link QName} corresponding to this definition. Calls
+ * {@link #createQName()} by default, but it could return a cached value.
+ */
default QName qName() {
- return new ContentName(getNamespace(), localName(), getDefaultPrefix());
+ return createQName();
}
+ /**
+ * A prefixed representation of this qualified name within the provided
+ * {@link NamespaceContext}.
+ */
default String get(NamespaceContext namespaceContext) {
return namespaceContext.getPrefix(getNamespace()) + ":" + localName();
}
- /** This qualified named with its default prefix. If it is unqualified this method should be overridden, or QNamed.Unqualified be used. */
+ /**
+ * Create a {@link QName} corresponding on this definition. Can typically be
+ * used to cache the {@link QName} in enums.
+ */
+ default QName createQName() {
+ return new ContentName(getNamespace(), localName(), getDefaultPrefix());
+ }
+
+ /**
+ * This qualified named with its default prefix. If it is unqualified this
+ * method should be overridden, or QNamed.Unqualified be used.
+ */
default String get() {
return getDefaultPrefix() + ":" + localName();
}
+ /** The namespace URI of this qualified name. */
String getNamespace();
+ /**
+ * The default prefix of this qualified name, as expected to be found in
+ * {@link RuntimeNamespaceContext}.
+ */
String getDefaultPrefix();
+ /** Compares to a plain {@link QName}. */
+ default boolean equals(QName qName) {
+ return qName().equals(qName);
+ }
+
/** To be used by enums without namespace (typically XML attributes). */
static interface Unqualified extends QNamed {
@Override
}
public BasicSearch where(Consumer<AndFilter> and) {
- if (where != null)
- throw new IllegalStateException("A where clause is already set");
- AndFilter subFilter = new AndFilter();
- and.accept(subFilter);
- where = subFilter;
+// if (where != null)
+// throw new IllegalStateException("A where clause is already set");
+// AndFilter subFilter = new AndFilter();
+ and.accept((AndFilter) getWhere());
+// where = subFilter;
return this;
}
}
public ContentFilter<? extends Composition> getWhere() {
+ if (where == null)
+ where = new AndFilter();
return where;
}
package org.argeo.api.acr.search;
-import java.util.HashSet;
-import java.util.Set;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.List;
import java.util.function.Consumer;
import javax.xml.namespace.QName;
/** A constraint filtering based ona given composition (and/or). */
public abstract class ContentFilter<COMPOSITION extends Composition> implements Constraint {
- private Set<Constraint> constraintss = new HashSet<>();
+ // even though not necessary, we use a list in order to have a predictable order
+ private List<Constraint> constraints = new ArrayList<>();
private COMPOSITION composition;
}
public COMPOSITION eq(QNamed attr, Object value) {
- addConstraint(new Eq(attr.qName(), value));
+ return eq(attr.qName(), value);
+ }
+
+ public COMPOSITION lt(QName attr, Object value) {
+ addConstraint(new Lt(attr, value));
+ return composition;
+ }
+
+ public COMPOSITION lt(QNamed attr, Object value) {
+ return lt(attr.qName(), value);
+ }
+
+ public COMPOSITION lte(QName attr, Object value) {
+ addConstraint(new Lte(attr, value));
return composition;
}
+ public COMPOSITION lte(QNamed attr, Object value) {
+ return lte(attr.qName(), value);
+ }
+
+ public COMPOSITION gt(QName attr, Object value) {
+ addConstraint(new Gt(attr, value));
+ return composition;
+ }
+
+ public COMPOSITION gt(QNamed attr, Object value) {
+ return gt(attr.qName(), value);
+ }
+
+ public COMPOSITION gte(QName attr, Object value) {
+ addConstraint(new Gte(attr, value));
+ return composition;
+ }
+
+ public COMPOSITION gte(QNamed attr, Object value) {
+ return gte(attr.qName(), value);
+ }
+
+ public COMPOSITION like(QName attr, String pattern) {
+ addConstraint(new Like(attr, pattern));
+ return composition;
+ }
+
+ public COMPOSITION like(QNamed attr, String pattern) {
+ return like(attr.qName(), pattern);
+ }
+
+ /*
+ * PROPERTIES CONSTRAINTS
+ */
+
+ public COMPOSITION isDefined(QName attr) {
+ addConstraint(new IsDefined(attr));
+ return composition;
+ }
+
+ public COMPOSITION isDefined(QNamed attr) {
+ return isDefined(attr.qName());
+ }
+
/*
* UTILITIES
*/
} else {
operatorToAdd = operator;
}
- constraintss.add(operatorToAdd);
+ constraints.add(operatorToAdd);
}
/** Checks that the root operator is not set. */
private void checkAddConstraint() {
- if (composition == null && !constraintss.isEmpty())
- throw new IllegalStateException("An operator is already registered (" + constraintss.iterator().next()
+ if (composition == null && !constraints.isEmpty())
+ throw new IllegalStateException("An operator is already registered (" + constraints.iterator().next()
+ ") and no composition is defined");
}
/*
* ACCESSORs
*/
- public Set<Constraint> getConstraints() {
- return constraintss;
+ public Collection<Constraint> getConstraints() {
+ return constraints;
}
public boolean isUnion() {
* CLASSES
*/
- public static class Not implements Constraint {
- final Constraint negated;
-
- public Not(Constraint negated) {
- this.negated = negated;
- }
-
- public Constraint getNegated() {
- return negated;
- }
-
- }
-
- public static class Eq implements Constraint {
- final QName prop;
- final Object value;
-
- public Eq(QName prop, Object value) {
- super();
- this.prop = prop;
- this.value = value;
- }
-
- public QName getProp() {
- return prop;
- }
-
- public Object getValue() {
- return value;
- }
-
- }
-
- public static class IsContentClass implements Constraint {
- final QName[] contentClasses;
-
- public IsContentClass(QName[] contentClasses) {
- this.contentClasses = contentClasses;
- }
-
- public IsContentClass(QNamed[] contentClasses) {
- this.contentClasses = new QName[contentClasses.length];
- for (int i = 0; i < contentClasses.length; i++)
- this.contentClasses[i] = contentClasses[i].qName();
- }
-
- public QName[] getContentClasses() {
- return contentClasses;
- }
-
- }
-
// public static void main(String[] args) {
// AndFilter filter = new AndFilter();
// filter.eq(new QName("test"), "test").and().not().eq(new QName("type"), "integer");
--- /dev/null
+package org.argeo.api.acr.search;
+
+import javax.xml.namespace.QName;
+
+/** Whether this property equals this value. */
+public class Eq extends PropertyValueContraint {
+ public Eq(QName prop, Object value) {
+ super(prop, value);
+ }
+}
--- /dev/null
+package org.argeo.api.acr.search;
+
+import javax.xml.namespace.QName;
+
+/** Whether this property is strictly greater than this value. */
+public class Gt extends PropertyValueContraint {
+ public Gt(QName prop, Object value) {
+ super(prop, value);
+ }
+}
--- /dev/null
+package org.argeo.api.acr.search;
+
+import javax.xml.namespace.QName;
+
+/** Whether this property is greater than this value or equal. */
+public class Gte extends PropertyValueContraint {
+ public Gte(QName prop, Object value) {
+ super(prop, value);
+ }
+}
--- /dev/null
+package org.argeo.api.acr.search;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.QNamed;
+
+/** Whether the content is all these content classes. */
+public class IsContentClass implements Constraint {
+ final QName[] contentClasses;
+
+ public IsContentClass(QName[] contentClasses) {
+ this.contentClasses = contentClasses;
+ }
+
+ public IsContentClass(QNamed[] contentClasses) {
+ this.contentClasses = new QName[contentClasses.length];
+ for (int i = 0; i < contentClasses.length; i++)
+ this.contentClasses[i] = contentClasses[i].qName();
+ }
+
+ public QName[] getContentClasses() {
+ return contentClasses;
+ }
+
+}
--- /dev/null
+package org.argeo.api.acr.search;
+
+import javax.xml.namespace.QName;
+
+/** Whether this property is defined. */
+public class IsDefined implements Constraint {
+ final QName prop;
+
+ public IsDefined(QName prop) {
+ this.prop = prop;
+ }
+
+ public QName getProp() {
+ return prop;
+ }
+}
--- /dev/null
+package org.argeo.api.acr.search;
+
+import javax.xml.namespace.QName;
+
+/** Whether this property equals this value. */
+public class Like extends PropertyValueContraint {
+ public Like(QName prop, String pattern) {
+ super(prop, pattern);
+ }
+}
--- /dev/null
+package org.argeo.api.acr.search;
+
+import javax.xml.namespace.QName;
+
+/** Whether this property is strictly less than this value. */
+public class Lt extends PropertyValueContraint {
+ public Lt(QName prop, Object value) {
+ super(prop, value);
+ }
+}
--- /dev/null
+package org.argeo.api.acr.search;
+
+import javax.xml.namespace.QName;
+
+/** Whether this property is less than this value or equal. */
+public class Lte extends PropertyValueContraint {
+ public Lte(QName prop, Object value) {
+ super(prop, value);
+ }
+}
--- /dev/null
+package org.argeo.api.acr.search;
+
+/** Negates the provided constraint. */
+public class Not implements Constraint {
+ final Constraint negated;
+
+ public Not(Constraint negated) {
+ this.negated = negated;
+ }
+
+ public Constraint getNegated() {
+ return negated;
+ }
+
+}
--- /dev/null
+package org.argeo.api.acr.search;
+
+import javax.xml.namespace.QName;
+
+/** Whether this property equals this value. */
+public abstract class PropertyValueContraint implements Constraint {
+ final QName prop;
+ final Object value;
+
+ public PropertyValueContraint(QName prop, Object value) {
+ this.prop = prop;
+ this.value = value;
+ }
+
+ public QName getProp() {
+ return prop;
+ }
+
+ public Object getValue() {
+ return value;
+ }
+
+}
package org.argeo.api.cms;
+import java.util.concurrent.CompletionStage;
+
+import com.sun.net.httpserver.HttpServer;
+
/** A configured node deployment. */
public interface CmsDeployment {
-
-// void addFactoryDeployConfig(String factoryPid, Dictionary<String, Object> props);
-//
-// Dictionary<String, Object> getProps(String factoryPid, String cn);
+ /** The local HTTP server, or null if none is expected. */
+ CompletionStage<HttpServer> getHttpServer();
+
+ /** The local SSH server, or null if none is expected. */
+ CompletionStage<CmsSshd> getCmsSshd();
}
--- /dev/null
+package org.argeo.api.cms;
+
+import java.net.InetSocketAddress;
+
+/** A local SSH server. */
+public interface CmsSshd {
+ final static String NODE_USERNAME_ALIAS = "user.name";
+ final static String DEFAULT_SSH_HOST_KEY_PATH = "private/" + CmsConstants.NODE + ".ser";
+
+ InetSocketAddress getAddress();
+}
package org.argeo.api.cms.ux;
+import java.net.URI;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import org.argeo.api.cms.CmsSession;
-/** Provides interaction with the CMS system. */
+/** Provides UX interactions with the CMS system. */
public interface CmsView {
final static String CMS_VIEW_UID_PROPERTY = "argeo.cms.view.uid";
// String KEY = "org.argeo.cms.ui.view";
// SERVICES
void exception(Throwable e);
- <V,M> CmsImageManager<V, M> getImageManager();
+ <V, M> CmsImageManager<V, M> getImageManager();
boolean isAnonymous();
+ /**
+ * Translates to an URL that can be reached by a client, depending on its type.
+ * Typically, if a web interface asks for /path/on/the/web/server it will be
+ * returned without modifications; but a thin client will probably need to add a
+ * server and a port.
+ */
+ URI toBackendUri(String url);
+
/**
* Send an event to this topic. Does nothing by default., but if implemented it
* MUST set the {@link #CMS_VIEW_UID_PROPERTY} in the properties.
org.argeo.cms.2.1.jar \
org.argeo.cms.cli.2.1.jar \
org.argeo.cms.ee.2.1.jar \
+org.argeo.cms.jshell.2.1.jar \
org.argeo.cms.lib.dbus.2.1.jar \
org.argeo.cms.lib.jetty.2.1.jar \
+org.argeo.cms.lib.json.2.1.jar \
org.argeo.cms.lib.sshd.2.1.jar \
org.argeo.cms.ux.2.1.jar \
org.argeo.init.2.1.jar \
../log/syslogger/org.argeo.tp/org.argeo.tp.syslogger.2.1.jar \
-../org.argeo.tp/com.fasterxml.jackson.core.jackson.annotations.2.14.jar \
-../org.argeo.tp/com.fasterxml.jackson.core.jackson.core.2.14.jar \
-../org.argeo.tp/com.fasterxml.jackson.core.jackson.databind.2.14.jar \
+../org.argeo.tp/com.apicatalog.jsonld.1.3.jar \
+../org.argeo.tp/com.fasterxml.jackson.core.jackson.annotations.2.15.jar \
+../org.argeo.tp/com.fasterxml.jackson.core.jackson.core.2.15.jar \
+../org.argeo.tp/com.fasterxml.jackson.core.jackson.databind.2.15.jar \
../org.argeo.tp/javax.servlet.4.0.jar \
../org.argeo.tp/javax.websocket.1.1.jar \
-../org.argeo.tp/org.apache.batik.1.16.jar \
-../org.argeo.tp/org.apache.batik.constants.1.16.jar \
-../org.argeo.tp/org.apache.batik.css.1.16.jar \
-../org.argeo.tp/org.apache.batik.i18n.1.16.jar \
-../org.argeo.tp/org.apache.batik.util.1.16.jar \
+../org.argeo.tp/org.apache.batik.1.17.jar \
+../org.argeo.tp/org.apache.batik.constants.1.17.jar \
+../org.argeo.tp/org.apache.batik.css.1.17.jar \
+../org.argeo.tp/org.apache.batik.i18n.1.17.jar \
+../org.argeo.tp/org.apache.batik.util.1.17.jar \
../org.argeo.tp/org.apache.commons.cli.1.5.jar \
-../org.argeo.tp/org.apache.commons.codec.1.15.jar \
-../org.argeo.tp/org.apache.commons.compress.1.22.jar \
+../org.argeo.tp/org.apache.commons.codec.1.16.jar \
+../org.argeo.tp/org.apache.commons.compress.1.24.jar \
../org.argeo.tp/org.apache.commons.fileupload.1.5.jar \
-../org.argeo.tp/org.apache.commons.io.2.11.jar \
+../org.argeo.tp/org.apache.commons.io.2.14.jar \
../org.argeo.tp/org.apache.httpcomponents.httpclient.4.5.jar \
../org.argeo.tp/org.apache.httpcomponents.httpcore.4.4.jar \
../org.argeo.tp/org.apache.httpcomponents.httpmime.4.5.jar \
-../org.argeo.tp/org.apache.sshd.2.9.jar \
-../org.argeo.tp/org.apache.sshd.cli.2.9.jar \
-../org.argeo.tp/org.apache.sshd.git.2.9.jar \
-../org.argeo.tp/org.apache.sshd.putty.2.9.jar \
-../org.argeo.tp/org.apache.sshd.scp.2.9.jar \
-../org.argeo.tp/org.apache.sshd.sftp.2.9.jar \
+../org.argeo.tp/org.apache.sshd.2.10.jar \
+../org.argeo.tp/org.apache.sshd.cli.2.10.jar \
+../org.argeo.tp/org.apache.sshd.git.2.10.jar \
+../org.argeo.tp/org.apache.sshd.putty.2.10.jar \
+../org.argeo.tp/org.apache.sshd.scp.2.10.jar \
+../org.argeo.tp/org.apache.sshd.sftp.2.10.jar \
../org.argeo.tp/org.apache.tomcat.jni.9.0.jar \
-../org.argeo.tp/org.apache.xalan.2.7.jar \
../org.argeo.tp/org.apache.xerces.2.12.jar \
-../org.argeo.tp/org.apache.xmlgraphics.2.8.jar \
+../org.argeo.tp/org.apache.xmlgraphics.2.9.jar \
../org.argeo.tp/org.apache.xml.resolver.1.2.jar \
-../org.argeo.tp/org.freeedesktop.dbus.4.2.jar \
+../org.argeo.tp/org.eclipse.parsson.1.1.jar \
+../org.argeo.tp/org.freeedesktop.dbus.4.3.jar \
../org.argeo.tp/org.h2.2.1.jar \
-../org.argeo.tp/org.postgresql.jdbc.42.5.jar \
+../org.argeo.tp/org.jline.3.23.jar \
+../org.argeo.tp/org.mozilla.universalchardet.2.4.jar \
+../org.argeo.tp/org.postgresql.jdbc.42.6.jar \
../org.argeo.tp/org.tukaani.xz.1.9.jar \
../org.argeo.tp/org.w3c.css.sac.1.3.jar \
../org.argeo.tp/org.w3c.dom.smil.1.0.jar \
../org.argeo.tp/org.w3c.dom.svg.1.1.jar \
+../crypto/fips/org.argeo.tp.crypto/bc-fips.1.0.jar \
+../crypto/fips/org.argeo.tp.crypto/bc-fips.1.0.src.jar \
../crypto/fips/org.argeo.tp.crypto/bcmail-fips.1.0.jar \
../crypto/fips/org.argeo.tp.crypto/bcmail-fips.1.0.src.jar \
-../crypto/fips/org.argeo.tp.crypto/bc-noncert.1.0.jar \
-../crypto/fips/org.argeo.tp.crypto/bc-noncert.1.0.src.jar \
../crypto/fips/org.argeo.tp.crypto/bcpg-fips.1.0.jar \
../crypto/fips/org.argeo.tp.crypto/bcpg-fips.1.0.src.jar \
../crypto/fips/org.argeo.tp.crypto/bcpkix-fips.1.0.jar \
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.URI;
+import java.net.URLEncoder;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;
+import java.util.Map;
+import java.util.StringJoiner;
import javax.net.ssl.SSLSession;
import javax.servlet.http.HttpServletRequest;
@Override
public URI getRequestURI() {
- return URI.create(httpServletRequest.getRequestURI());
+ // TODO properly deal with charset?
+ Charset encoding = StandardCharsets.UTF_8;
+ Map<String, String[]> parameters = httpServletRequest.getParameterMap();
+ StringJoiner sb = new StringJoiner("&");
+ for (String key : parameters.keySet()) {
+ for (String value : parameters.get(key)) {
+ String pair = URLEncoder.encode(key, encoding) + '=' + URLEncoder.encode(value, encoding);
+ sb.add(pair);
+ }
+ }
+ return URI.create(httpServletRequest.getRequestURI() + (sb.length() != 0 ? '?' + sb.toString() : ""));
}
@Override
import java.io.IOException;
import java.io.InputStream;
+import java.net.FileNameMap;
import java.net.URL;
+import java.net.URLConnection;
import java.util.Collection;
import java.util.SortedMap;
import java.util.TreeMap;
import org.osgi.framework.wiring.FrameworkWiring;
import org.osgi.resource.Requirement;
+/**
+ * Publishes client-side web resources (JavaScript, HTML, CSS, images, etc.)
+ * from the OSGi runtime.
+ */
public class PkgServlet extends HttpServlet {
private static final long serialVersionUID = 7660824185145214324L;
+ private static FileNameMap fileNameMap = URLConnection.getFileNameMap();
+
private BundleContext bundleContext = FrameworkUtil.getBundle(PkgServlet.class).getBundleContext();
@Override
throw new IllegalArgumentException("Unsupported path length " + pathInfo);
}
+ // content type
+ String contentType = fileNameMap.getContentTypeFor(file);
+ resp.setContentType(contentType);
+
FrameworkWiring frameworkWiring = bundleContext.getBundle(0).adapt(FrameworkWiring.class);
String filter;
if (versionStr == null) {
import org.argeo.api.cms.CmsLog;
import org.argeo.api.cms.CmsState;
import org.argeo.api.uuid.UuidFactory;
+import org.argeo.cms.util.FsUtils;
import org.argeo.cms.util.OS;
import org.argeo.internal.cms.jshell.osgi.OsgiExecutionControlProvider;
import org.osgi.framework.Bundle;
String symbolicName = bundleSnDir.getFileName().toString();
Bundle fromBundle = OsgiExecutionControlProvider.getBundleFromSn(symbolicName);
if (fromBundle == null) {
- log.error("Ignoring bundle " + symbolicName + " because it was not found");
+ log.error("Removing directory for bundle " + symbolicName + " because it was not found in runtime...");
+ FsUtils.delete(bundleSnDir);
return;
}
Long bundleId = fromBundle.getBundleId();
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
+import java.io.PrintStream;
import java.lang.System.Logger;
import java.lang.management.ManagementFactory;
import java.net.StandardSocketOptions;
}
- public static void main(String[] args) throws IOException, InterruptedException {
- if (benchmark)
- System.err.println(ManagementFactory.getRuntimeMXBean().getUptime());
- List<String> plainArgs = new ArrayList<>();
- Map<String, List<String>> options = new HashMap<>();
- String currentOption = null;
- for (int i = 0; i < args.length; i++) {
- if (args[i].startsWith("-")) {
- currentOption = args[i];
- if (!options.containsKey(currentOption))
- options.put(currentOption, new ArrayList<>());
- i++;
- options.get(currentOption).add(args[i]);
- } else {
- plainArgs.add(args[i]);
+ public static void main(String[] args) {
+ try {
+ if (benchmark)
+ System.err.println(ManagementFactory.getRuntimeMXBean().getUptime());
+ List<String> plainArgs = new ArrayList<>();
+ Map<String, List<String>> options = new HashMap<>();
+ String currentOption = null;
+ for (int i = 0; i < args.length; i++) {
+ if (args[i].startsWith("-")) {
+ currentOption = args[i];
+ if ("-h".equals(currentOption) || "--help".equals(currentOption)) {
+ printHelp(System.out);
+ return;
+ }
+ if (!options.containsKey(currentOption))
+ options.put(currentOption, new ArrayList<>());
+ i++;
+ options.get(currentOption).add(args[i]);
+ } else {
+ plainArgs.add(args[i]);
+ }
}
+
+ List<String> dir = opt(options, "-d", "--sockets-dir");
+ if (dir.size() > 1)
+ throw new IllegalArgumentException("Only one run directory can be specified");
+ Path targetStateDirectory;
+ if (dir.isEmpty())
+ targetStateDirectory = Paths.get(System.getProperty("user.dir"));
+ else {
+ targetStateDirectory = Paths.get(dir.get(0));
+ if (!Files.exists(targetStateDirectory)) {
+ // we assume argument is the application id
+ targetStateDirectory = getRunDir().resolve(dir.get(0));
+ }
+ }
+
+ List<String> bundle = opt(options, "-b", "--bundle");
+ if (bundle.size() > 1)
+ throw new IllegalArgumentException("Only one bundle can be specified");
+ String symbolicName = bundle.isEmpty() ? "org.argeo.cms.cli" : bundle.get(0);
+
+ Path script = plainArgs.isEmpty() ? null : Paths.get(plainArgs.get(0));
+ List<String> scriptArgs = new ArrayList<>();
+ for (int i = 1; i < plainArgs.size(); i++)
+ scriptArgs.add(plainArgs.get(i));
+
+ JShellClient client = new JShellClient(targetStateDirectory, symbolicName, script, scriptArgs);
+ client.run();
+ } catch (Exception e) {
+ e.printStackTrace();
+ printHelp(System.err);
}
+ }
- Path targetStateDirectory = Paths.get(options.get("-d").get(0));
- String symbolicName = options.get("-b").get(0);
+ /** Guaranteed to return a non-null list (which may be empty). */
+ private static List<String> opt(Map<String, List<String>> options, String shortOpt, String longOpt) {
+ List<String> res = new ArrayList<>();
+ if (options.get(shortOpt) != null)
+ res.addAll(options.get(shortOpt));
+ if (options.get(longOpt) != null)
+ res.addAll(options.get(longOpt));
+ return res;
+ }
- Path script = plainArgs.isEmpty() ? null : Paths.get(plainArgs.get(0));
- List<String> scriptArgs = new ArrayList<>();
- for (int i = 1; i < plainArgs.size(); i++)
- scriptArgs.add(plainArgs.get(i));
+ public static void printHelp(PrintStream out) {
+ out.println("Start a JShell terminal or execute a JShell script in a local Argeo CMS instance");
+ out.println("Usage: jshc -d <sockets directory> -b <bundle> [JShell script] [script arguments...]");
+ out.println(" -d, --sockets-dir app directory with UNIX sockets (default to current dir)");
+ out.println(" -b, --bundle bundle to activate and use as context (default to org.argeo.cms.cli)");
+ out.println(" -h, --help this help message");
+ }
- JShellClient client = new JShellClient(targetStateDirectory, symbolicName, script, scriptArgs);
- client.run();
+ // Copied from org.argeo.cms.util.OS
+ private static Path getRunDir() {
+ Path runDir;
+ String xdgRunDir = System.getenv("XDG_RUNTIME_DIR");
+ if (xdgRunDir != null) {
+ // TODO support multiple names
+ runDir = Paths.get(xdgRunDir);
+ } else {
+ String username = System.getProperty("user.name");
+ if (username.equals("root")) {
+ runDir = Paths.get("/run");
+ } else {
+ Path homeDir = Paths.get(System.getProperty("user.home"));
+ if (!Files.isWritable(homeDir)) {
+ // typically, dameon's home (/usr/sbin) is not writable
+ runDir = Paths.get("/tmp/" + username + "/run");
+ } else {
+ runDir = homeDir.resolve(".cache/argeo");
+ }
+ }
+ }
+ return runDir;
}
/*
import org.argeo.cms.jshell.CmsExecutionControl;
import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.Version;
import org.osgi.framework.namespace.PackageNamespace;
public static Path getBundleStartupScript(Long bundleId) {
BundleContext bc = FrameworkUtil.getBundle(OsgiExecutionControlProvider.class).getBundleContext();
Bundle fromBundle = bc.getBundle(bundleId);
+
+ int bundleState = fromBundle.getState();
+ if (Bundle.INSTALLED == bundleState)
+ throw new IllegalStateException("Bundle " + fromBundle.getSymbolicName() + " is not resolved");
+ if (Bundle.RESOLVED == bundleState) {
+ try {
+ fromBundle.start();
+ } catch (BundleException e) {
+ throw new IllegalStateException("Cannot start bundle " + fromBundle.getSymbolicName(), e);
+ }
+ while (Bundle.ACTIVE != fromBundle.getState())
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ // we assume the session has been closed
+ throw new RuntimeException("Bundle " + fromBundle.getSymbolicName() + " is not active", e);
+ }
+ }
+
Path bundleStartupScript = fromBundle.getDataFile("BUNDLE.jsh").toPath();
BundleWiring fromBundleWiring = fromBundle.adapt(BundleWiring.class);
packagesToImport.add(pkg.getName());
}
- List<BundleWire> bundleWires = fromBundleWiring.getRequiredWires(BundleRevision.PACKAGE_NAMESPACE);
- for (BundleWire bw : bundleWires) {
+// List<BundleWire> exportedWires = fromBundleWiring.getProvidedWires(BundleRevision.PACKAGE_NAMESPACE);
+// for (BundleWire bw : exportedWires) {
+// packagesToImport.add(bw.getCapability().getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE).toString());
+// }
+
+ List<BundleWire> importedWires = fromBundleWiring.getRequiredWires(BundleRevision.PACKAGE_NAMESPACE);
+ for (BundleWire bw : importedWires) {
packagesToImport.add(bw.getCapability().getAttributes().get(PackageNamespace.PACKAGE_NAMESPACE).toString());
}
}
public static String getBundleClasspath(Long bundleId) throws IOException {
- String framework = System.getProperty("osgi.framework");
- Path frameworkLocation = Paths.get(URI.create(framework)).toAbsolutePath();
BundleContext bc = FrameworkUtil.getBundle(OsgiExecutionControlProvider.class).getBundleContext();
+ String framework = bc.getProperty("osgi.framework");
+ Path frameworkLocation = Paths.get(URI.create(framework)).toAbsolutePath();
Bundle fromBundle = bc.getBundle(bundleId);
BundleWiring fromBundleWiring = fromBundle.adapt(BundleWiring.class);
continue bundles;
}
Path p = bundleToPath(frameworkLocation, b);
- classpath.add(p.toString());
+ if (p != null)
+ classpath.add(p.toString());
}
return classpath.toString();
String location = bundle.getLocation();
if (location.startsWith("initial@reference:file:")) {
location = location.substring("initial@reference:file:".length());
- Path p = frameworkLocation.getParent().resolve(location).toRealPath();
- // TODO load dev.properties from OSGi configuration directory
- if (Files.isDirectory(p))
- p = p.resolve("bin");
- return p;
+ Path p = frameworkLocation.getParent().resolve(location).toAbsolutePath();
+ if (Files.exists(p)) {
+ p = p.toRealPath();
+ // TODO load dev.properties from OSGi configuration directory
+ if (Files.isDirectory(p))
+ p = p.resolve("bin");
+ return p;
+ } else {
+ log.warn("Ignore bundle " + p + " as it does not exist");
+ return null;
+ }
}
Path p = Paths.get(location);
return p;
import java.io.IOException;
import java.net.InetSocketAddress;
+import java.security.NoSuchAlgorithmException;
import java.util.Map;
import java.util.TreeMap;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadPoolExecutor;
+import javax.net.ssl.SSLContext;
import javax.servlet.ServletException;
import javax.websocket.server.ServerContainer;
}
if (httpsEnabled) {
+ if (httpsConfigurator == null) {
+ // we make sure that an HttpSConfigurator is set, so that clients can detect
+ // whether this server is HTTP or HTTPS
+ try {
+ httpsConfigurator = new HttpsConfigurator(SSLContext.getDefault());
+ } catch (NoSuchAlgorithmException e) {
+ throw new IllegalStateException("Cannot initalise SSL Context", e);
+ }
+ }
+
SslContextFactory.Server sslContextFactory = new SslContextFactory.Server();
// sslContextFactory.setKeyStore(KeyS)
@Override
public synchronized void removeContext(String path) throws IllegalArgumentException {
+ if (!path.endsWith("/"))
+ path = path + "/";
if (!contexts.containsKey(path))
throw new IllegalArgumentException("Context " + path + " does not exist");
JettyHttpContext httpContext = contexts.remove(path);
if (httpContext instanceof ContextHandlerHttpContext contextHandlerHttpContext) {
// TODO stop handler first?
contextHandlerCollection.removeHandler(contextHandlerHttpContext.getServletContextHandler());
+ } else {
+ // FIXME apparently servlets cannot be removed in Jetty, we should replace the
+ // handler
}
}
import javax.websocket.DeploymentException;
import javax.websocket.server.ServerContainer;
-import javax.websocket.server.ServerEndpointConfig;
import org.argeo.api.cms.CmsLog;
import org.argeo.cms.servlet.httpserver.HttpContextServlet;
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-17"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.argeo.cms.lib.json</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.jdt.core.javabuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ManifestBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.SchemaBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+eclipse.preferences.version=1
+encoding/<project>=UTF-8
--- /dev/null
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=17
+org.eclipse.jdt.core.compiler.compliance=17
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
+org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning
+org.eclipse.jdt.core.compiler.release=enabled
+org.eclipse.jdt.core.compiler.source=17
--- /dev/null
+eclipse.preferences.version=1
+pluginProject.extensions=false
+resolve.requirebundle=false
--- /dev/null
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .
--- /dev/null
+package org.argeo.cms.acr.json;
+
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.DName;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.QNamed;
+
+import jakarta.json.stream.JsonGenerator;
+
+/** Utilities around ACR and the JSON format. */
+public class AcrJsonUtils {
+ public static void writeAttr(JsonGenerator g, Content content, String attr) {
+ writeAttr(g, content, NamespaceUtils.parsePrefixedName(attr));
+ }
+
+ public static void writeAttr(JsonGenerator g, Content content, QNamed attr) {
+ writeAttr(g, content, attr.qName());
+ }
+
+ public static void writeAttr(JsonGenerator g, Content content, QName attr) {
+ // String value = content.attr(attr);
+ Object value = content.get(attr);
+ if (value != null) {
+ // TODO specify NamespaceContext
+ String key = NamespaceUtils.toPrefixedName(attr);
+ if (value instanceof Double v)
+ g.write(key, v);
+ else if (value instanceof Long v)
+ g.write(key, v);
+ else if (value instanceof Integer v)
+ g.write(key, v);
+ else if (value instanceof Boolean v)
+ g.write(key, v);
+ else
+ g.write(key, value.toString());
+ }
+ }
+
+ /** singleton */
+ private AcrJsonUtils() {
+ }
+
+// private final QName JCR_CREATED = NamespaceUtils.parsePrefixedName("jcr:created");
+//
+// private final QName JCR_LAST_MODIFIED = NamespaceUtils.parsePrefixedName("jcr:lastModified");
+
+ public static void writeTimeProperties(JsonGenerator g, Content content) {
+ String creationDate = content.attr(DName.creationdate);
+ // if (creationDate == null)
+ // creationDate = content.attr(JCR_CREATED);
+ if (creationDate != null)
+ g.write(DName.creationdate.get(), creationDate);
+ String lastModified = content.attr(DName.getlastmodified);
+ // if (lastModified == null)
+ // lastModified = content.attr(JCR_LAST_MODIFIED);
+ if (lastModified != null)
+ g.write(DName.getlastmodified.get(), lastModified);
+ }
+}
<implementation class="org.argeo.cms.ssh.CmsSshServer"/>
<reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
<service>
- <provide interface="org.argeo.cms.CmsSshd"/>
+ <provide interface="org.argeo.api.cms.CmsSshd"/>
</service>
</scr:component>
import java.io.IOException;
import java.io.InputStream;
import java.io.Writer;
+import java.net.InetSocketAddress;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import org.argeo.api.cms.CmsAuth;
import org.argeo.api.cms.CmsConstants;
import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsSshd;
import org.argeo.api.cms.CmsState;
import org.argeo.cms.CmsDeployProperty;
-import org.argeo.cms.CmsSshd;
public class CmsSshServer implements CmsSshd {
private final static CmsLog log = CmsLog.getLog(CmsSshServer.class);
this.cmsState = cmsState;
}
+ @Override
+ public InetSocketAddress getAddress() {
+ return new InetSocketAddress(host, port);
+ }
+
}
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" immediate="true" name="CMS Deployment">
<implementation class="org.argeo.cms.internal.runtime.CmsDeploymentImpl"/>
<reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
- <reference bind="setCmsSshd" cardinality="0..1" interface="org.argeo.cms.CmsSshd" policy="dynamic"/>
+ <reference bind="setCmsSshd" cardinality="0..1" interface="org.argeo.api.cms.CmsSshd" policy="dynamic"/>
<reference bind="setHttpServer" cardinality="0..1" interface="com.sun.net.httpserver.HttpServer" policy="dynamic"/>
<reference bind="addHttpHandler" unbind="removeHttpHandler" cardinality="0..n" interface="com.sun.net.httpserver.HttpHandler" policy="dynamic"/>
<service>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="start" deactivate="stop" name="org.argeo.cms.cmsFileSystemProvider">
+ <implementation class="org.argeo.cms.file.provider.CmsFileSystemProvider"/>
+ <reference bind="setContentRepository" cardinality="1..1" interface="org.argeo.api.acr.spi.ProvidedRepository" name="ProvidedRepository" policy="static"/>
+ <service>
+ <provide interface="java.nio.file.spi.FileSystemProvider"/>
+ </service>
+</scr:component>
OSGI-INF/cmsAcrHttpHandler.xml,\
OSGI-INF/cmsDeployment.xml,\
OSGI-INF/cmsContext.xml,\
+OSGI-INF/cmsFileSystemProvider.xml,\
.,\
bin/,\
OSGI-INF/,\
- OSGI-INF/cmsEventBus.xml
+ OSGI-INF/cmsEventBus.xml,\
+ OSGI-INF/cmsFileSystemProvider.xml
source.. = src/
output.. = bin/
+++ /dev/null
-package org.argeo.cms;
-
-import org.argeo.api.cms.CmsConstants;
-
-/** Just a marker interface for the time being. */
-public interface CmsSshd {
- final static String NODE_USERNAME_ALIAS = "user.name";
- final static String DEFAULT_SSH_HOST_KEY_PATH = "private/" + CmsConstants.NODE + ".ser";
-}
/** Lead transformation on the translated string. */
public static String toLead(String raw, Locale locale) {
+ if ("".equals(raw))
+ return "";
return raw.substring(0, 1).toUpperCase(locale) + raw.substring(1);
}
NavigableMap<String, ContentProvider> contentProviders = contentRepository.getMountManager()
.findContentProviders(scopePath);
for (Map.Entry<String, ContentProvider> contentProvider : contentProviders.entrySet()) {
+ assert scopePath.startsWith(contentProvider.getKey())
+ : "scopePath=" + scopePath + ", contentProvider path=" + contentProvider.getKey();
// TODO deal with depth
String relPath;
- if (scopePath.startsWith(contentProvider.getKey())) {
- relPath = scopePath.substring(contentProvider.getKey().length());
+ if (!scopePath.equals(contentProvider.getKey())) {
+ relPath = scopePath.substring(contentProvider.getKey().length() + 1, scopePath.length());
} else {
relPath = null;
}
searchPartitions.put(contentProvider.getKey(), searchPartition);
}
}
+ if (searchPartitions.isEmpty())
+ return Stream.empty();
return StreamSupport.stream(new SearchPartitionsSpliterator(searchPartitions), true);
}
package org.argeo.cms.acr;
import java.io.PrintStream;
+import java.net.URLEncoder;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.StringJoiner;
+import java.util.StringTokenizer;
import java.util.function.BiConsumer;
import javax.security.auth.login.LoginContext;
import org.argeo.api.acr.ContentSession;
import org.argeo.api.acr.DName;
import org.argeo.api.cms.CmsAuth;
+import org.argeo.api.cms.CmsConstants;
import org.argeo.api.cms.CmsSession;
import org.argeo.api.cms.directory.CmsDirectory;
import org.argeo.api.cms.directory.CmsUserManager;
throw new IllegalArgumentException("Path " + path + " contains //");
}
+ /** The last element of a path. */
+ public static String lastPathElement(String path) {
+ if (path.charAt(path.length() - 1) == '/')
+ throw new IllegalArgumentException("Path " + path + " cannot end with '/'");
+ int index = path.lastIndexOf('/');
+ if (index < 0)
+ return path;
+ return path.substring(index + 1);
+ }
+
/*
* DIRECTORY
*/
return relativePath;
}
+ /** A path in the node repository */
+ public static String getDataPath(Content node) {
+ // TODO make it more configurable?
+ StringBuilder buf = new StringBuilder(CmsConstants.PATH_API_ACR);
+ buf.append(node.getPath());
+ return buf.toString();
+ }
+
+ /** A path in the node repository */
+ public static String getDataPathForUrl(Content node) {
+ return cleanPathForUrl(getDataPath(node));
+ }
+
+ /** Clean reserved URL characters for use in HTTP links. */
+ public static String cleanPathForUrl(String path) {
+ StringTokenizer st = new StringTokenizer(path, "/");
+ StringBuilder sb = new StringBuilder();
+ while (st.hasMoreElements()) {
+ sb.append('/');
+ String encoded = URLEncoder.encode(st.nextToken(), StandardCharsets.UTF_8);
+ encoded = encoded.replace("+", "%20");
+ sb.append(encoded);
+
+ }
+ return sb.toString();
+ }
+
/** Singleton. */
private ContentUtils() {
synchronized ContentProvider findContentProvider(String path) {
// if (ContentUtils.EMPTY.equals(path))
// return partitions.firstEntry().getValue();
- Map.Entry<String, ContentProvider> entry = partitions.floorEntry(path);
- if (entry == null)
- throw new IllegalArgumentException("No entry provider found for path '" + path + "'");
- String mountPath = entry.getKey();
+ Map.Entry<String, ContentProvider> floorEntry = partitions.floorEntry(path);
+ if (floorEntry == null)
+ throw new IllegalArgumentException("No floor entry provider found for path '" + path + "'");
+ String mountPath = floorEntry.getKey();
if (!path.startsWith(mountPath)) {
// FIXME make it more robust and find when there is no content provider
String[] parent = ContentUtils.getParentPath(path);
// throw new IllegalArgumentException("Path " + path + " doesn't have a content
// provider");
}
- ContentProvider contentProvider = entry.getValue();
+ ContentProvider contentProvider = floorEntry.getValue();
assert mountPath.equals(contentProvider.getMountPath());
return contentProvider;
}
- /** All content provider under this path. */
+ /** All content providers under this path. */
synchronized NavigableMap<String, ContentProvider> findContentProviders(String path) {
+ Map.Entry<String, ContentProvider> floorEntry = partitions.floorEntry(path);
+ if (floorEntry == null)
+ throw new IllegalArgumentException("No floor entry provider found for path '" + path + "'");
+ // we first find the parent provider
+ String parentProviderPath = floorEntry.getKey();
+ // then gather all sub-providers
NavigableMap<String, ContentProvider> res = new TreeMap<>();
- tail: for (Map.Entry<String, ContentProvider> provider : partitions.tailMap(path).entrySet()) {
+ res.put(floorEntry.getKey(), floorEntry.getValue());
+ tail: for (Map.Entry<String, ContentProvider> provider : partitions.tailMap(parentProviderPath).entrySet()) {
if (!provider.getKey().startsWith(path))
break tail;
res.put(provider.getKey(), provider.getValue());
throw new IllegalArgumentException("Relative path cannot start with /");
String xPathExpression = '/' + relativePath;
if (Content.ROOT_PATH.equals(mountPath)) // repository root
- xPathExpression = "/" + CrName.root.qName() + xPathExpression;
+ xPathExpression = "/" + CrName.root.get() + xPathExpression;
try {
NodeList nodes = (NodeList) xPath.get().evaluate(xPathExpression, document, XPathConstants.NODESET);
return nodes;
locale = request.getLocale();
if (locale == null)
locale = Locale.getDefault();
- Authorization authorization = new SingleUserAuthorization(authorizationName);
- CmsAuthUtils.addAuthorization(subject, authorization);
+
+ Authorization authorization = null;
+ if (kerberosPrincipal != null) {
+ authorization = new SingleUserAuthorization(authorizationName);
+ CmsAuthUtils.addAuthorization(subject, authorization);
+ } else {
+ // next step with user admin will properly populate
+ }
// Add standard Java OS login
OsUserUtils.loginAsSystemUser(subject);
// principals.add(new ImpliedByPrincipal(NodeConstants.ROLE_ADMIN, principal));
// principals.add(new DataAdminPrincipal());
- CmsAuthUtils.registerSessionAuthorization(request, subject, authorization, locale);
+ if (authorization != null)
+ CmsAuthUtils.registerSessionAuthorization(request, subject, authorization, locale);
return true;
}
import java.nio.file.attribute.FileStoreAttributeView;
import org.argeo.api.acr.fs.AbstractFsStore;
+import org.argeo.api.acr.spi.ContentProvider;
public class CmsFileStore extends AbstractFsStore {
+ private final ContentProvider contentProvider;
+
+ public CmsFileStore(ContentProvider contentProvider) {
+ this.contentProvider = contentProvider;
+ }
@Override
public String name() {
- // TODO Auto-generated method stub
- return null;
+ // TODO return an URI
+ String name = contentProvider.getMountPath();
+ return name;
}
@Override
public String type() {
- // TODO Auto-generated method stub
- return null;
+ String type = contentProvider.getClass().getName();
+ return type;
}
@Override
public boolean isReadOnly() {
- // TODO Auto-generated method stub
return false;
}
@Override
public boolean supportsFileAttributeView(Class<? extends FileAttributeView> type) {
- // TODO Auto-generated method stub
+ if (ContentFileAttributeView.class.isAssignableFrom(type))
+ return true;
return false;
}
@Override
public boolean supportsFileAttributeView(String name) {
- // TODO Auto-generated method stub
+ if (ContentFileAttributeView.NAME.equals(name))
+ return true;
return false;
}
import java.nio.file.WatchService;
import java.nio.file.attribute.UserPrincipalLookupService;
import java.nio.file.spi.FileSystemProvider;
+import java.util.Collections;
import java.util.Set;
import org.argeo.api.acr.fs.AbstractFsSystem;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.api.acr.spi.ProvidedRepository;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.api.cms.CmsSession;
+import org.argeo.cms.acr.ContentUtils;
public class CmsFileSystem extends AbstractFsSystem<CmsFileStore> {
+ private final CmsFileSystemProvider provider;
+// private final ProvidedRepository contentRepository;
+ private final CmsSession cmsSession;
+ private final ProvidedSession contentSession;
+
+ private final CmsPath rootPath;
+ private final CmsFileStore baseFileStore;
+
+ public CmsFileSystem(CmsFileSystemProvider provider, ProvidedRepository contentRepository, CmsSession cmsSession) {
+ this.provider = provider;
+// this.contentRepository = contentRepository;
+ this.cmsSession = cmsSession;
+ this.contentSession = (ProvidedSession) ContentUtils.openSession(contentRepository, cmsSession);
+
+ rootPath = new CmsPath(this, ProvidedContent.ROOT_PATH);
+ baseFileStore = new CmsFileStore(rootPath.getContent().getProvider());
+ }
@Override
public CmsFileStore getBaseFileStore() {
- // TODO Auto-generated method stub
- return null;
+ return baseFileStore;
}
@Override
public CmsFileStore getFileStore(String path) {
- // TODO Auto-generated method stub
- return null;
+ ProvidedContent c = (ProvidedContent) contentSession.get(path);
+ return new CmsFileStore(c.getProvider());
}
@Override
public FileSystemProvider provider() {
- // TODO Auto-generated method stub
- return null;
+ return provider;
}
@Override
public void close() throws IOException {
- // TODO Auto-generated method stub
-
+ // TODO close content session?
+ provider.close(this);
}
@Override
public boolean isOpen() {
- // TODO Auto-generated method stub
- return false;
+ // TODO check provider
+ return true;
}
@Override
public boolean isReadOnly() {
- // TODO Auto-generated method stub
return false;
}
@Override
public String getSeparator() {
- // TODO Auto-generated method stub
- return null;
+ return CmsPath.SEPARATOR;
}
@Override
public Iterable<Path> getRootDirectories() {
- // TODO Auto-generated method stub
- return null;
+ return Collections.singleton(rootPath);
}
@Override
public Iterable<FileStore> getFileStores() {
- // TODO Auto-generated method stub
- return null;
+ // TODO return all mount points
+ return Collections.singleton(baseFileStore);
}
@Override
public Set<String> supportedFileAttributeViews() {
- // TODO Auto-generated method stub
- return null;
+ return Collections.singleton(ContentFileAttributeView.NAME);
}
@Override
public Path getPath(String first, String... more) {
- // TODO Auto-generated method stub
- return null;
+ StringBuilder sb = new StringBuilder(first);
+ // TODO Make it more robust
+ for (String part : more)
+ sb.append('/').append(part);
+ return new CmsPath(this, sb.toString());
}
@Override
public PathMatcher getPathMatcher(String syntaxAndPattern) {
- // TODO Auto-generated method stub
return null;
}
@Override
public UserPrincipalLookupService getUserPrincipalLookupService() {
- // TODO Auto-generated method stub
return null;
}
@Override
public WatchService newWatchService() throws IOException {
- // TODO Auto-generated method stub
return null;
}
+ /*
+ * ACR
+ */
+
+ ProvidedContent getContent(String acrPath) {
+ return (ProvidedContent) contentSession.get(acrPath);
+ }
+
+ ProvidedSession getContentSession() {
+ return contentSession;
+ }
+
+ /*
+ * CMS
+ */
+
+ CmsSession getCmsSession() {
+ return cmsSession;
+ }
+
}
package org.argeo.cms.file.provider;
import java.io.IOException;
+import java.io.UncheckedIOException;
import java.net.URI;
import java.nio.channels.SeekableByteChannel;
import java.nio.file.AccessMode;
import java.nio.file.CopyOption;
import java.nio.file.DirectoryStream;
import java.nio.file.DirectoryStream.Filter;
+import java.nio.file.FileAlreadyExistsException;
import java.nio.file.FileStore;
import java.nio.file.FileSystem;
+import java.nio.file.FileSystemAlreadyExistsException;
import java.nio.file.LinkOption;
+import java.nio.file.NoSuchFileException;
import java.nio.file.OpenOption;
import java.nio.file.Path;
import java.nio.file.attribute.BasicFileAttributes;
import java.nio.file.attribute.FileAttribute;
import java.nio.file.attribute.FileAttributeView;
import java.nio.file.spi.FileSystemProvider;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.Map;
import java.util.Set;
+import javax.xml.namespace.QName;
+
+import org.argeo.api.acr.DName;
+import org.argeo.api.acr.NamespaceUtils;
+import org.argeo.api.acr.spi.ProvidedRepository;
+import org.argeo.api.acr.spi.ProvidedSession;
+import org.argeo.api.cms.CmsSession;
+import org.argeo.cms.CurrentUser;
+
public class CmsFileSystemProvider extends FileSystemProvider {
+ private Map<CmsSession, CmsFileSystem> fileSystems = Collections.synchronizedMap(new HashMap<>());
+
+ private ProvidedRepository contentRepository;
+
+ public void start() {
+
+ }
+
+ public void stop() {
+
+ }
@Override
public String getScheme() {
@Override
public FileSystem newFileSystem(URI uri, Map<String, ?> env) throws IOException {
- // TODO Auto-generated method stub
- return null;
+ CmsSession cmsSession = CurrentUser.getCmsSession();
+ if (cmsSession.isAnonymous()) {
+ // TODO deal with anonymous
+ return null;
+ }
+ if (fileSystems.containsKey(cmsSession))
+ throw new FileSystemAlreadyExistsException("CMS file system already exists for user " + cmsSession);
+
+ String host = uri.getHost();
+ if (host != null && !host.trim().equals("")) {
+// URI repoUri = new URI("http", uri.getUserInfo(), uri.getHost(), uri.getPort(), "/jcr/node", null, null);
+ // FIXME deal with remote
+ CmsFileSystem fileSystem = null;
+ fileSystems.put(cmsSession, fileSystem);
+ return fileSystem;
+ } else {
+ // FIXME send exception if it exists already
+ CmsFileSystem fileSystem = new CmsFileSystem(this, contentRepository, cmsSession);
+ fileSystems.put(cmsSession, fileSystem);
+ cmsSession.addOnCloseCallback((s) -> {
+ fileSystems.remove(s);
+ });
+ return fileSystem;
+ }
}
@Override
public FileSystem getFileSystem(URI uri) {
- // TODO Auto-generated method stub
- return null;
+ return currentUserFileSystem();
}
@Override
public Path getPath(URI uri) {
- // TODO Auto-generated method stub
- return null;
+ CmsFileSystem fileSystem = currentUserFileSystem();
+ String path = uri.getPath();
+ if (fileSystem == null)
+ try {
+ fileSystem = (CmsFileSystem) newFileSystem(uri, new HashMap<String, Object>());
+ } catch (IOException e) {
+ throw new UncheckedIOException("Could not autocreate file system for " + uri, e);
+ }
+ return fileSystem.getPath(path);
}
@Override
@Override
public DirectoryStream<Path> newDirectoryStream(Path dir, Filter<? super Path> filter) throws IOException {
- // TODO Auto-generated method stub
- return null;
+ CmsPath cmsPath = (CmsPath) dir;
+ return new ContentDirectoryStream(cmsPath, filter);
}
@Override
public void createDirectory(Path dir, FileAttribute<?>... attrs) throws IOException {
- // TODO Auto-generated method stub
+ CmsPath cmsPath = (CmsPath) dir;
+ ProvidedSession contentSession = cmsPath.getContentSession();
+ if (contentSession.exists(dir.toString()))
+ throw new FileAlreadyExistsException(dir.toString());
+ CmsPath parent = (CmsPath) cmsPath.getParent();
+ if (!contentSession.exists(parent.toString()))
+ throw new NoSuchFileException(parent.toString());
+ // TODO use a proper naming context
+ QName fileName = NamespaceUtils.parsePrefixedName(dir.getFileName().toString());
+ parent.getContent().add(fileName, DName.collection);
}
@Override
public void delete(Path path) throws IOException {
- // TODO Auto-generated method stub
-
+ CmsPath cmsPath = (CmsPath) path;
+ ProvidedSession contentSession = cmsPath.getContentSession();
+ if (!contentSession.exists(cmsPath.toString()))
+ throw new NoSuchFileException(cmsPath.toString());
+ contentSession.edit((s) -> {
+ cmsPath.getContent().remove();
+ });
}
@Override
@Override
public boolean isSameFile(Path path, Path path2) throws IOException {
- // TODO Auto-generated method stub
- return false;
+ // TODO make it smarter
+ return path.toString().equals(path2.toString());
}
@Override
public boolean isHidden(Path path) throws IOException {
- // TODO Auto-generated method stub
return false;
}
@Override
public FileStore getFileStore(Path path) throws IOException {
- // TODO Auto-generated method stub
- return null;
+ CmsFileSystem fileSystem = (CmsFileSystem) path.getFileSystem();
+ return fileSystem.getFileStore(path.toString());
}
@Override
public void checkAccess(Path path, AccessMode... modes) throws IOException {
- // TODO Auto-generated method stub
-
}
+ @SuppressWarnings("unchecked")
@Override
public <V extends FileAttributeView> V getFileAttributeView(Path path, Class<V> type, LinkOption... options) {
- // TODO Auto-generated method stub
+ CmsPath cmsPath = (CmsPath) path;
+ if (BasicFileAttributes.class.isAssignableFrom(type))
+ return (V) new ContentFileAttributeView(cmsPath.getContent());
+ else if (ContentFileAttributeView.class.isAssignableFrom(type))
+ return (V) new ContentFileAttributeView(cmsPath.getContent());
return null;
}
+ @SuppressWarnings("unchecked")
@Override
public <A extends BasicFileAttributes> A readAttributes(Path path, Class<A> type, LinkOption... options)
throws IOException {
- // TODO Auto-generated method stub
- return null;
+ CmsPath cmsPath = (CmsPath) path;
+ return (A) new ContentAttributes(cmsPath.getContent());
}
@Override
}
+ /*
+ * UTILITIES
+ */
+
+ CmsFileSystem currentUserFileSystem() {
+ CmsSession cmsSession = CurrentUser.getCmsSession();
+ return fileSystems.get(cmsSession);
+ }
+
+ void close(CmsFileSystem fileSystem) {
+ CmsSession cmsSession = fileSystem.getCmsSession();
+ CmsFileSystem ref = fileSystems.remove(cmsSession);
+ assert ref == fileSystem;
+ }
+
+ /*
+ * DEPENDENCY INJECTION
+ */
+ public void setContentRepository(ProvidedRepository contentRepository) {
+ this.contentRepository = contentRepository;
+ }
+
}
package org.argeo.cms.file.provider;
+import org.argeo.api.acr.Content;
import org.argeo.api.acr.fs.AbstractFsPath;
+import org.argeo.api.acr.spi.ProvidedContent;
+import org.argeo.api.acr.spi.ProvidedSession;
public class CmsPath extends AbstractFsPath<CmsFileSystem, CmsFileStore> {
+ final static String SEPARATOR = "/";
+
+ // lazy loaded
+ private ProvidedContent content;
+
+ ProvidedContent getContent() {
+ if (content == null) {
+ content = getFileSystem().getContent(toString());
+ }
+ return content;
+ }
+
+ CmsPath(CmsFileSystem fileSystem, Content content) {
+ this(fileSystem, content.getPath());
+ this.content = (ProvidedContent) content;
+ }
public CmsPath(CmsFileSystem filesSystem, CmsFileStore fileStore, String[] segments, boolean absolute) {
super(filesSystem, fileStore, segments, absolute);
return new CmsPath(getFileSystem(), getFileStore(), segments, absolute);
}
+ ProvidedSession getContentSession() {
+ return getFileSystem().getContentSession();
+ }
+
}
--- /dev/null
+package org.argeo.cms.file.provider;
+
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
+import java.time.Instant;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.DName;
+
+public class ContentAttributes implements BasicFileAttributes {
+ // TODO optimise for FS-based content
+ private final Content content;
+
+ public ContentAttributes(Content content) {
+ assert content != null;
+ this.content = content;
+ }
+
+ @Override
+ public FileTime lastModifiedTime() {
+ Instant t = content.get(DName.getlastmodified, Instant.class).orElseThrow();
+ return FileTime.from(t);
+ }
+
+ @Override
+ public FileTime lastAccessTime() {
+ // TODO implement the concept in ACR ?
+ return FileTime.fromMillis(0l);
+ }
+
+ @Override
+ public FileTime creationTime() {
+ Instant t = content.get(DName.getlastmodified, Instant.class).orElseThrow();
+ return FileTime.from(t);
+ }
+
+ @Override
+ public boolean isRegularFile() {
+ return isRegularFile(content);
+ }
+
+ @Override
+ public boolean isDirectory() {
+ return isDirectory(content);
+ }
+
+ @Override
+ public boolean isSymbolicLink() {
+ // TODO supports links in ACR
+ return false;
+ }
+
+ @Override
+ public boolean isOther() {
+ return !isDirectory() && !isRegularFile() && !isSymbolicLink();
+ }
+
+ @Override
+ public long size() {
+ long size = content.get(DName.getcontentlength, Long.class).orElse(-1l);
+ return size;
+ }
+
+ @Override
+ public Object fileKey() {
+ // TODO check for UUIDs, etc.
+ return null;
+ }
+
+ static boolean isDirectory(Content c) {
+ return !isRegularFile(c);
+// return c.isContentClass(DName.collection);
+ }
+
+ static boolean isRegularFile(Content c) {
+// return c.containsKey(DName.getcontenttype.qName());
+ return !c.get(DName.getcontenttype, String.class).isEmpty();
+ }
+
+}
--- /dev/null
+package org.argeo.cms.file.provider;
+
+import java.io.IOException;
+import java.io.UncheckedIOException;
+import java.nio.file.DirectoryStream;
+import java.nio.file.Path;
+import java.util.Iterator;
+
+import org.argeo.api.acr.Content;
+
+public class ContentDirectoryStream implements DirectoryStream<Path> {
+ private final CmsPath dir;
+ private final Filter<? super Path> filter;
+
+ private FilesAndCollectionsIterator iterator;
+
+ public ContentDirectoryStream(CmsPath dir, Filter<? super Path> filter) {
+ this.dir = dir;
+ this.filter = filter;
+ }
+
+ @Override
+ public void close() throws IOException {
+ }
+
+ @Override
+ public Iterator<Path> iterator() {
+ if (iterator == null)
+ iterator = new FilesAndCollectionsIterator();
+ return iterator;
+ }
+
+ class FilesAndCollectionsIterator implements Iterator<Path> {
+ private Content next;
+ private final Iterator<Content> it;
+
+ public FilesAndCollectionsIterator() {
+ Content content = dir.getContent();
+ if (!ContentAttributes.isDirectory(content))
+ throw new IllegalStateException("Content " + content + " is not a collection");
+ it = content.iterator();
+ findNext();
+ }
+
+ private void findNext() {
+ next = null;
+ while (it.hasNext() && next == null) {
+ Content n = it.next();
+ if (ContentAttributes.isRegularFile(n) || ContentAttributes.isDirectory(n)) {
+ if (filter != null) {
+ try {
+ if (filter.accept(new CmsPath(dir.getFileSystem(), n)))
+ next = n;
+ } catch (IOException e) {
+ throw new UncheckedIOException("Cannot filter " + dir, e);
+ }
+ } else {
+ next = n;
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean hasNext() {
+ return next != null;
+ }
+
+ @Override
+ public Path next() {
+ if (next == null)
+ throw new IllegalStateException("Iterator doesn't have more elements");
+ CmsPath p = new CmsPath(dir.getFileSystem(), next);
+ findNext();
+ return p;
+ }
+
+ }
+}
--- /dev/null
+package org.argeo.cms.file.provider;
+
+import java.io.IOException;
+import java.nio.ByteBuffer;
+import java.nio.file.attribute.BasicFileAttributeView;
+import java.nio.file.attribute.BasicFileAttributes;
+import java.nio.file.attribute.FileTime;
+import java.nio.file.attribute.UserDefinedFileAttributeView;
+import java.util.List;
+
+import org.argeo.api.acr.Content;
+import org.argeo.api.acr.DName;
+
+public class ContentFileAttributeView implements BasicFileAttributeView, UserDefinedFileAttributeView {
+ final static String NAME = "content";
+
+ private final Content content;
+
+ public ContentFileAttributeView(Content content) {
+ this.content = content;
+ }
+
+ @Override
+ public String name() {
+ return NAME;
+ }
+
+ /*
+ * BasicFileAttributeView
+ */
+
+ @Override
+ public BasicFileAttributes readAttributes() throws IOException {
+ return new ContentAttributes(content);
+ }
+
+ @Override
+ public void setTimes(FileTime lastModifiedTime, FileTime lastAccessTime, FileTime createTime) throws IOException {
+ if (lastModifiedTime != null)
+ content.put(DName.getlastmodified, lastModifiedTime.toInstant());
+ if (createTime != null)
+ content.put(DName.getlastmodified, createTime.toInstant());
+ // ignore last accessed time
+ }
+
+ /*
+ * UserDefinedFileAttributeView
+ */
+
+ @Override
+ public List<String> list() throws IOException {
+// List<String> res = new ArrayList<>();
+ return null;
+ }
+
+ @Override
+ public int size(String name) throws IOException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public int read(String name, ByteBuffer dst) throws IOException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public int write(String name, ByteBuffer src) throws IOException {
+ // TODO Auto-generated method stub
+ return 0;
+ }
+
+ @Override
+ public void delete(String name) throws IOException {
+ // TODO Auto-generated method stub
+
+ }
+
+ public Content getContent() {
+ return content;
+ }
+}
WWW_AUTHENTICATE("WWW-Authenticate"), //
ALLOW("Allow"), //
VIA("Via"), //
+ CONTENT_TYPE("Content-Type"), //
+ CONTENT_LENGTH("Content-Length"), //
+ CONTENT_DISPOSITION("Content-Disposition"), //
+ DATE("Date"), //
// WebDav
DAV("DAV"), //
X_FORWARDED_HOST("X-Forwarded-Host"), //
;
+ // WWW-Authenticate related constants
public final static String BASIC = "Basic";
public final static String REALM = "realm";
public final static String NEGOTIATE = "Negotiate";
+ // Content-Disposition related constants
+ public final static String ATTACHMENT = "attachment";
+ public final static String FILENAME = "filename";
+
private final String name;
private HttpHeader(String headerName) {
--- /dev/null
+package org.argeo.cms.http;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Locale;
+import java.util.Objects;
+
+import org.argeo.cms.auth.RemoteAuthRequest;
+import org.argeo.cms.auth.RemoteAuthResponse;
+import org.argeo.cms.auth.RemoteAuthSession;
+
+import com.sun.net.httpserver.HttpExchange;
+
+/**
+ * Implementation of {@link RemoteAuthRequest} and {@link RemoteAuthResponse}
+ * based on {@link HttpExchange}.
+ */
+public class RemoteAuthHttpExchange implements RemoteAuthRequest, RemoteAuthResponse {
+ private final HttpExchange httpExchange;
+ private RemoteAuthSession remoteAuthSession;
+
+ public RemoteAuthHttpExchange(HttpExchange httpExchange) {
+ this.httpExchange = httpExchange;
+ this.remoteAuthSession = (RemoteAuthSession) httpExchange.getAttribute(RemoteAuthSession.class.getName());
+ Objects.requireNonNull(this.remoteAuthSession);
+ }
+
+ @Override
+ public void setHeader(String headerName, String value) {
+ httpExchange.getResponseHeaders().put(headerName, Collections.singletonList(value));
+ }
+
+ @Override
+ public void addHeader(String headerName, String value) {
+ List<String> values = httpExchange.getResponseHeaders().getOrDefault(headerName, new ArrayList<>());
+ values.add(value);
+ }
+
+ @Override
+ public RemoteAuthSession getSession() {
+ return remoteAuthSession;
+ }
+
+ @Override
+ public RemoteAuthSession createSession() {
+ throw new UnsupportedOperationException("Cannot create remote session");
+ }
+
+ @Override
+ public Locale getLocale() {
+ // TODO Auto-generated method stub
+ return null;
+ }
+
+ @Override
+ public Object getAttribute(String key) {
+ return httpExchange.getAttribute(key);
+ }
+
+ @Override
+ public void setAttribute(String key, Object object) {
+ httpExchange.setAttribute(key, object);
+ }
+
+ @Override
+ public String getHeader(String key) {
+ List<String> lst = httpExchange.getRequestHeaders().get(key);
+ if (lst == null || lst.size() == 0)
+ return null;
+ return lst.get(0);
+ }
+
+ @Override
+ public int getLocalPort() {
+ return httpExchange.getLocalAddress().getPort();
+ }
+
+ @Override
+ public String getRemoteAddr() {
+ return httpExchange.getRemoteAddress().getHostName();
+ }
+
+ @Override
+ public int getRemotePort() {
+ return httpExchange.getRemoteAddress().getPort();
+ }
+
+}
package org.argeo.cms.http.server;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.UncheckedIOException;
import java.net.URI;
+import java.net.URLDecoder;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
import java.util.Objects;
+import org.argeo.api.acr.ContentRepository;
+import org.argeo.api.acr.ContentSession;
+import org.argeo.cms.auth.RemoteAuthUtils;
+import org.argeo.cms.http.HttpMethod;
+import org.argeo.cms.http.RemoteAuthHttpExchange;
+
import com.sun.net.httpserver.HttpContext;
import com.sun.net.httpserver.HttpExchange;
return extractPathWithingContext(httpContext, uri.getPath(), true);
}
+ /** Returns content session consistent with this HTTP context. */
+ public static ContentSession getContentSession(ContentRepository contentRepository, HttpExchange exchange) {
+ ContentSession session = RemoteAuthUtils.doAs(() -> contentRepository.get(),
+ new RemoteAuthHttpExchange(exchange));
+ return session;
+ }
+
+ /*
+ * QUERY PARAMETERS
+ */
+ /** Returns the HTTP parameters form an {@link HttpExchange}. */
+ public static Map<String, List<String>> parseParameters(HttpExchange exchange) {
+ // TODO check encoding?
+ Charset encoding = StandardCharsets.UTF_8;
+
+ Map<String, List<String>> parameters = new HashMap<>();
+ URI requestedUri = exchange.getRequestURI();
+ String query = requestedUri.getRawQuery();
+ parseQuery(query, parameters, encoding);
+
+ // TODO do we really want to support POST?
+ if (HttpMethod.POST.name().equalsIgnoreCase(exchange.getRequestMethod())) {
+ String postQuery;
+ try {
+ // We do not close the stream on purpose, since the body still needs to be read
+ BufferedReader br = new BufferedReader(new InputStreamReader(exchange.getRequestBody(), encoding));
+ postQuery = br.readLine();
+ } catch (IOException e) {
+ throw new UncheckedIOException("Cannot read exchange body", e);
+ }
+ parseQuery(postQuery, parameters, encoding);
+ }
+ return parameters;
+ }
+
+ private static void parseQuery(String query, Map<String, List<String>> parameters, Charset encoding) {
+ if (query == null)
+ return;
+ String pairs[] = query.split("[&]");
+ for (String pair : pairs) {
+ String param[] = pair.split("[=]");
+
+ String key = null;
+ String value = null;
+ if (param.length > 0) {
+ key = URLDecoder.decode(param[0], encoding);
+ }
+
+ if (param.length > 1) {
+ value = URLDecoder.decode(param[1], encoding);
+ }
+
+ if (!parameters.containsKey(key))
+ parameters.put(key, new ArrayList<>());
+ parameters.get(key).add(value);
+ }
+ }
+
/** singleton */
private HttpServerUtils() {
--- /dev/null
+package org.argeo.cms.http.server;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.UncheckedIOException;
+import java.lang.System.Logger;
+import java.lang.System.Logger.Level;
+import java.net.FileNameMap;
+import java.net.InetAddress;
+import java.net.InetSocketAddress;
+import java.net.MalformedURLException;
+import java.net.URI;
+import java.net.URL;
+import java.net.URLConnection;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Map;
+import java.util.NavigableMap;
+import java.util.TreeMap;
+
+import org.argeo.cms.acr.ContentUtils;
+import org.argeo.cms.http.HttpHeader;
+import org.argeo.cms.http.HttpStatus;
+import org.argeo.cms.util.StreamUtils;
+
+import com.sun.net.httpserver.HttpExchange;
+import com.sun.net.httpserver.HttpHandler;
+import com.sun.net.httpserver.HttpServer;
+
+/** A simple {@link HttpHandler} which just serves or proxy resources. */
+public class StaticHttpHandler implements HttpHandler {
+ private final static Logger logger = System.getLogger(StaticHttpHandler.class.getName());
+
+ private static FileNameMap fileNameMap = URLConnection.getFileNameMap();
+
+ private NavigableMap<String, Object> binds = new TreeMap<>();
+
+ @Override
+ public void handle(HttpExchange exchange) throws IOException {
+ try {
+ String path = HttpServerUtils.subPath(exchange);
+ Map.Entry<String, Object> bindEntry = findBind(path);
+ boolean isRoot = "/".equals(bindEntry.getKey());
+
+ String relPath = isRoot ? path.substring(bindEntry.getKey().length())
+ : path.substring(bindEntry.getKey().length() + 1);
+ process(bindEntry.getValue(), exchange, relPath);
+ } catch (Exception e) {
+ logger.log(Level.ERROR, exchange.getRequestURI().toString(), e);
+ }
+ }
+
+ public void addBind(String path, Object bind) {
+ if (binds.containsKey(path))
+ throw new IllegalStateException("Path '" + path + "' is already bound");
+ Object bindToUse = checkBindSupport(bind);
+ binds.put(path, bindToUse);
+ }
+
+ protected Map.Entry<String, Object> findBind(String path) {
+ Map.Entry<String, Object> entry = binds.floorEntry(path);
+ if (entry == null)
+ return null;
+ String mountPath = entry.getKey();
+ if (!path.startsWith(mountPath)) {
+ // FIXME make it more robust and find when there is no content provider
+ String[] parent = ContentUtils.getParentPath(path);
+ return findBind(parent[0]);
+ }
+ return entry;
+ }
+
+ protected void process(Object bind, HttpExchange httpExchange, String relativePath) throws IOException {
+ OutputStream out = null;
+
+ try {
+ String contentType = fileNameMap.getContentTypeFor(relativePath);
+ if (contentType != null)
+ httpExchange.getResponseHeaders().set(HttpHeader.CONTENT_TYPE.getHeaderName(), contentType);
+
+ if (bind instanceof Path bindPath) {
+ Path path = bindPath.resolve(relativePath);
+ if (!Files.exists(path)) {
+ httpExchange.sendResponseHeaders(HttpStatus.NOT_FOUND.getCode(), -1);
+ return;
+ }
+ long size = Files.size(path);
+ httpExchange.sendResponseHeaders(HttpStatus.OK.getCode(), size);
+ out = httpExchange.getResponseBody();
+ Files.copy(path, out);
+ } else if (bind instanceof URL bindUrl) {
+ URL url = new URL(bindUrl.toString() + relativePath);
+ URLConnection urlConnection;
+ try {
+ urlConnection = url.openConnection();
+ urlConnection.connect();
+ } catch (IOException e) {
+ httpExchange.sendResponseHeaders(HttpStatus.NOT_FOUND.getCode(), -1);
+ return;
+ }
+ // TODO check other headers?
+ // TODO use Proxy?
+ String contentLengthStr = urlConnection.getHeaderField(HttpHeader.CONTENT_LENGTH.getHeaderName());
+ httpExchange.sendResponseHeaders(HttpStatus.OK.getCode(),
+ contentLengthStr != null ? Long.parseLong(contentLengthStr) : 0);
+ try (InputStream in = urlConnection.getInputStream()) {
+ out = httpExchange.getResponseBody();
+ StreamUtils.copy(in, out);
+ } finally {
+ }
+ }
+ // make sure everything is flushed
+ httpExchange.getResponseBody().flush();
+ } catch (RuntimeException e) {
+ try {
+ httpExchange.sendResponseHeaders(HttpStatus.INTERNAL_SERVER_ERROR.getCode(), -1);
+ } catch (IOException e1) {
+ // silent
+ }
+ throw e;
+ } finally {
+ if (out != null) {
+ try {
+ out.close();
+ } catch (IOException e) {
+ throw e;
+ }
+ }
+ }
+ }
+
+ /**
+ * Checks whether this bind type is supported. This can be overridden in order
+ * to ass new bind type.
+ *
+ * @see #process(Object, HttpExchange, String) for overriding the actual
+ * implementation.
+ *
+ * @param bind the bind to check
+ * @return the bind object to actually use (an URI will have been converted to
+ * URL)
+ * @throws UnsupportedOperationException if this bind type is not supported
+ */
+ protected Object checkBindSupport(Object bind) throws UnsupportedOperationException {
+ if (bind instanceof Path)
+ return bind;
+ if (bind instanceof URL)
+ return bind;
+ if (bind instanceof URI uri) {
+ try {
+ return uri.toURL();
+ } catch (MalformedURLException e) {
+ throw new UnsupportedOperationException("URI " + uri + " cannot be connverted to URL.", e);
+ }
+ }
+ // TODO string as a path within the server?
+ throw new UnsupportedOperationException("Bind " + bind + " type " + bind.getClass() + " is not supported.");
+ }
+
+ public static void main(String... args) {
+ try {
+ HttpServer httpServer = HttpServer.create(new InetSocketAddress(InetAddress.getLoopbackAddress(), 6060), 0);
+
+ StaticHttpHandler staticHttpHandler = new StaticHttpHandler();
+ staticHttpHandler.addBind("/", Paths.get("/home/mbaudier/dev/workspaces/test-node-js/test-static"));
+ staticHttpHandler.addBind("/js",
+ Paths.get("/home/mbaudier/dev/workspaces/test-node-js/test-static/node_modules"));
+
+ httpServer.createContext("/", staticHttpHandler);
+ httpServer.start();
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+}
import org.argeo.cms.auth.RemoteAuthRequest;
import org.argeo.cms.auth.RemoteAuthResponse;
import org.argeo.cms.auth.RemoteAuthUtils;
+import org.argeo.cms.http.RemoteAuthHttpExchange;
import com.sun.net.httpserver.Authenticator;
import com.sun.net.httpserver.HttpExchange;
+++ /dev/null
-package org.argeo.cms.internal.http;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Locale;
-import java.util.Objects;
-
-import org.argeo.cms.auth.RemoteAuthRequest;
-import org.argeo.cms.auth.RemoteAuthResponse;
-import org.argeo.cms.auth.RemoteAuthSession;
-
-import com.sun.net.httpserver.HttpExchange;
-
-public class RemoteAuthHttpExchange implements RemoteAuthRequest, RemoteAuthResponse {
- private final HttpExchange httpExchange;
- private RemoteAuthSession remoteAuthSession;
-
- public RemoteAuthHttpExchange(HttpExchange httpExchange) {
- this.httpExchange = httpExchange;
- this.remoteAuthSession = (RemoteAuthSession) httpExchange.getAttribute(RemoteAuthSession.class.getName());
- Objects.requireNonNull(this.remoteAuthSession);
- }
-
- @Override
- public void setHeader(String headerName, String value) {
- httpExchange.getResponseHeaders().put(headerName, Collections.singletonList(value));
- }
-
- @Override
- public void addHeader(String headerName, String value) {
- List<String> values = httpExchange.getResponseHeaders().getOrDefault(headerName, new ArrayList<>());
- values.add(value);
- }
-
- @Override
- public RemoteAuthSession getSession() {
- return remoteAuthSession;
- }
-
- @Override
- public RemoteAuthSession createSession() {
- throw new UnsupportedOperationException("Cannot create remote session");
- }
-
- @Override
- public Locale getLocale() {
- // TODO Auto-generated method stub
- return null;
- }
-
- @Override
- public Object getAttribute(String key) {
- return httpExchange.getAttribute(key);
- }
-
- @Override
- public void setAttribute(String key, Object object) {
- httpExchange.setAttribute(key, object);
- }
-
- @Override
- public String getHeader(String key) {
- List<String> lst = httpExchange.getRequestHeaders().get(key);
- if (lst == null || lst.size() == 0)
- return null;
- return lst.get(0);
- }
-
- @Override
- public int getLocalPort() {
- return httpExchange.getLocalAddress().getPort();
- }
-
- @Override
- public String getRemoteAddr() {
- return httpExchange.getRemoteAddress().getHostName();
- }
-
- @Override
- public int getRemotePort() {
- return httpExchange.getRemoteAddress().getPort();
- }
-
-}
import org.argeo.cms.dav.DavPropfind;
import org.argeo.cms.dav.DavResponse;
import org.argeo.cms.http.HttpStatus;
-import org.argeo.cms.internal.http.RemoteAuthHttpExchange;
+import org.argeo.cms.http.RemoteAuthHttpExchange;
import org.argeo.cms.util.StreamUtils;
import com.sun.net.httpserver.HttpExchange;
import static org.argeo.api.cms.CmsConstants.CONTEXT_PATH;
import java.util.Map;
+import java.util.Objects;
import java.util.TreeMap;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CompletionStage;
import org.argeo.api.cms.CmsConstants;
import org.argeo.api.cms.CmsDeployment;
import org.argeo.api.cms.CmsLog;
+import org.argeo.api.cms.CmsSshd;
import org.argeo.api.cms.CmsState;
import org.argeo.cms.CmsDeployProperty;
-import org.argeo.cms.CmsSshd;
import org.argeo.cms.internal.http.CmsAuthenticator;
import org.argeo.cms.internal.http.PublicCmsAuthenticator;
private boolean sshdExpected = false;
// HTTP
- private HttpServer httpServer;
+ private CompletableFuture<HttpServer> httpServer = new CompletableFuture<>();
private Map<String, HttpHandler> httpHandlers = new TreeMap<>();
private Map<String, CmsAuthenticator> httpAuthenticators = new TreeMap<>();
// SSHD
- private CmsSshd cmsSshd;
+ private CompletableFuture<CmsSshd> cmsSshd = new CompletableFuture<>();
public void start() {
log.debug(() -> "CMS deployment available");
String httpPort = this.cmsState.getDeployProperty(CmsDeployProperty.HTTP_PORT.getProperty());
String httpsPort = this.cmsState.getDeployProperty(CmsDeployProperty.HTTPS_PORT.getProperty());
httpExpected = httpPort != null || httpsPort != null;
+ if (!httpExpected)
+ httpServer.complete(null);
String sshdPort = this.cmsState.getDeployProperty(CmsDeployProperty.SSHD_PORT.getProperty());
sshdExpected = sshdPort != null;
+ if (!sshdExpected)
+ cmsSshd.complete(null);
}
public void setHttpServer(HttpServer httpServer) {
- this.httpServer = httpServer;
+ Objects.requireNonNull(httpServer);
+ this.httpServer.complete(httpServer);
// create contexts whose handles had already been published
for (String contextPath : httpHandlers.keySet()) {
HttpHandler httpHandler = httpHandlers.get(contextPath);
}
public void createHttpContext(String contextPath, HttpHandler httpHandler, CmsAuthenticator authenticator) {
- HttpContext httpContext = httpServer.createContext(contextPath);
+ if (!httpExpected) {
+ if (log.isTraceEnabled())
+ log.warn("Ignore HTTP context " + contextPath + " as we don't provide an HTTP server");
+ return;
+ }
+ HttpContext httpContext = httpServer.join().createContext(contextPath);
// we want to set the authenticator BEFORE the handler actually becomes active
httpContext.setAuthenticator(authenticator);
httpContext.setHandler(httpHandler);
httpHandlers.remove(contextPath);
if (httpServer == null)
return;
- httpServer.removeContext(contextPath);
+ httpServer.join().removeContext(contextPath);
log.debug(() -> "Removed handler " + contextPath + " : " + httpHandler.getClass().getName());
}
}
public void setCmsSshd(CmsSshd cmsSshd) {
- this.cmsSshd = cmsSshd;
+ Objects.requireNonNull(cmsSshd);
+ this.cmsSshd.complete(cmsSshd);
+ }
+
+ @Override
+ public CompletionStage<HttpServer> getHttpServer() {
+ return httpServer.minimalCompletionStage();
+ }
+
+ @Override
+ public CompletionStage<CmsSshd> getCmsSshd() {
+ return cmsSshd.minimalCompletionStage();
}
}
import java.net.InetAddress;
import java.net.URL;
import java.net.UnknownHostException;
+import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
}
public void start() {
+ Charset defaultCharset = Charset.defaultCharset();
+ if (!StandardCharsets.UTF_8.equals(defaultCharset))
+ log.error("Default JVM charset is " + defaultCharset + " and not " + StandardCharsets.UTF_8);
try {
// First init check
Path privateBase = getDataPath(KernelConstants.DIR_PRIVATE);
--- /dev/null
+package org.argeo.cms.util;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.io.UncheckedIOException;
+import java.util.Objects;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+
+/**
+ * An output stream whose {@link #close()} method will wait for read actions to
+ * be completed. It is meant to be used transparently as an
+ * {@link OutputStream}, fulfilling the expectation that everything has been
+ * done when the {@link #close()} method has returned.
+ */
+public class AsyncPipedOutputStream extends PipedOutputStream {
+// private final static Logger logger = System.getLogger(AsyncPipedOutputStream.class.getName());
+
+ private CompletableFuture<Void> readingDone;
+
+ private long timeout = 60 * 1000;
+
+ /**
+ * Provides the actions which will read (and close) the related piped input
+ * stream. Reading starts immediately asynchronously, but the provided
+ * {@link InputStream} will block until data starts to be written to this output
+ * stream.
+ */
+ public void asyncRead(Consumer<InputStream> readActions) {
+ try {
+ PipedInputStream in = new PipedInputStream(this);
+ readingDone = CompletableFuture.runAsync(() -> {
+ readActions.accept(in);
+ });
+ } catch (IOException e) {
+ throw new UncheckedIOException("Cannot create piped input stream", e);
+ }
+ }
+
+ /**
+ * Closes this output stream immediately but then wait for the reading of the
+ * related input stream to be completed.
+ */
+ @Override
+ public void close() throws IOException {
+ Objects.requireNonNull(readingDone, "Async read must have started");
+ super.flush();
+ super.close();
+ readingDone.orTimeout(timeout, TimeUnit.MILLISECONDS).join();
+// logger.log(Logger.Level.DEBUG, "OUT waiting " + timeout);
+// try {
+// readingDone.get(timeout, TimeUnit.MILLISECONDS);
+// } catch (InterruptedException | ExecutionException | TimeoutException e) {
+// logger.log(Logger.Level.ERROR, "Reading was not completed", e);
+// }
+// logger.log(Logger.Level.DEBUG, "OUT closed");
+ }
+
+ /**
+ * Sets the timeout in milliseconds when waiting for reading to be completed
+ * before returning in the {@link #close()} method.
+ */
+ public void setTimeout(long timeout) {
+ this.timeout = timeout;
+ }
+
+}
import java.time.temporal.Temporal;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Iterator;
+import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Properties;
import javax.naming.InvalidNameException;
throw new IllegalArgumentException("Index " + index + " is not available (size is " + i + ")");
}
+ public static <K, V extends Comparable<? super V>> Map<K, V> sortByValue(Map<K, V> map) {
+ return sortByValue(map, false);
+ }
+
+ public static <K, V extends Comparable<? super V>> Map<K, V> sortByValue(Map<K, V> map, boolean descending) {
+ List<Entry<K, V>> list = new ArrayList<>(map.entrySet());
+ list.sort(Entry.comparingByValue());
+ if (descending)
+ Collections.reverse(list);
+
+ Map<K, V> result = new LinkedHashMap<>();
+ for (Entry<K, V> entry : list) {
+ result.put(entry.getKey(), entry.getValue());
+ }
+
+ return result;
+ }
+
/*
* EXCEPTIONS
*/
package org.argeo.cms.util;
import java.io.File;
+import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
// TODO support multiple names
runDir = Paths.get(xdgRunDir);
} else {
- if (System.getProperty("user.name").equals("root")) {
+ String username = System.getProperty("user.name");
+ if (username.equals("root")) {
runDir = Paths.get("/run");
} else {
- runDir = Paths.get(System.getProperty("user.home"), ".cache/argeo");
+ Path homeDir = Paths.get(System.getProperty("user.home"));
+ if (!Files.isWritable(homeDir)) {
+ // typically, dameon's home (/usr/sbin) is not writable
+ runDir = Paths.get("/tmp/" + username + "/run");
+ } else {
+ runDir = homeDir.resolve(".cache/argeo");
+ }
}
}
return runDir;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
+import java.io.UncheckedIOException;
import java.io.Writer;
+import java.nio.charset.Charset;
+import java.nio.charset.StandardCharsets;
import java.util.StringJoiner;
/** Stream utilities to be used when Apache Commons IO is not available. */
}
}
+ public static String toString(Class<?> clss, String resource) {
+ return toString(clss.getResourceAsStream(resource), StandardCharsets.UTF_8);
+ }
+
+ public static String toString(InputStream in) {
+ return toString(in, StandardCharsets.UTF_8);
+ }
+
+ public static String toString(InputStream in, Charset encoding) {
+ try {
+ return new String(in.readAllBytes(), encoding);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
public static String toString(BufferedReader reader) throws IOException {
StringJoiner sn = new StringJoiner("\n");
String line = null;
Bundle bundle = bc.installBundle(referenceUrl);
return bundle;
} else {
-
- Path tempJar = null;
- if (locator instanceof Path && Files.isDirectory((Path) locator))
- tempJar = toTempJar((Path) locator);
+ Path locatorPath = (Path) locator;
+ Path pathToUse;
+ boolean isTemp = false;
+ if (locator instanceof Path && Files.isDirectory(locatorPath)) {
+ pathToUse = toTempJar(locatorPath);
+ isTemp = true;
+ } else {
+ pathToUse = locatorPath;
+ }
Bundle bundle;
- try (InputStream in = newInputStream(tempJar != null ? tempJar : locator)) {
- bundle = bc.installBundle(module.getBranch().getCoordinates(), in);
+ try (InputStream in = newInputStream(pathToUse)) {
+ bundle = bc.installBundle(locatorPath.toAbsolutePath().toString(), in);
}
- if (tempJar != null)
- Files.deleteIfExists(tempJar);
+ if (isTemp && pathToUse != null)
+ Files.deleteIfExists(pathToUse);
return bundle;
}
} catch (BundleException | IOException e) {
bundle.update(in);
}
} else {
- Path tempJar = null;
- if (locator instanceof Path && Files.isDirectory((Path) locator))
- tempJar = toTempJar((Path) locator);
- try (InputStream in = newInputStream(tempJar != null ? tempJar : locator)) {
+ Path locatorPath = (Path) locator;
+ Path pathToUse;
+ boolean isTemp = false;
+ if (locator instanceof Path && Files.isDirectory(locatorPath)) {
+ pathToUse = toTempJar(locatorPath);
+ isTemp = true;
+ } else {
+ pathToUse = locatorPath;
+ }
+ try (InputStream in = newInputStream(pathToUse)) {
bundle.update(in);
}
- if (tempJar != null)
- Files.deleteIfExists(tempJar);
+ if (isTemp && pathToUse != null)
+ Files.deleteIfExists(pathToUse);
}
} catch (BundleException | IOException e) {
throw new A2Exception("Cannot update module " + module, e);
private final boolean synchronous;
ThinLogging() {
- publisher = new LogEntryPublisher();
synchronous = Boolean.parseBoolean(System.getProperty(PROP_ARGEO_LOGGING_SYNCHRONOUS, "false"));
if (synchronous) {
synchronousSubscriber = new PrintStreamSubscriber();
+ publisher = null;
} else {
PrintStreamSubscriber subscriber = new PrintStreamSubscriber();
+ publisher = new LogEntryPublisher();
publisher.subscribe(subscriber);
}
Runtime.getRuntime().addShutdownHook(new Thread(() -> close(), "Log shutdown"));
return Collections.unmodifiableNavigableMap(levels);
}
+ private void dispatchLogEntry(ThinLogger logger, Level level, ResourceBundle bundle, String msg, Instant instant,
+ Thread thread, Throwable thrown, StackTraceElement callLocation) {
+ assert level != null;
+ assert logger != null;
+// assert msg != null;
+ assert instant != null;
+ assert thread != null;
+
+ if (msg == null)
+ msg = "null";
+
+ final long sequence = nextEntry.incrementAndGet();
+
+ Map<String, Serializable> logEntry = new LogEntryMap(sequence);
+
+ // same object as key class name
+ logEntry.put(KEY_LEVEL, level);
+ logEntry.put(KEY_MSG, msg);
+ logEntry.put(KEY_INSTANT, instant);
+ if (thrown != null)
+ logEntry.put(KEY_THROWABLE, thrown);
+ if (callLocation != null)
+ logEntry.put(KEY_CALL_LOCATION, callLocation);
+
+ // object is a string
+ logEntry.put(KEY_LOGGER, logger.getName());
+ logEntry.put(KEY_THREAD, thread.getName());
+
+ // should be unmodifiable for security reasons
+ if (synchronous) {
+ assert synchronousSubscriber != null;
+ synchronousSubscriber.onNext(logEntry);
+ } else {
+ if (!publisher.isClosed())
+ publisher.submit(Collections.unmodifiableMap(logEntry));
+ }
+
+ }
+
/*
* INTERNAL CLASSES
*/
// measure timestamp first
Instant now = Instant.now();
Thread thread = Thread.currentThread();
- publisher.log(this, level, bundle, msg, now, thread, thrown, findCallLocation(level, thread));
+ dispatchLogEntry(ThinLogger.this, level, bundle, msg, now, thread, thrown, findCallLocation(level, thread));
}
@Override
format = sb.toString();
}
String msg = params == null ? format : MessageFormat.format(format, params);
- publisher.log(this, level, bundle, msg, now, thread, (Throwable) null, findCallLocation(level, thread));
+ dispatchLogEntry(ThinLogger.this, level, bundle, msg, now, thread, (Throwable) null,
+ findCallLocation(level, thread));
}
private void setLevel(Level level) {
super();
}
- private void log(ThinLogger logger, Level level, ResourceBundle bundle, String msg, Instant instant,
- Thread thread, Throwable thrown, StackTraceElement callLocation) {
- assert level != null;
- assert logger != null;
- assert msg != null;
- assert instant != null;
- assert thread != null;
-
- final long sequence = nextEntry.incrementAndGet();
-
- Map<String, Serializable> logEntry = new LogEntryMap(sequence);
-
- // same object as key class name
- logEntry.put(KEY_LEVEL, level);
- logEntry.put(KEY_MSG, msg);
- logEntry.put(KEY_INSTANT, instant);
- if (thrown != null)
- logEntry.put(KEY_THROWABLE, thrown);
- if (callLocation != null)
- logEntry.put(KEY_CALL_LOCATION, callLocation);
-
- // object is a string
- logEntry.put(KEY_LOGGER, logger.getName());
- logEntry.put(KEY_THREAD, thread.getName());
-
- // should be unmodifiable for security reasons
- if (synchronous) {
- assert synchronousSubscriber != null;
- synchronousSubscriber.onNext(logEntry);
- } else {
- if (!isClosed())
- submit(Collections.unmodifiableMap(logEntry));
- }
- }
+// private void log(ThinLogger logger, Level level, ResourceBundle bundle, String msg, Instant instant,
+// Thread thread, Throwable thrown, StackTraceElement callLocation) {
+//
+// }
}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" activate="start" deactivate="stop" name="Jetty Service Factory">
+ <implementation class="org.argeo.cms.equinox.http.jetty.EquinoxJettyServer"/>
+ <property name="service.pid" type="String" value="org.argeo.equinox.jetty.config"/>
+ <reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
+ <service>
+ <provide interface="com.sun.net.httpserver.HttpServer"/>
+ <provide interface="com.sun.net.httpserver.HttpsServer"/>
+ </service>
+</scr:component>
+++ /dev/null
-<?xml version="1.0" encoding="UTF-8"?>
-<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" immediate="true" activate="start" deactivate="stop" name="Jetty Service Factory">
- <implementation class="org.argeo.cms.equinox.http.jetty.EquinoxJettyServer"/>
- <property name="service.pid" type="String" value="org.argeo.equinox.jetty.config"/>
- <reference bind="setCmsState" cardinality="1..1" interface="org.argeo.api.cms.CmsState" name="CmsState" policy="static"/>
- <service>
- <provide interface="com.sun.net.httpserver.HttpServer"/>
- </service>
-</scr:component>
Service-Component: \
-OSGI-INF/jettyServiceFactory.xml,\
+OSGI-INF/equinoxJettyServer.xml,\
-Subproject commit d9cae87d811258d5a13e43eea8492f3792377ce4
+Subproject commit d5943f556d6fba9db0dd63d4c4cfceef89e4888e
major=2
minor=3
-micro=18
+micro=23
qualifier=
Bundle-Copyright= \
SPDX-License-Identifier= \
LGPL-2.1-or-later \
OR EPL-2.0 \
-OR LicenseRef-argeo2-GPL-2.0-or-later-with-EPL-and-JCR-permissions
+OR LicenseRef-argeo2-GPL-2.0-or-later-with-EPL-and-Apache-and-JCR-permissions
-argeo.osgi.start.2.node=\
-org.eclipse.equinox.metatype,\
-org.eclipse.equinox.cm,\
-org.eclipse.equinox.ds,\
+argeo.osgi.start.2=\
+org.apache.felix.scr,\
org.argeo.init
-argeo.osgi.start.3.node=\
+argeo.osgi.start.3=\
org.argeo.cms,\
-org.argeo.cms.jcr,\
-org.argeo.cms.ui.rcp
+org.argeo.cms.swt.rcp,\
+org.argeo.cms.ee,\
+org.argeo.cms.lib.dbus,\
+argeo.osgi.start.5=\
+org.argeo.app.profile.acr.fs,\
-# Local
-argeo.node.repo.type=h2
-org.osgi.service.http.port=7070
-#org.osgi.service.http.port.secure=7073
-argeo.node.useradmin.uris=os:///
+
+#argeo.node.useradmin.uris=os:///
+argeo.directory=ipa:///
#argeo.node.useradmin.uris=ldap://cn=Directory%20Manager:argeoargeo@localhost:10389/dc=example,dc=com
# DON'T CHANGE BELOW
org.eclipse.equinox.http.jetty.autostart=false
-org.osgi.framework.bootdelegation=com.sun.jndi.ldap,\
+org.osgi.framework.bootdelegation=\
+sun.security.internal.spec,\
+sun.security.provider,\
+com.sun.jndi.ldap,\
com.sun.jndi.ldap.sasl,\
com.sun.security.jgss,\
com.sun.jndi.dns,\
--- /dev/null
+#!/usr/bin/sh
+java -Xms32m -Xmx64m -jar /usr/share/a2/org.argeo.cms/org.argeo.cms.jshell.2.3.jar "$@"
\ No newline at end of file
</service>
<reference bind="setCmsContext" cardinality="1..1" interface="org.argeo.api.cms.CmsContext" name="CmsContext" policy="static"/>
<reference bind="setContentRepository" cardinality="1..1" interface="org.argeo.api.acr.ContentRepository" name="ContentRepository" policy="static"/>
+ <reference bind="setCmsFileSystemProvider" cardinality="1..1" interface="java.nio.file.spi.FileSystemProvider" name="CmsFileSystemProvider" policy="static"/>
</scr:component>
package org.argeo.cms.swt;
-import java.net.URLEncoder;
-import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
-import java.util.StringTokenizer;
import org.argeo.api.cms.ux.CmsIcon;
import org.argeo.api.cms.ux.CmsStyle;
child.dispose();
}
- /** Clean reserved URL characters for use in HTTP links. */
- public static String cleanPathForUrl(String path) {
- StringTokenizer st = new StringTokenizer(path, "/");
- StringBuilder sb = new StringBuilder();
- while (st.hasMoreElements()) {
- sb.append('/');
- String encoded = URLEncoder.encode(st.nextToken(), StandardCharsets.UTF_8);
- encoded = encoded.replace("+", "%20");
- sb.append(encoded);
-
- }
- return sb.toString();
- }
-
/** Singleton. */
private CmsSwtUtils() {
}
import java.util.Optional;
import org.argeo.api.acr.Content;
-import org.argeo.api.cms.CmsConstants;
import org.argeo.api.cms.ux.Cms2DSize;
+import org.argeo.cms.acr.ContentUtils;
import org.argeo.cms.acr.SvgAttrs;
import org.argeo.cms.swt.AbstractSwtImageManager;
-import org.argeo.cms.swt.CmsSwtUtils;
import org.argeo.cms.ux.CmsUxUtils;
import org.eclipse.swt.graphics.ImageData;
}
protected String getDataPathForUrl(Content content) {
- return CmsSwtUtils.cleanPathForUrl(getDataPath(content));
- }
-
- /** A path in the node repository */
- protected String getDataPath(Content node) {
- // TODO make it more configurable?
- StringBuilder buf = new StringBuilder(CmsConstants.PATH_API_ACR);
- buf.append(node.getPath());
- return buf.toString();
+ return ContentUtils.getDataPathForUrl(content);
}
@Override
import java.net.URI;
import org.argeo.api.acr.Content;
-import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.acr.ContentUtils;
import org.argeo.cms.swt.widgets.StyledControl;
import org.eclipse.swt.widgets.Composite;
if (plainUri != null)
return plainUri;
if (linkedContent != null)
- return URI.create("#" + CmsSwtUtils.cleanPathForUrl(linkedContent.getPath()));
+ return URI.create("#" + ContentUtils.cleanPathForUrl(linkedContent.getPath()));
return null;
}
import org.argeo.api.acr.Content;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Layout;
+/**
+ * Creates SWT UI parts based on a given view/model context. For simple UIs, the
+ * view and controller can usually be implemented directly in the method, while
+ * more complex UI components may require a dedicated object (either an internal
+ * or external class). The main purpose of this factory pattern is to ease the
+ * dependency injection of other services used.
+ */
@FunctionalInterface
public interface SwtUiProvider {
+ /**
+ * Populates the provided {@link Composite} with SWT UI components (view) and
+ * listeners (controller), based on the provided {@link Content}. For a typical
+ * view or editor, the context will be the data to display/edit, but it can also
+ * just be used to access the underlying data session.
+ *
+ * @param parent the SWT {@link Composite} to use as parent for the widgets.
+ * Implementations should not assume that a {@link Layout} has
+ * been set on it, and should therefore set the appropriate
+ * layout themselves.
+ * @param context the data to display or a generic data context (typically a
+ * user home area).
+ * @return a {@link Control} within the parent on which to focus if needed, Can
+ * be null, so client should always check.
+ */
Control createUiPart(Composite parent, Content context);
}
package org.argeo.cms.swt.app;
+import java.net.URI;
+import java.nio.file.Path;
+import java.nio.file.spi.FileSystemProvider;
import java.util.HashSet;
import java.util.Set;
import org.argeo.api.acr.Content;
import org.argeo.api.acr.ContentRepository;
-import org.argeo.api.cms.CmsContext;
import org.argeo.api.cms.ux.CmsUi;
import org.argeo.api.cms.ux.CmsView;
import org.argeo.cms.AbstractCmsApp;
import org.argeo.cms.swt.CmsSwtUi;
import org.argeo.cms.swt.CmsSwtUtils;
import org.argeo.cms.swt.auth.CmsLogin;
+import org.argeo.eclipse.ui.fs.SimpleFsBrowser;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
public class CmsUserApp extends AbstractCmsApp {
private ContentRepository contentRepository;
+ private FileSystemProvider cmsFileSystemProvider;
+
@Override
public Set<String> getUiNames() {
Set<String> uiNames = new HashSet<>();
AcrContentTreeView view = new AcrContentTreeView(cmsUi, 0, rootContent);
view.setLayoutData(CmsSwtUtils.fillAll());
+ } else if ("app".equals(uiName)) {
+ Path rootPath = cmsFileSystemProvider.getPath(URI.create("cms:///"));
+ SimpleFsBrowser view = new SimpleFsBrowser(cmsUi, 0);
+ view.setInput(rootPath);
+ view.setLayoutData(CmsSwtUtils.fillAll());
+
}
return cmsUi;
}
this.contentRepository = contentRepository;
}
+ public void setCmsFileSystemProvider(FileSystemProvider cmsFileSystemProvider) {
+ this.cmsFileSystemProvider = cmsFileSystemProvider;
+ }
+
}
\ No newline at end of file
--- /dev/null
+package org.argeo.cms.swt.app;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.argeo.api.cms.CmsApp;
+import org.argeo.api.cms.ux.CmsUi;
+import org.argeo.cms.AbstractCmsApp;
+import org.argeo.cms.swt.CmsSwtUi;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+
+/** Simplifies creating a simple {@link CmsApp} based on SWT. */
+public class SimpleSwtApp extends AbstractCmsApp {
+ protected final static String DEFAULT_UI_NAME = "app";
+
+ protected void createDefaultUi(Composite parent) {
+
+ }
+
+ protected void createUi(String uiName, Composite parent) {
+ if (DEFAULT_UI_NAME.equals(uiName)) {
+ createDefaultUi(parent);
+ }
+ }
+
+ @Override
+ public Set<String> getUiNames() {
+ Set<String> uiNames = new HashSet<>();
+ uiNames.add(DEFAULT_UI_NAME);
+ return uiNames;
+ }
+
+ @Override
+ public CmsUi initUi(Object uiParent) {
+ Composite parent = (Composite) uiParent;
+ String uiName = parent.getData(UI_NAME_PROPERTY) != null ? parent.getData(UI_NAME_PROPERTY).toString() : null;
+ CmsSwtUi cmsUi = new CmsSwtUi(parent, SWT.NONE);
+ if (uiName != null)
+ createUi(uiName, cmsUi);
+ return cmsUi;
+ }
+
+ @Override
+ public void refreshUi(CmsUi cmsUi, String state) {
+ }
+
+ @Override
+ public void setState(CmsUi cmsUi, String state) {
+ }
+
+}
import static org.eclipse.rap.rwt.internal.service.ContextProvider.getApplicationContext;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.security.PrivilegedAction;
import java.util.Locale;
import java.util.UUID;
return cmsSession;
}
+ @Override
+ public URI toBackendUri(String url) {
+ try {
+ return new URI(url);
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("Cannot convert " + url, e);
+ }
+ }
+
/*
* EntryPoint IMPLEMENTATION
*/
public class EclipseUiSpecificUtils {
public static void setStyleData(Widget widget, Object data) {
- widget.setData(RWT.CUSTOM_VARIANT, data);
+ if (!widget.isDisposed())
+ widget.setData(RWT.CUSTOM_VARIANT, data);
}
public static Object getStyleData(Widget widget) {
<implementation class="org.argeo.cms.ui.rcp.dbus.CmsRcpDBusLauncher"/>
<reference bind="setCmsDBus" cardinality="1..1" interface="org.argeo.cms.dbus.CmsDBus" name="CmsDBus" policy="static"/>
<reference bind="addCmsApp" cardinality="0..n" interface="org.argeo.api.cms.CmsApp" name="CmsApp" policy="dynamic" unbind="removeCmsApp"/>
+ <reference bind="setCmsRcpDisplayFactory" cardinality="1..1" interface="org.argeo.cms.ui.rcp.CmsRcpDisplayFactory" name="CmsRcpDisplayFactory" policy="static"/>
</scr:component>
<?xml version="1.0" encoding="UTF-8"?>
<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" immediate="true" name="CMS RCP Display Factory">
<implementation class="org.argeo.cms.ui.rcp.CmsRcpDisplayFactory"/>
+ <service>
+ <provide interface="org.argeo.cms.ui.rcp.CmsRcpDisplayFactory"/>
+ </service>
+ <reference bind="setCmsDeployment" cardinality="1..1" interface="org.argeo.api.cms.CmsDeployment" name="CmsDeployment" policy="static"/>
</scr:component>
Import-Package:\
org.argeo.cms.auth,\
+org.argeo.api.acr,\
org.eclipse.swt,\
org.eclipse.swt.widgets,\
org.eclipse.swt.graphics,\
import java.io.IOException;
import java.io.InputStream;
+import java.net.URI;
+import java.net.URISyntaxException;
import java.security.PrivilegedAction;
import java.util.Map;
import java.util.UUID;
import org.argeo.api.cms.CmsEventBus;
import org.argeo.api.cms.CmsLog;
import org.argeo.api.cms.CmsSession;
+import org.argeo.api.cms.ux.CmsImageManager;
import org.argeo.api.cms.ux.CmsTheme;
import org.argeo.api.cms.ux.CmsView;
import org.argeo.cms.swt.AbstractSwtCmsView;
import org.argeo.cms.swt.CmsSwtUtils;
+import org.argeo.cms.swt.acr.AcrSwtImageManager;
import org.eclipse.e4.ui.css.core.engine.CSSEngine;
import org.eclipse.e4.ui.css.core.engine.CSSErrorHandler;
import org.eclipse.e4.ui.css.swt.engine.CSSSWTEngineImpl;
private CSSEngine cssEngine;
+ private String httpServerBase;
+
public CmsRcpApp(String uiName) {
super(uiName);
uid = UUID.randomUUID().toString();
}
+ @SuppressWarnings("rawtypes")
public void initRcpApp() {
+ imageManager = (CmsImageManager) new AcrSwtImageManager();
+
display = Display.getCurrent();
shell = new Shell(display);
shell.setText("Argeo CMS");
return cmsApp;
}
+ @Override
+ public URI toBackendUri(String url) {
+ try {
+ URI u = new URI(url);
+ if (u.getHost() == null) {
+ // TODO make it more robust
+ u = new URI(httpServerBase + url);
+ }
+ return u;
+ } catch (URISyntaxException e) {
+ throw new IllegalArgumentException("Cannot convert " + url, e);
+ }
+ }
+
+ public void setHttpServerBase(String httpServerBase) {
+ this.httpServerBase = httpServerBase;
+ }
+
/*
* DEPENDENCY INJECTION
*/
import java.lang.System.Logger;
import java.lang.System.Logger.Level;
+import java.net.InetSocketAddress;
import java.nio.file.Path;
import org.argeo.api.cms.CmsApp;
+import org.argeo.api.cms.CmsDeployment;
import org.argeo.cms.util.OS;
import org.eclipse.swt.events.DisposeListener;
import org.eclipse.swt.widgets.Display;
+import com.sun.net.httpserver.HttpsServer;
/** Creates the SWT {@link Display} in a dedicated thread. */
public class CmsRcpDisplayFactory {
private final static String ARGEO_RCP_URL = "argeo.rcp.url";
/** There is only one display in RCP mode */
- private static Display display;
+ private Display display;
private CmsUiThread uiThread;
private boolean shutdown = false;
+ private CmsDeployment cmsDeployment;
+
public void init() {
uiThread = new CmsUiThread();
uiThread.start();
}
}
- public static Display getDisplay() {
+ public Display getDisplay() {
return display;
}
- public static void openCmsApp(CmsApp cmsApp, String uiName, DisposeListener disposeListener) {
- CmsRcpDisplayFactory.getDisplay().syncExec(() -> {
- CmsRcpApp cmsRcpApp = new CmsRcpApp(uiName);
- cmsRcpApp.setCmsApp(cmsApp, null);
- cmsRcpApp.initRcpApp();
- if (disposeListener != null)
- cmsRcpApp.getShell().addDisposeListener(disposeListener);
+ public void openCmsApp(CmsApp cmsApp, String uiName, DisposeListener disposeListener) {
+ cmsDeployment.getHttpServer().thenAccept((httpServer) -> {
+ getDisplay().syncExec(() -> {
+ CmsRcpApp cmsRcpApp = new CmsRcpApp(uiName);
+ cmsRcpApp.setCmsApp(cmsApp, null);
+ if (httpServer != null) {
+ InetSocketAddress addr = httpServer.getAddress();
+ String scheme = "http";
+ if (httpServer instanceof HttpsServer httpsServer) {
+ if (httpsServer.getHttpsConfigurator() != null)
+ scheme = "https";
+ }
+ String httpServerBase = scheme + "://" + addr.getHostString() + ":" + addr.getPort();
+ cmsRcpApp.setHttpServerBase(httpServerBase);
+ }
+ cmsRcpApp.initRcpApp();
+ if (disposeListener != null)
+ cmsRcpApp.getShell().addDisposeListener(disposeListener);
+ });
});
}
public static Path getUrlRunFile() {
return OS.getRunDir().resolve(CmsRcpDisplayFactory.ARGEO_RCP_URL);
}
+
+ public void setCmsDeployment(CmsDeployment cmsDeployment) {
+ this.cmsDeployment = cmsDeployment;
+ }
+
}
private final static Logger logger = System.getLogger(CmsRcpHttpLauncher.class.getName());
private CompletableFuture<HttpServer> httpServer = new CompletableFuture<>();
+ private CmsRcpDisplayFactory cmsRcpDisplayFactory;
+
public void init() {
}
public void handle(HttpExchange exchange) throws IOException {
String path = exchange.getRequestURI().getPath();
String uiName = path != null ? path.substring(path.lastIndexOf('/') + 1) : "";
- CmsRcpDisplayFactory.openCmsApp(cmsApp, uiName, null);
+ cmsRcpDisplayFactory.openCmsApp(cmsApp, uiName, null);
exchange.sendResponseHeaders(200, -1);
logger.log(Level.DEBUG, "Opened RCP UI " + uiName + " of CMS App /" + contextName);
}
import org.argeo.api.cms.CmsApp;
import org.argeo.cms.dbus.CmsDBus;
+import org.argeo.cms.ui.rcp.CmsRcpDisplayFactory;
public class CmsRcpDBusLauncher {
private CompletableFuture<CmsDBus> cmsDBus = new CompletableFuture<>();
private Map<String, CmsRcpFreeDesktopApplication> apps = new HashMap<>();
+ private CmsRcpDisplayFactory cmsRcpDisplayFactory;
+
public void start() {
}
public void addCmsApp(CmsApp cmsApp, Map<String, String> properties) {
final String contextName = properties.get(CmsApp.CONTEXT_NAME_PROPERTY);
cmsDBus.thenAcceptAsync((cmsDBus) -> {
- CmsRcpFreeDesktopApplication application = new CmsRcpFreeDesktopApplication(cmsDBus, contextName, cmsApp);
+ CmsRcpFreeDesktopApplication application = new CmsRcpFreeDesktopApplication(cmsRcpDisplayFactory, cmsDBus,
+ contextName, cmsApp);
apps.put(contextName, application);
});
}
this.cmsDBus.complete(cmsDBus);
}
+ public void setCmsRcpDisplayFactory(CmsRcpDisplayFactory cmsRcpDisplayFactory) {
+ this.cmsRcpDisplayFactory = cmsRcpDisplayFactory;
+ }
+
}
private DBusConnection dBusConnection;
- public CmsRcpFreeDesktopApplication(CmsDBus cmsDBus, String contextName, CmsApp cmsApp) {
+ private CmsRcpDisplayFactory cmsRcpDisplayFactory;
+
+ public CmsRcpFreeDesktopApplication(CmsRcpDisplayFactory cmsRcpDisplayFactory, CmsDBus cmsDBus, String contextName,
+ CmsApp cmsApp) {
+ this.cmsRcpDisplayFactory = cmsRcpDisplayFactory;
// TODO find a better prefix and/or make it customisable
this.path = "/org/argeo/cms/" + contextName;
this.cmsApp = cmsApp;
// String uiName = path != null ? path.substring(path.lastIndexOf('/') + 1) :
// "";
String uiName = "app";
- CmsRcpDisplayFactory.openCmsApp(cmsApp, uiName, null);
+ cmsRcpDisplayFactory.openCmsApp(cmsApp, uiName, null);
}
@Override
--- /dev/null
+package org.eclipse.rap.fileupload;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+
+public class DiskFileUploadReceiver extends FileUploadReceiver {
+ public File[] getTargetFiles() {
+ return null;
+ }
+
+ @Override
+ public void receive(InputStream stream, FileDetails details) throws IOException {
+ }
+}
return null;
}
+ public void setFilterExtensions(String[] exts) {
+
+ }
}