--- /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-11"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+/bin/
+/target/
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.argeo.entity.api</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
+/MANIFEST.MF
--- /dev/null
+Require-Capability:\
+cms.datamodel;filter:="(name=jcrx)"
+
+Provide-Capability:\
+cms.datamodel; name=entity; cnd=/org/argeo/entity/entity.cnd
--- /dev/null
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.argeo.suite</groupId>
+ <artifactId>core</artifactId>
+ <version>2.1.18-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+ <artifactId>org.argeo.entity.api</artifactId>
+ <name>Entity API</name>
+ <packaging>jar</packaging>
+ <dependencies>
+ <!-- Argeo Commons -->
+ <dependency>
+ <groupId>org.argeo.commons</groupId>
+ <artifactId>org.argeo.enterprise</artifactId>
+ <version>${version.argeo-commons}</version>
+ </dependency>
+ </dependencies>
+</project>
--- /dev/null
+package org.argeo.entity;
+
+/** Constant related to entities, typically used in an OSGi context. */
+public interface EntityConstants {
+ final static String TYPE = "entity.type";
+ final static String DEFAULT_EDITORY_ID = "entity.defaultEditorId";
+
+}
--- /dev/null
+package org.argeo.entity;
+
+import javax.jcr.Node;
+
+/** The definition of an entity, a composite configurable data structure. */
+public interface EntityDefinition {
+ String getEditorId(Node entity);
+
+ String getType();
+}
--- /dev/null
+package org.argeo.entity;
+
+import org.argeo.naming.LdapAttrs;
+
+/** Constants used to name entity structures. */
+public interface EntityNames {
+ @Deprecated
+ final String FORM_BASE = "form";
+ final String SUBMISSIONS_BASE = "submissions";
+ @Deprecated
+ final String TERM = "term";
+ final String NAME = "name";
+
+// final String ENTITY_DEFINITIONS_PATH = "/entity";
+ @Deprecated
+ final String TYPOLOGIES_PATH = "/" + TERM;
+ /** Administrative units. */
+ final String ADM = "adm";
+
+ final String ENTITY_TYPE = "entity:type";
+ // final String ENTITY_UID = "entity:uid";
+ // final String ENTITY_NAME = "entity:name";
+
+ // GENERIC CONCEPTS
+ /** The language which is relevant. */
+ final String XML_LANG = "xml:lang";
+ /** The date which is relevant. */
+ final String ENTITY_DATE = "entity:date";
+ @Deprecated
+ final String ENTITY_RELATED_TO = "entity:relatedTo";
+
+ // DEFAULT FOLDER NAMES
+ final String MEDIA = "media";
+ final String FILES = "files";
+
+ // LDAP-LIKE ENTITIES
+ @Deprecated
+ final String DISPLAY_NAME = LdapAttrs.displayName.property();
+ // Persons
+ @Deprecated
+ final String GIVEN_NAME = LdapAttrs.givenName.property();
+ @Deprecated
+ final String SURNAME = LdapAttrs.sn.property();
+ @Deprecated
+ final String EMAIL = LdapAttrs.mail.property();
+ @Deprecated
+ final String OU = LdapAttrs.ou.property();
+
+ // WGS84
+ final String GEO_LAT = "geo:lat";
+ final String GEO_LONG = "geo:long";
+ final String GEO_ALT = "geo:alt";
+
+ // SVG
+ final String SVG_WIDTH = "svg:width";
+ final String SVG_HEIGHT = "svg:height";
+ final String SVG_LENGTH = "svg:length";
+ final String SVG_UNIT = "svg:unit";
+ final String SVG_DUR = "svg:dur";
+ final String SVG_DIRECTION = "svg:direction";
+}
--- /dev/null
+package org.argeo.entity;
+
+/** Types related to entities. */
+public enum EntityType implements JcrName {
+ // entity
+ entity, local, relatedTo,
+ // typology
+ typologies, terms, term,
+ // form
+ form, formSet, formSubmission,
+ // graphics
+ box,
+ // geography
+ geopoint, bearing,
+ // ldap
+ person, user;
+
+ @Override
+ public String getPrefix() {
+ return prefix();
+ }
+
+ public static String prefix() {
+ return "entity";
+ }
+
+ public String basePath() {
+ return '/' + name();
+ }
+
+ @Override
+ public String getNamespace() {
+ return namespace();
+ }
+
+ public static String namespace() {
+ return "http://www.argeo.org/ns/entity";
+ }
+
+}
--- /dev/null
+package org.argeo.entity;
+
+/** Types related to entities. */
+@Deprecated
+public interface EntityTypes {
+ final static String ENTITY_ENTITY = "entity:entity";
+ final static String ENTITY_DEFINITION = "entity:definition";
+
+ final static String ENTITY_PERSON = "entity:person";
+}
--- /dev/null
+package org.argeo.entity;
+
+import java.util.function.Supplier;
+
+/** Can be applied to {@link Enum}s in order to generate prefixed names. */
+@FunctionalInterface
+public interface JcrName extends Supplier<String> {
+ String name();
+
+ default String getPrefix() {
+ return null;
+ }
+
+ default String getNamespace() {
+ return null;
+ }
+
+ @Override
+ default String get() {
+ String prefix = getPrefix();
+ return prefix != null ? prefix + ":" + name() : name();
+ }
+
+ default String withNamespace() {
+ String namespace = getNamespace();
+ if (namespace == null)
+ throw new UnsupportedOperationException("No namespace is specified for " + getClass());
+ return "{" + namespace + "}" + name();
+ }
+}
--- /dev/null
+package org.argeo.entity;
+
+import java.util.List;
+
+/** Provides optimised access and utilities around terms typologies. */
+public interface TermsManager {
+ List<String> listAllTerms(String typology);
+}
--- /dev/null
+// Standard namespaces
+<xsd = "http://www.w3.org/2001/XMLSchema">
+<h = "http://www.w3.org/1999/xhtml">
+// see https://www.w3.org/2003/01/geo/
+<geo = "http://www.w3.org/2003/01/geo/wgs84_pos#">
+<svg = "http://www.w3.org/2000/svg">
+
+<ldap = "http://www.argeo.org/ns/ldap">
+<entity = 'http://www.argeo.org/ns/entity'>
+
+[entity:entity] > mix:created, mix:referenceable
+mixin
+
+[entity:local] > entity:entity
+mixin
+- entity:type (String) m
+
+[entity:relatedTo]
+mixin
++ entity:relatedTo (nt:address) *
+
+//
+// ENTITY DEFINITION
+//
+//[entity:definition] > entity:composite, mix:created, mix:lastModified, mix:referenceable
+//- entity:type (String) multiple
+
+//[entity:part]
+
+//[entity:reference]
+
+//[entity:composite]
+//orderable
+//+ * (entity:part)
+//+ * (entity:reference)
+//+ * (entity:composite)
+
+//
+// TYPOLOGY
+//
+[entity:typologies]
++ * (entity:terms) = entity:terms
+
+[entity:term]
+orderable
+- name (NAME) m
+- * (*)
++ term (entity:term) = entity:term *
+
+[entity:terms] > mix:referenceable
+orderable
++ term (entity:term) = entity:term *
+
+//
+// FORM
+//
+[entity:form]
+mixin
+
+[entity:formSubmission]
+mixin
+
+[entity:formSet] > mix:title
+mixin
+
+//
+// GRAPHICS
+//
+[entity:box]
+mixin
+- svg:width (DOUBLE)
+- svg:height (DOUBLE)
+- svg:length (DOUBLE)
+- svg:unit (STRING)
+- svg:dur (DOUBLE)
+
+// LDAP-LIKE ENTITIES
+// A real person
+[entity:person] > entity:entity
+mixin
+- ldap:sn (String)
+- ldap:givenName (String)
+- ldap:mail (String) *
+
+[entity:user] > entity:person
+mixin
+- ldap:distinguishedName (String)
+- ldap:uid (String)
+
+// GEOGRAPHY
+[entity:geopoint]
+mixin
+- geo:long (DOUBLE)
+- geo:lat (DOUBLE)
+- geo:alt (DOUBLE)
+
+[entity:bearing]
+mixin
+- svg:direction (DOUBLE)
--- /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-11"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+/bin/
+/target/
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.argeo.entity.core</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
+/MANIFEST.MF
--- /dev/null
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.argeo.suite</groupId>
+ <artifactId>core</artifactId>
+ <version>2.1.18-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+ <artifactId>org.argeo.entity.core</artifactId>
+ <name>Entity Reference Implementation</name>
+ <packaging>jar</packaging>
+ <dependencies>
+ <dependency>
+ <groupId>org.argeo.suite</groupId>
+ <artifactId>org.argeo.entity.api</artifactId>
+ <version>2.1.18-SNAPSHOT</version>
+ </dependency>
+
+ <!-- Argeo Commons -->
+ <dependency>
+ <groupId>org.argeo.commons</groupId>
+ <artifactId>org.argeo.cms</artifactId>
+ <version>${version.argeo-commons}</version>
+ </dependency>
+ </dependencies>
+</project>
--- /dev/null
+package org.argeo.entity.core;
+
+import java.util.Map;
+
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.argeo.api.NodeUtils;
+import org.argeo.entity.EntityConstants;
+import org.argeo.entity.EntityDefinition;
+import org.argeo.jcr.Jcr;
+import org.osgi.framework.BundleContext;
+
+/** An entity definition based on a JCR data structure. */
+public class JcrEntityDefinition implements EntityDefinition {
+ private Repository repository;
+
+ private String type;
+ private String defaultEditoryId;
+
+ public void init(BundleContext bundleContext, Map<String, String> properties) throws RepositoryException {
+ Session adminSession = NodeUtils.openDataAdminSession(repository, null);
+ try {
+ type = properties.get(EntityConstants.TYPE);
+ if (type == null)
+ throw new IllegalArgumentException("Entity type property " + EntityConstants.TYPE + " must be set.");
+ defaultEditoryId = properties.get(EntityConstants.DEFAULT_EDITORY_ID);
+// String definitionPath = EntityNames.ENTITY_DEFINITIONS_PATH + '/' + type;
+// if (!adminSession.itemExists(definitionPath)) {
+// Node entityDefinition = JcrUtils.mkdirs(adminSession, definitionPath, EntityTypes.ENTITY_DEFINITION);
+//// entityDefinition.addMixin(EntityTypes.ENTITY_DEFINITION);
+// adminSession.save();
+// }
+ initJcr(adminSession);
+ } finally {
+ Jcr.logout(adminSession);
+ }
+ }
+
+ /** To be overridden in order to perform additional initialisations. */
+ protected void initJcr(Session adminSession) throws RepositoryException {
+
+ }
+
+ public void destroy(BundleContext bundleContext, Map<String, String> properties) throws RepositoryException {
+
+ }
+
+ @Override
+ public String getEditorId(Node entity) {
+ return defaultEditoryId;
+ }
+
+ @Override
+ public String getType() {
+ return type;
+ }
+
+ protected Repository getRepository() {
+ return repository;
+ }
+
+ public void setRepository(Repository repository) {
+ this.repository = repository;
+ }
+
+ public String toString() {
+ return "Entity Definition " + getType();
+ }
+
+}
--- /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-11"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+/bin/
+/target/
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.argeo.entity.ui</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
+/MANIFEST.MF
--- /dev/null
+Import-Package:\
+org.eclipse.swt,\
+*
\ No newline at end of file
--- /dev/null
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+ .
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.argeo.suite</groupId>
+ <artifactId>core</artifactId>
+ <version>2.1.18-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+ <artifactId>org.argeo.entity.ui</artifactId>
+ <name>Entity UI</name>
+ <packaging>jar</packaging>
+ <dependencies>
+ <dependency>
+ <groupId>org.argeo.suite</groupId>
+ <artifactId>org.argeo.entity.core</artifactId>
+ <version>2.1.18-SNAPSHOT</version>
+ </dependency>
+
+ <!-- Argeo Commons -->
+ <dependency>
+ <groupId>org.argeo.commons</groupId>
+ <artifactId>org.argeo.cms.ui</artifactId>
+ <version>${version.argeo-commons}</version>
+ </dependency>
+
+ <!-- Specific -->
+ <dependency>
+ <groupId>org.argeo.commons</groupId>
+ <artifactId>org.argeo.eclipse.ui.rap</artifactId>
+ <version>2.1.91-SNAPSHOT</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <!-- Eclipse E4 -->
+ <dependency>
+ <groupId>org.argeo.tp</groupId>
+ <artifactId>argeo-tp-rap-e4</artifactId>
+ <version>${version.argeo-tp}</version>
+ <type>pom</type>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+</project>
--- /dev/null
+package org.argeo.entity.ui.forms;
+
+import javax.jcr.Item;
+
+import org.argeo.cms.ui.CmsTheme;
+import org.argeo.cms.ui.util.CmsIcon;
+import org.argeo.cms.ui.viewers.EditablePart;
+import org.argeo.cms.ui.widgets.ContextOverlay;
+import org.argeo.cms.ui.widgets.StyledControl;
+import org.argeo.entity.TermsManager;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.graphics.Color;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swt.widgets.ToolItem;
+
+/** Common logic between single and mutliple terms editable part. */
+public abstract class AbstractTermsPart extends StyledControl implements EditablePart {
+ private static final long serialVersionUID = -5497097995341927710L;
+ protected final TermsManager termsManager;
+ protected final String typology;
+
+ protected final boolean editable;
+
+ private CmsIcon deleteIcon;
+ private CmsIcon addIcon;
+ private CmsIcon cancelIcon;
+
+ private Color highlightColor;
+ private Composite highlight;
+
+ protected final CmsTheme theme;
+
+ public AbstractTermsPart(Composite parent, int style, Item item, TermsManager termsManager,
+ String typology) {
+ super(parent, style, item);
+ this.termsManager = termsManager;
+ this.typology = typology;
+ this.theme = CmsTheme.getCmsTheme(parent);
+ editable = !(SWT.READ_ONLY == (style & SWT.READ_ONLY));
+ highlightColor = parent.getDisplay().getSystemColor(SWT.COLOR_GRAY);
+ }
+
+ protected void createHighlight(Composite block) {
+ highlight = new Composite(block, SWT.NONE);
+ highlight.setBackground(highlightColor);
+ GridData highlightGd = new GridData(SWT.FILL, SWT.FILL, false, false);
+ highlightGd.widthHint = 5;
+ highlightGd.heightHint = 3;
+ highlight.setLayoutData(highlightGd);
+
+ }
+
+ protected String getTermLabel(String name) {
+ return name;
+ }
+
+ protected abstract void refresh(ContextOverlay contextArea, String filter, Text txt);
+
+ protected boolean isTermSelectable(String term) {
+ return true;
+ }
+
+ protected void processTermListLabel(String term, Label label) {
+
+ }
+
+ //
+ // STYLING
+ //
+ public void setDeleteIcon(CmsIcon deleteIcon) {
+ this.deleteIcon = deleteIcon;
+ }
+
+ public void setAddIcon(CmsIcon addIcon) {
+ this.addIcon = addIcon;
+ }
+
+ public void setCancelIcon(CmsIcon cancelIcon) {
+ this.cancelIcon = cancelIcon;
+ }
+
+ protected TermsManager getTermsManager() {
+ return termsManager;
+ }
+
+ protected void styleDelete(ToolItem deleteItem) {
+ if (deleteIcon != null)
+ deleteItem.setImage(deleteIcon.getSmallIcon(theme));
+ else
+ deleteItem.setText("-");
+ }
+
+ protected void styleCancel(ToolItem cancelItem) {
+ if (cancelIcon != null)
+ cancelItem.setImage(cancelIcon.getSmallIcon(theme));
+ else
+ cancelItem.setText("X");
+ }
+
+ protected void styleAdd(ToolItem addItem) {
+ if (addIcon != null)
+ addItem.setImage(addIcon.getSmallIcon(theme));
+ else
+ addItem.setText("+");
+ }
+}
--- /dev/null
+package org.argeo.entity.ui.forms;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.Item;
+
+import org.argeo.cms.ui.forms.FormStyle;
+import org.argeo.cms.ui.util.CmsUiUtils;
+import org.argeo.cms.ui.viewers.EditablePart;
+import org.argeo.cms.ui.widgets.ContextOverlay;
+import org.argeo.eclipse.ui.MouseDoubleClick;
+import org.argeo.eclipse.ui.MouseDown;
+import org.argeo.eclipse.ui.Selected;
+import org.argeo.entity.TermsManager;
+import org.argeo.jcr.Jcr;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.FocusListener;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.layout.RowLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swt.widgets.ToolBar;
+import org.eclipse.swt.widgets.ToolItem;
+
+/** {@link EditablePart} for multiple terms. */
+public class MultiTermsPart extends AbstractTermsPart {
+ private static final long serialVersionUID = -4961135649177920808L;
+
+ public MultiTermsPart(Composite parent, int style, Item item, TermsManager termsManager, String typology) {
+ super(parent, style, item, termsManager, typology);
+ }
+
+ @Override
+ protected Control createControl(Composite box, String style) {
+ Composite placeholder = new Composite(box, SWT.NONE);
+ RowLayout rl = new RowLayout(SWT.HORIZONTAL | SWT.WRAP);
+ placeholder.setLayout(rl);
+ List<String> currentValue = Jcr.getMultiple(getNode(), typology);
+ if (currentValue != null && !currentValue.isEmpty())
+ for (String value : currentValue) {
+ Composite block = new Composite(placeholder, SWT.NONE);
+ block.setLayout(CmsUiUtils.noSpaceGridLayout(3));
+ Label lbl = new Label(block, SWT.SINGLE);
+ String display = getTermLabel(value);
+ lbl.setText(display);
+ CmsUiUtils.style(lbl, style == null ? FormStyle.propertyText.style() : style);
+ if (editable)
+ lbl.addMouseListener((MouseDoubleClick) (e) -> {
+ startEditing();
+ });
+ if (isEditing()) {
+ ToolBar toolBar = new ToolBar(block, SWT.HORIZONTAL);
+ ToolItem deleteItem = new ToolItem(toolBar, SWT.FLAT);
+ styleDelete(deleteItem);
+ deleteItem.addSelectionListener((Selected) (e) -> {
+ // we retrieve them again here because they may have changed
+ List<String> curr = Jcr.getMultiple(getNode(), typology);
+ List<String> newValue = new ArrayList<>();
+ for (String v : curr) {
+ if (!v.equals(value))
+ newValue.add(v);
+ }
+ Jcr.set(getNode(), typology, newValue);
+ Jcr.save(getNode());
+ block.dispose();
+ layout(true, true);
+ });
+
+ }
+ }
+ else {// empty
+ if (editable && !isEditing()) {
+ ToolBar toolBar = new ToolBar(placeholder, SWT.HORIZONTAL);
+ ToolItem addItem = new ToolItem(toolBar, SWT.FLAT);
+ styleAdd(addItem);
+ addItem.addSelectionListener((Selected) (e) -> {
+ startEditing();
+ });
+ }
+ }
+
+ if (isEditing()) {
+ Composite block = new Composite(placeholder, SWT.NONE);
+ block.setLayout(CmsUiUtils.noSpaceGridLayout(3));
+
+ createHighlight(block);
+
+ Text txt = new Text(block, SWT.SINGLE | SWT.BORDER);
+ txt.setLayoutData(CmsUiUtils.fillWidth());
+// txt.setMessage("[new]");
+
+ CmsUiUtils.style(txt, style == null ? FormStyle.propertyText.style() : style);
+
+ ToolBar toolBar = new ToolBar(block, SWT.HORIZONTAL);
+ ToolItem cancelItem = new ToolItem(toolBar, SWT.FLAT);
+ styleCancel(cancelItem);
+ cancelItem.addSelectionListener((Selected) (e) -> {
+ stopEditing();
+ });
+
+ ContextOverlay contextOverlay = new ContextOverlay(txt, SWT.NONE) {
+ private static final long serialVersionUID = -7980078594405384874L;
+
+ @Override
+ protected void onHide() {
+ stopEditing();
+ }
+ };
+ contextOverlay.setLayout(new GridLayout());
+ // filter
+ txt.addModifyListener((e) -> {
+ String filter = txt.getText().toLowerCase();
+ if ("".equals(filter.trim()))
+ filter = null;
+ refresh(contextOverlay, filter, txt);
+ });
+ txt.addFocusListener(new FocusListener() {
+ private static final long serialVersionUID = -6024501573409619949L;
+
+ @Override
+ public void focusLost(FocusEvent event) {
+// if (!contextOverlay.isDisposed() && contextOverlay.isShellVisible())
+// getDisplay().asyncExec(() -> stopEditing());
+ }
+
+ @Override
+ public void focusGained(FocusEvent event) {
+ // txt.setText("");
+ if (!contextOverlay.isDisposed() && !contextOverlay.isShellVisible())
+ refresh(contextOverlay, null, txt);
+ }
+ });
+ layout(new Control[] { txt });
+ // getDisplay().asyncExec(() -> txt.setFocus());
+ }
+ return placeholder;
+ }
+
+ @Override
+ protected void refresh(ContextOverlay contextArea, String filter, Text txt) {
+ CmsUiUtils.clear(contextArea);
+ List<String> terms = termsManager.listAllTerms(typology);
+ List<String> currentValue = Jcr.getMultiple(getNode(), typology);
+ terms: for (String term : terms) {
+ if (currentValue != null && currentValue.contains(term))
+ continue terms;
+ String display = getTermLabel(term);
+ if (filter != null && !display.toLowerCase().contains(filter))
+ continue terms;
+ Label termL = new Label(contextArea, SWT.WRAP);
+ termL.setText(display);
+ processTermListLabel(term, termL);
+ if (isTermSelectable(term))
+ termL.addMouseListener((MouseDown) (e) -> {
+ List<String> newValue = new ArrayList<>();
+ List<String> curr = Jcr.getMultiple(getNode(), typology);
+ if (currentValue != null)
+ newValue.addAll(curr);
+ newValue.add(term);
+ Jcr.set(getNode(), typology, newValue);
+ Jcr.save(getNode());
+ contextArea.hide();
+ stopEditing();
+ });
+ }
+ contextArea.show();
+ }
+
+}
--- /dev/null
+package org.argeo.entity.ui.forms;
+
+import java.util.List;
+
+import javax.jcr.Item;
+
+import org.argeo.cms.ui.forms.FormStyle;
+import org.argeo.cms.ui.util.CmsUiUtils;
+import org.argeo.cms.ui.viewers.EditablePart;
+import org.argeo.cms.ui.widgets.ContextOverlay;
+import org.argeo.eclipse.ui.MouseDoubleClick;
+import org.argeo.eclipse.ui.MouseDown;
+import org.argeo.eclipse.ui.Selected;
+import org.argeo.entity.TermsManager;
+import org.argeo.jcr.Jcr;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.FocusEvent;
+import org.eclipse.swt.events.FocusListener;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swt.widgets.ToolBar;
+import org.eclipse.swt.widgets.ToolItem;
+
+/** {@link EditablePart} for terms. */
+public class SingleTermPart extends AbstractTermsPart {
+ private static final long serialVersionUID = -4961135649177920808L;
+
+ public SingleTermPart(Composite parent, int style, Item item, TermsManager termsManager, String typology) {
+ super(parent, style, item, termsManager, typology);
+ }
+
+ @Override
+ protected Control createControl(Composite box, String style) {
+ if (isEditing()) {
+ Composite block = new Composite(box, SWT.NONE);
+ block.setLayout(CmsUiUtils.noSpaceGridLayout(3));
+
+ createHighlight(block);
+
+ Text txt = new Text(block, SWT.SINGLE | SWT.BORDER);
+ CmsUiUtils.style(txt, style == null ? FormStyle.propertyText.style() : style);
+
+ ToolBar toolBar = new ToolBar(block, SWT.HORIZONTAL);
+ ToolItem deleteItem = new ToolItem(toolBar, SWT.PUSH);
+ styleDelete(deleteItem);
+ deleteItem.addSelectionListener((Selected) (e) -> {
+ Jcr.set(getNode(), typology, null);
+ Jcr.save(getNode());
+ stopEditing();
+ });
+ ToolItem cancelItem = new ToolItem(toolBar, SWT.PUSH);
+ styleCancel(cancelItem);
+ cancelItem.addSelectionListener((Selected) (e) -> {
+ stopEditing();
+ });
+
+ ContextOverlay contextOverlay = new ContextOverlay(txt, SWT.NONE) {
+ private static final long serialVersionUID = -7980078594405384874L;
+
+ @Override
+ protected void onHide() {
+ stopEditing();
+ }
+ };
+ contextOverlay.setLayout(new GridLayout());
+ // filter
+ txt.addModifyListener((e) -> {
+ String filter = txt.getText().toLowerCase();
+ if ("".equals(filter.trim()))
+ filter = null;
+ refresh(contextOverlay, filter, txt);
+ });
+ txt.addFocusListener(new FocusListener() {
+ private static final long serialVersionUID = -6024501573409619949L;
+
+ @Override
+ public void focusLost(FocusEvent event) {
+// if (!contextOverlay.isDisposed() && contextOverlay.isShellVisible())
+// getDisplay().asyncExec(() -> stopEditing());
+ }
+
+ @Override
+ public void focusGained(FocusEvent event) {
+ // txt.setText("");
+ if (!contextOverlay.isDisposed() && !contextOverlay.isShellVisible())
+ refresh(contextOverlay, null, txt);
+ }
+ });
+ layout(new Control[] { block });
+ getDisplay().asyncExec(() -> txt.setFocus());
+ return block;
+ } else {
+ Composite block = new Composite(box, SWT.NONE);
+ block.setLayout(CmsUiUtils.noSpaceGridLayout(2));
+ String currentValue = Jcr.get(getNode(), typology);
+ if (currentValue != null) {
+ Label lbl = new Label(block, SWT.SINGLE);
+ String display = getTermLabel(currentValue);
+ lbl.setText(display);
+ CmsUiUtils.style(lbl, style == null ? FormStyle.propertyText.style() : style);
+
+ lbl.addMouseListener((MouseDoubleClick) (e) -> {
+ startEditing();
+ });
+ } else {
+ ToolBar toolBar = new ToolBar(block, SWT.HORIZONTAL);
+ ToolItem addItem = new ToolItem(toolBar, SWT.FLAT);
+ styleAdd(addItem);
+ addItem.addSelectionListener((Selected) (e) -> {
+ startEditing();
+ });
+ }
+ return block;
+ }
+ }
+
+ @Override
+ protected void refresh(ContextOverlay contextArea, String filter, Text txt) {
+ CmsUiUtils.clear(contextArea);
+ List<String> terms = termsManager.listAllTerms(typology);
+ terms: for (String term : terms) {
+ String display = getTermLabel(term);
+ if (filter != null && !display.toLowerCase().contains(filter))
+ continue terms;
+ Label termL = new Label(contextArea, SWT.WRAP);
+ termL.setText(display);
+ processTermListLabel(term, termL);
+ if (isTermSelectable(term))
+ termL.addMouseListener((MouseDown) (e) -> {
+ Jcr.set(getNode(), typology, term);
+ Jcr.save(getNode());
+ contextArea.hide();
+ stopEditing();
+ });
+ }
+ contextArea.show();
+ // txt.setFocus();
+ }
+
+}
--- /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-11"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+/bin/
+/target/
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.argeo.suite.core</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>
+ <buildCommand>
+ <name>org.eclipse.pde.ds.core.builder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+/MANIFEST.MF
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="Suite Maintenance Service">
+ <implementation class="org.argeo.suite.core.SuiteMaintenanceService"/>
+ <reference bind="setRepository" cardinality="1..1" interface="javax.jcr.Repository" name="Repository" policy="static" target="(cn=entity)"/>
+ <reference bind="setUserTransaction" cardinality="1..1" interface="javax.transaction.UserTransaction" name="UserTransaction" policy="static"/>
+ <reference bind="setUserAdmin" cardinality="1..1" interface="org.osgi.service.useradmin.UserAdmin" name="UserAdmin" policy="static"/>
+</scr:component>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="Suite Terms Manager">
+ <implementation class="org.argeo.suite.core.SuiteTermsManager"/>
+ <reference bind="setRepository" cardinality="1..1" interface="javax.jcr.Repository" name="Repository" policy="static" target="(cn=entity)"/>
+ <service>
+ <provide interface="org.argeo.entity.TermsManager"/>
+ </service>
+</scr:component>
--- /dev/null
+Bundle-ActivationPolicy: lazy
+
+Service-Component:\
+OSGI-INF/termsManager.xml,\
+OSGI-INF/maintenanceService.xml
+
+Import-Package:\
+javax.transaction,\
+org.osgi.service.useradmin,\
+javax.jcr.nodetype,\
+javax.jcr.security,\
+org.argeo.api,\
+org.argeo.entity,\
+*
\ No newline at end of file
--- /dev/null
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ OSGI-INF/
+source.. = src/
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.argeo.suite</groupId>
+ <artifactId>core</artifactId>
+ <version>2.1.18-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+ <artifactId>org.argeo.suite.core</artifactId>
+ <name>Suite Core</name>
+ <packaging>jar</packaging>
+ <dependencies>
+ <dependency>
+ <groupId>org.argeo.suite</groupId>
+ <artifactId>org.argeo.entity.core</artifactId>
+ <version>2.1.18-SNAPSHOT</version>
+ </dependency>
+
+ <!-- Argeo Commons -->
+ <dependency>
+ <groupId>org.argeo.commons</groupId>
+ <artifactId>org.argeo.cms</artifactId>
+ <version>${version.argeo-commons}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.argeo.commons</groupId>
+ <artifactId>org.argeo.maintenance</artifactId>
+ <version>${version.argeo-commons}</version>
+ </dependency>
+ </dependencies>
+</project>
--- /dev/null
+package org.argeo.suite;
+
+import java.util.Map;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+/**
+ * A container for an object whose relevance can be ranked. Typically used in an
+ * OSGi context with the service.ranking property.
+ */
+public class RankedObject<T> {
+ private final static Log log = LogFactory.getLog(RankedObject.class);
+
+ private final static String SERVICE_RANKING = "service.ranking";
+// private final static String SERVICE_ID = "service.id";
+
+ private T object;
+ private Map<String, Object> properties;
+ private final Long rank;
+
+ public RankedObject(T object, Map<String, Object> properties) {
+ this(object, properties, extractRanking(properties));
+ }
+
+ public RankedObject(T object, Map<String, Object> properties, Long rank) {
+ super();
+ this.object = object;
+ this.properties = properties;
+ this.rank = rank;
+ }
+
+ private static Long extractRanking(Map<String, Object> properties) {
+ if (properties == null)
+ return 0l;
+ if (properties.containsKey(SERVICE_RANKING))
+ return Long.valueOf(properties.get(SERVICE_RANKING).toString());
+// else if (properties.containsKey(SERVICE_ID))
+// return (Long) properties.get(SERVICE_ID);
+ else
+ return 0l;
+ }
+
+ public T get() {
+ return object;
+ }
+
+ public Map<String, Object> getProperties() {
+ return properties;
+ }
+
+ public Long getRank() {
+ return rank;
+ }
+
+ @Override
+ public int hashCode() {
+ return object.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof RankedObject))
+ return false;
+ RankedObject<?> other = (RankedObject<?>) obj;
+ return rank.equals(other.rank) && object.equals(other.object);
+ }
+
+ @Override
+ public String toString() {
+ return object.getClass().getName() + " with rank " + rank;
+ }
+
+ public static <K, T> RankedObject<T> putIfHigherRank(Map<K, RankedObject<T>> map, K key, T object,
+ Map<String, Object> properties) {
+ RankedObject<T> rankedObject = new RankedObject<>(object, properties);
+ if (!map.containsKey(key)) {
+ map.put(key, rankedObject);
+ if (log.isTraceEnabled())
+ log.trace(
+ "Added " + key + " as " + object.getClass().getName() + " with rank " + rankedObject.getRank());
+ return rankedObject;
+ } else {
+ RankedObject<T> current = map.get(key);
+ if (current.getRank() <= rankedObject.getRank()) {
+ map.put(key, rankedObject);
+ if (log.isTraceEnabled())
+ log.trace("Replaced " + key + " by " + object.getClass().getName() + " with rank "
+ + rankedObject.getRank());
+ return rankedObject;
+ } else {
+ return current;
+ }
+ }
+
+ }
+
+}
--- /dev/null
+package org.argeo.suite;
+
+import java.util.Map;
+
+/**
+ * Key used to classify and filter available components (typically provided by
+ * OSGi services).
+ */
+@Deprecated
+public class RankingKey implements Comparable<RankingKey> {
+ public final static String SERVICE_PID = "service.pid";
+ public final static String SERVICE_ID = "service.id";
+ public final static String SERVICE_RANKING = "service.ranking";
+ public final static String DATA_TYPE = "data.type";
+
+ private String pid;
+ private Integer ranking = 0;
+ private Long id = 0l;
+ private String dataType;
+ private String dataPath;
+
+ public RankingKey(String pid, Integer ranking, Long id, String dataType, String dataPath) {
+ super();
+ this.pid = pid;
+ this.ranking = ranking;
+ this.id = id;
+ this.dataType = dataType;
+ this.dataPath = dataPath;
+ }
+
+ public RankingKey(Map<String, Object> properties) {
+ this.pid = properties.containsKey(SERVICE_PID) ? properties.get(SERVICE_PID).toString() : null;
+ this.ranking = properties.containsKey(SERVICE_RANKING)
+ ? Integer.parseInt(properties.get(SERVICE_RANKING).toString())
+ : 0;
+ this.id = properties.containsKey(SERVICE_ID) ? (Long) properties.get(SERVICE_ID) : null;
+
+ // Argeo specific
+ this.dataType = properties.containsKey(DATA_TYPE) ? properties.get(DATA_TYPE).toString() : null;
+ }
+
+ @Override
+ public int hashCode() {
+ Integer result = 0;
+ if (pid != null)
+ result = +pid.hashCode();
+ if (ranking != null)
+ result = +ranking;
+ if (dataType != null)
+ result = +dataType.hashCode();
+ return result;
+ }
+
+ @Override
+ protected Object clone() throws CloneNotSupportedException {
+ return new RankingKey(pid, ranking, id, dataType, dataPath);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("");
+ if (pid != null)
+ sb.append(pid);
+ if (ranking != null && ranking != 0)
+ sb.append(' ').append(ranking);
+ if (dataType != null)
+ sb.append(' ').append(dataType);
+ return sb.toString();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (!(obj instanceof RankingKey))
+ return false;
+ RankingKey other = (RankingKey) obj;
+ return equalsOrBothNull(pid, other.pid) && equalsOrBothNull(ranking, other.ranking)
+ && equalsOrBothNull(id, other.id) && equalsOrBothNull(dataType, other.dataType)
+ && equalsOrBothNull(dataPath, other.dataPath);
+ }
+
+ @Override
+ public int compareTo(RankingKey o) {
+ if (pid != null && o.pid != null) {
+ if (pid.equals(o.pid)) {
+ if (ranking.equals(o.ranking))
+ if (id != null && o.id != null)
+ return id.compareTo(o.id);
+ else
+ return 0;
+ else
+ return ranking.compareTo(o.ranking);
+ } else {
+ return pid.compareTo(o.pid);
+ }
+
+ } else {
+ if (dataType != null && o.dataType != null) {
+ if (dataType.equals(o.dataType)) {
+ // TODO factorise
+ if (ranking.equals(o.ranking))
+ if (id != null && o.id != null)
+ return id.compareTo(o.id);
+ else
+ return 0;
+ else
+ return ranking.compareTo(o.ranking);
+ } else {
+ return dataPath.compareTo(o.dataType);
+ }
+ }
+ }
+ return -1;
+ }
+
+ public String getPid() {
+ return pid;
+ }
+
+ public Integer getRanking() {
+ return ranking;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public String getDataType() {
+ return dataType;
+ }
+
+ public String getDataPath() {
+ return dataPath;
+ }
+
+ public static RankingKey minPid(String pid) {
+ return new RankingKey(pid, Integer.MIN_VALUE, null, null, null);
+ }
+
+ public static RankingKey maxPid(String pid) {
+ return new RankingKey(pid, Integer.MAX_VALUE, null, null, null);
+ }
+
+ public static RankingKey minDataType(String dataType) {
+ return new RankingKey(null, Integer.MIN_VALUE, null, dataType, null);
+ }
+
+ public static RankingKey maxDataType(String dataType) {
+ return new RankingKey(null, Integer.MAX_VALUE, null, dataType, null);
+ }
+
+ private static boolean equalsOrBothNull(Object o1, Object o2) {
+ if (o1 == null && o2 == null)
+ return true;
+ if (o1 == null && o2 != null)
+ return false;
+ if (o1 != null && o2 == null)
+ return false;
+ return o2.equals(o1);
+ }
+}
--- /dev/null
+package org.argeo.suite;
+
+import org.argeo.api.NodeConstants;
+import org.argeo.naming.Distinguished;
+import org.argeo.naming.LdapAttrs;
+
+/** Office specific roles used in the code */
+public enum SuiteRole implements Distinguished {
+ coworker, manager;
+
+ public String getRolePrefix() {
+ return "org.argeo.suite";
+ }
+
+ public String dn() {
+ return new StringBuilder(LdapAttrs.cn.name()).append("=").append(getRolePrefix()).append(".").append(name())
+ .append(",").append(NodeConstants.ROLES_BASEDN).toString();
+ }
+}
--- /dev/null
+package org.argeo.suite;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.security.Privilege;
+import javax.naming.ldap.LdapName;
+import javax.security.auth.x500.X500Principal;
+
+import org.argeo.api.NodeConstants;
+import org.argeo.cms.auth.CmsSession;
+import org.argeo.entity.EntityType;
+import org.argeo.jackrabbit.security.JackrabbitSecurityUtils;
+import org.argeo.jcr.JcrException;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.naming.LdapAttrs;
+
+/** Utilities around the Argeo Suite APIs. */
+public class SuiteUtils {
+
+ public static String getUserNodePath(LdapName userDn) {
+ String uid = userDn.getRdn(userDn.size() - 1).getValue().toString();
+ return EntityType.user.basePath() + '/' + uid;
+ }
+
+ public static Node getOrCreateUserNode(Session adminSession, LdapName userDn) {
+ try {
+ Node usersBase = adminSession.getNode(EntityType.user.basePath());
+ String uid = userDn.getRdn(userDn.size() - 1).getValue().toString();
+ Node userNode;
+ if (!usersBase.hasNode(uid)) {
+ userNode = usersBase.addNode(uid, NodeType.NT_UNSTRUCTURED);
+ userNode.addMixin(EntityType.user.get());
+ userNode.addMixin(NodeType.MIX_CREATED);
+ userNode.setProperty(LdapAttrs.distinguishedName.property(), userDn.toString());
+ userNode.setProperty(LdapAttrs.uid.property(), uid);
+ adminSession.save();
+ JackrabbitSecurityUtils.denyPrivilege(adminSession, userNode.getPath(), SuiteRole.coworker.dn(),
+ Privilege.JCR_READ);
+ JcrUtils.addPrivilege(adminSession, userNode.getPath(), new X500Principal(userDn.toString()).getName(),
+ Privilege.JCR_READ);
+ JcrUtils.addPrivilege(adminSession, userNode.getPath(), NodeConstants.ROLE_USER_ADMIN,
+ Privilege.JCR_ALL);
+ } else {
+ userNode = usersBase.getNode(uid);
+ }
+ return userNode;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot create user node for " + userDn, e);
+ }
+ }
+
+ public static Node getCmsSessionNode(Session session, CmsSession cmsSession) {
+ try {
+ return session.getNode(getUserNodePath(cmsSession.getUserDn()) + '/' + cmsSession.getUuid().toString());
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot get session dir for " + cmsSession, e);
+ }
+ }
+
+ public static Node getOrCreateCmsSessionNode(Session adminSession, CmsSession cmsSession) {
+ try {
+ LdapName userDn = cmsSession.getUserDn();
+// String uid = userDn.get(userDn.size() - 1);
+ Node userNode = getOrCreateUserNode(adminSession, userDn);
+// if (!usersBase.hasNode(uid)) {
+// userNode = usersBase.addNode(uid, NodeType.NT_UNSTRUCTURED);
+// userNode.addMixin(EntityType.user.get());
+// userNode.addMixin(NodeType.MIX_CREATED);
+// usersBase.setProperty(LdapAttrs.uid.property(), uid);
+// usersBase.setProperty(LdapAttrs.distinguishedName.property(), userDn.toString());
+// adminSession.save();
+// } else {
+// userNode = usersBase.getNode(uid);
+// }
+ String cmsSessionUuid = cmsSession.getUuid().toString();
+ Node cmsSessionNode;
+ if (!userNode.hasNode(cmsSessionUuid)) {
+ cmsSessionNode = userNode.addNode(cmsSessionUuid, NodeType.NT_UNSTRUCTURED);
+ cmsSessionNode.addMixin(NodeType.MIX_CREATED);
+ adminSession.save();
+ JcrUtils.addPrivilege(adminSession, cmsSessionNode.getPath(), cmsSession.getUserRole(),
+ Privilege.JCR_ALL);
+ } else {
+ cmsSessionNode = userNode.getNode(cmsSessionUuid);
+ }
+ return cmsSessionNode;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot create session dir for " + cmsSession, e);
+ }
+ }
+
+ /** Singleton. */
+ private SuiteUtils() {
+
+ }
+
+}
--- /dev/null
+package org.argeo.suite.core;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.ImportUUIDBehavior;
+import javax.jcr.ItemExistsException;
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.entity.EntityType;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.maintenance.AbstractMaintenanceService;
+
+/** Base for custom initialisations. */
+public abstract class CustomMaintenanceService extends AbstractMaintenanceService {
+ private final static Log log = LogFactory.getLog(AbstractMaintenanceService.class);
+
+ protected List<String> getTypologies() {
+ return new ArrayList<>();
+ }
+
+ protected String getTypologiesLoadBase() {
+ return "/sys/terms";
+ }
+
+ protected void loadTypologies(Node customBaseNode) throws RepositoryException, IOException {
+ List<String> typologies = getTypologies();
+ if (!typologies.isEmpty()) {
+ Node termsBase = JcrUtils.getOrAdd(customBaseNode, EntityType.terms.name(), EntityType.typologies.get());
+ for (String terms : typologies) {
+ loadTerms(termsBase, terms);
+ }
+ termsBase.getSession().save();
+ }
+ }
+
+ protected void loadTerms(Node termsBase, String name) throws IOException, RepositoryException {
+ try {
+ if (termsBase.hasNode(name))
+ return;
+
+ String termsLoadPath = getTypologiesLoadBase() + '/' + name + ".xml";
+ URL termsUrl = getClass().getClassLoader().getResource(termsLoadPath);
+ if (termsUrl == null)
+ throw new IllegalArgumentException("Terms '" + name + "' not found.");
+ try (InputStream in = termsUrl.openStream()) {
+ termsBase.getSession().importXML(termsBase.getPath(), in,
+ ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
+ } catch (ItemExistsException e) {
+ log.warn("Terms " + name + " exists with another UUID, removing it...");
+ termsBase.getNode(name).remove();
+ try (InputStream in = termsUrl.openStream()) {
+ termsBase.getSession().importXML(termsBase.getPath(), in,
+ ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING);
+ }
+ }
+ if (log.isDebugEnabled())
+ log.debug("Terms '" + name + "' loaded.");
+ termsBase.getSession().save();
+ } catch (RepositoryException | IOException e) {
+ log.error("Cannot load terms '" + name + "': " + e.getMessage());
+ throw e;
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.suite.core;
+
+import java.io.IOException;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+import javax.jcr.security.Privilege;
+
+import org.argeo.api.NodeConstants;
+import org.argeo.entity.EntityType;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.maintenance.AbstractMaintenanceService;
+
+/** Initialises an Argeo Suite backend. */
+public class SuiteMaintenanceService extends AbstractMaintenanceService {
+
+ @Override
+ public boolean prepareJcrTree(Session adminSession) throws RepositoryException, IOException {
+ boolean modified = false;
+ Node rootNode = adminSession.getRootNode();
+ if (!rootNode.hasNode(EntityType.user.name())) {
+ rootNode.addNode(EntityType.user.name(), NodeType.NT_UNSTRUCTURED);
+ modified = true;
+ }
+ if (modified)
+ adminSession.save();
+ return modified;
+ }
+
+ @Override
+ public void configurePrivileges(Session adminSession) throws RepositoryException {
+ JcrUtils.addPrivilege(adminSession, EntityType.user.basePath(), NodeConstants.ROLE_USER_ADMIN,
+ Privilege.JCR_ALL);
+ //JcrUtils.addPrivilege(adminSession, "/", SuiteRole.coworker.dn(), Privilege.JCR_READ);
+ }
+
+}
--- /dev/null
+package org.argeo.suite.core;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A single term. Helper to optimise {@link SuiteTermsManager} implementation.
+ */
+class SuiteTerm {
+ private final String name;
+ private final String relativePath;
+ private final SuiteTypology typology;
+ private final String id;
+
+ private final SuiteTerm parentTerm;
+ private final List<SuiteTerm> subTerms = new ArrayList<>();
+
+ SuiteTerm(SuiteTypology typology, String relativePath, SuiteTerm parentTerm) {
+ this.typology = typology;
+ this.parentTerm = parentTerm;
+ this.relativePath = relativePath;
+ int index = relativePath.lastIndexOf('/');
+ if (index > 0) {
+ this.name = relativePath.substring(index);
+ } else {
+ this.name = relativePath;
+ }
+ id = typology.getName() + '/' + relativePath;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public String getRelativePath() {
+ return relativePath;
+ }
+
+ SuiteTypology getTypology() {
+ return typology;
+ }
+
+ public String getId() {
+ return id;
+ }
+
+ List<SuiteTerm> getSubTerms() {
+ return subTerms;
+ }
+
+ SuiteTerm getParentTerm() {
+ return parentTerm;
+ }
+
+}
--- /dev/null
+package org.argeo.suite.core;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+
+import org.argeo.api.NodeConstants;
+import org.argeo.api.NodeUtils;
+import org.argeo.entity.EntityNames;
+import org.argeo.entity.EntityType;
+import org.argeo.entity.TermsManager;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrException;
+
+/** Argeo Suite implementation of terms manager. */
+public class SuiteTermsManager implements TermsManager {
+ private final Map<String, SuiteTerm> terms = new HashMap<>();
+ private final Map<String, SuiteTypology> typologies = new HashMap<>();
+
+ // JCR
+ private Repository repository;
+ private Session adminSession;
+
+ public void init() {
+ adminSession = NodeUtils.openDataAdminSession(repository, NodeConstants.SYS_WORKSPACE);
+ }
+
+ @Override
+ public List<String> listAllTerms(String typology) {
+ List<String> res = new ArrayList<>();
+ SuiteTypology t = getTypology(typology);
+ for (SuiteTerm term : t.getAllTerms()) {
+ res.add(term.getId());
+ }
+ return res;
+ }
+
+ SuiteTypology getTypology(String typology) {
+ SuiteTypology t = typologies.get(typology);
+ if (t == null) {
+ Node termsNode = Jcr.getNode(adminSession, "SELECT * FROM [{0}] WHERE NAME()=\"{1}\"",
+ EntityType.terms.get(), typology);
+ if (termsNode == null)
+ throw new IllegalArgumentException("Typology " + typology + " not found.");
+ t = loadTypology(termsNode);
+ }
+ return t;
+ }
+
+ SuiteTypology loadTypology(Node termsNode) {
+ try {
+ SuiteTypology typology = new SuiteTypology(termsNode);
+ for (Node termNode : Jcr.iterate(termsNode.getNodes())) {
+ if (termNode.isNodeType(EntityType.term.get())) {
+ SuiteTerm term = loadTerm(typology, termNode, null);
+ if (!term.getSubTerms().isEmpty())
+ typology.markNotFlat();
+ typology.getSubTerms().add(term);
+ }
+ }
+ typologies.put(typology.getName(), typology);
+ return typology;
+ } catch (RepositoryException e) {
+ throw new JcrException("Cannot load typology from " + termsNode, e);
+ }
+ }
+
+ SuiteTerm loadTerm(SuiteTypology typology, Node termNode, SuiteTerm parentTerm) throws RepositoryException {
+ String name = termNode.getProperty(EntityNames.NAME).getString();
+ String relativePath = parentTerm == null ? name : parentTerm.getRelativePath() + '/' + name;
+ SuiteTerm term = new SuiteTerm(typology, relativePath, parentTerm);
+ terms.put(term.getId(), term);
+ for (Node subTermNode : Jcr.iterate(termNode.getNodes())) {
+ if (termNode.isNodeType(EntityType.term.get())) {
+ SuiteTerm subTerm = loadTerm(typology, subTermNode, term);
+ term.getSubTerms().add(subTerm);
+ }
+ }
+ return term;
+ }
+
+ public void destroy() {
+ Jcr.logout(adminSession);
+ }
+
+ public void setRepository(Repository repository) {
+ this.repository = repository;
+ }
+
+}
--- /dev/null
+package org.argeo.suite.core;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.jcr.Node;
+
+import org.argeo.jcr.Jcr;
+
+/** A typology. Helper to optimise {@link SuiteTermsManager} implementation. */
+class SuiteTypology {
+ private final String name;
+ private final Node node;
+ private boolean isFlat = true;
+
+ private final List<SuiteTerm> subTerms = new ArrayList<>();
+
+ public SuiteTypology(Node node) {
+ this.node = node;
+ this.name = Jcr.getName(this.node);
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public Node getNode() {
+ return node;
+ }
+
+ void markNotFlat() {
+ if (isFlat)
+ isFlat = false;
+ }
+
+ public boolean isFlat() {
+ return isFlat;
+ }
+
+ public List<SuiteTerm> getSubTerms() {
+ return subTerms;
+ }
+
+ public List<SuiteTerm> getAllTerms() {
+ if (isFlat)
+ return subTerms;
+ else {
+ List<SuiteTerm> terms = new ArrayList<>();
+ for (SuiteTerm subTerm : subTerms) {
+ terms.add(subTerm);
+ collectSubTerms(terms, subTerm);
+ }
+ return terms;
+ }
+ }
+
+ private void collectSubTerms(List<SuiteTerm> terms, SuiteTerm term) {
+ for (SuiteTerm subTerm : term.getSubTerms()) {
+ terms.add(subTerm);
+ collectSubTerms(terms, subTerm);
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.suite.library;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+
+import org.argeo.util.DigestUtils;
+import org.xml.sax.Attributes;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.XMLReader;
+import org.xml.sax.helpers.DefaultHandler;
+
+/** Parses a .docx document, trying its best to extract text and table data. */
+public class DocxExtractor {
+ final static String T = "t";
+ final static String TC = "tc";
+ final static String TR = "tr";
+ final static String TBL = "tbl";
+ final static String P = "p";
+ static boolean debug = false;
+
+ final static String PROOF_ERR = "proofErr";
+ final static String TYPE = "type";
+ final static String SPELL_START = "spellStart";
+ final static String SPELL_END = "spellEnd";
+
+ protected List<Tbl> tables = new ArrayList<>();
+ protected List<String> text = new ArrayList<>();
+ protected Map<String, byte[]> media = new TreeMap<>();
+ private Set<String> mediaDigests = new HashSet<>();
+
+ protected void processTextItem(List<String> lines, String str) {
+ lines.add(str);
+ }
+
+ protected boolean skipMedia(String digest) {
+ return false;
+ }
+
+ class DocxHandler extends DefaultHandler {
+
+ private StringBuilder buffer = new StringBuilder();
+ private Tbl currentTbl = null;
+
+ boolean inSpellErr = false;
+ boolean inParagraph = false;
+
+ @Override
+ public void startElement(String uri, String name, String qName, Attributes attributes) throws SAXException {
+ // System.out.println(localName + " " + qName + " " + uri.hashCode());
+ if (P.equals(name)) {
+ if (debug && currentTbl == null)
+ System.out.println("# START PARA");
+ inParagraph = true;
+ } else if (PROOF_ERR.equals(name)) {
+ String type = attributes.getValue(uri, TYPE);
+ if (SPELL_START.equals(type))
+ inSpellErr = true;
+ else if (SPELL_END.equals(type))
+ inSpellErr = false;
+
+ } else if (TBL.equals(name)) {
+ if (currentTbl != null) {
+ Tbl childTbl = new Tbl();
+ childTbl.parentTbl = currentTbl;
+ currentTbl = childTbl;
+ // throw new IllegalStateException("Already an active table");
+ } else {
+ currentTbl = new Tbl();
+ }
+ }
+ }
+
+ @Override
+ public void endElement(String uri, String name, String qName) throws SAXException {
+ if (name.equals(T)) {
+// if (inSpellErr) {
+// // do not reset the buffer
+// return;
+// }
+
+ if (currentTbl != null) {
+ currentTbl.appendText(buffer.toString());
+ } else {
+ String str = buffer.toString();
+ // replace NO-BREAK SPACE by regular space.
+ str = str.replace('\u00A0', ' ');
+ str = str.strip();
+ if (!"".equals(str)) {
+ processTextItem(text, str);
+ }
+ }
+ } else if (name.equals(P)) {
+ if (debug && currentTbl == null)
+ System.out.println("# END PARA");
+ if (currentTbl != null) {
+ currentTbl.currentRow.current.text.append('\n');
+ } else {
+
+ }
+ inParagraph = false;
+ } else if (name.equals(TC)) {
+ if (currentTbl != null)
+ currentTbl.closeColumn();
+ } else if (name.equals(TR)) {
+ if (currentTbl != null)
+ currentTbl.closeRow();
+ } else if (name.equals(TBL)) {
+ if (currentTbl != null) {
+ tables.add(currentTbl);
+ if (currentTbl.parentTbl != null)
+ currentTbl = currentTbl.parentTbl;
+ else
+ currentTbl = null;
+ } else {
+ throw new IllegalStateException("Closing a table while none was open.");
+ }
+ }
+ // reset the buffer
+ buffer.setLength(0);
+ }
+
+ @Override
+ public void characters(char[] ch, int start, int length) throws SAXException {
+ buffer.append(ch, start, length);
+ }
+
+ }
+
+ public static class Tbl {
+ Tbl parentTbl = null;
+ Tr currentRow = new Tr();
+ List<Tr> rows = new ArrayList<>();
+
+ void appendText(String str) {
+ currentRow.current.text.append(str);
+ }
+
+ void closeColumn() {
+ currentRow.columns.add(currentRow.current);
+ currentRow.current = new Tc();
+ }
+
+ void closeRow() {
+ rows.add(currentRow);
+ currentRow = new Tr();
+ }
+
+ public List<Tr> getRows() {
+ return rows;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ for (Tr tr : rows) {
+ String txt = tr.toString();
+ sb.append(txt).append('\n');
+ }
+ return sb.toString();
+ }
+ }
+
+ public static class Tr {
+ Tc current = new Tc();
+ List<Tc> columns = new ArrayList<>();
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ for (Tc tc : columns) {
+ sb.append("\"").append(tc.toString()).append("\"").append(',');
+ }
+ return sb.toString();
+ }
+
+ public List<Tc> getColumns() {
+ return columns;
+ }
+
+ }
+
+ public static class Tc {
+ StringBuilder text = new StringBuilder();
+
+ @Override
+ public String toString() {
+ return text.toString().trim();
+ }
+
+ }
+
+ protected void parse(Reader in) {
+ try {
+ SAXParserFactory spf = SAXParserFactory.newInstance();
+ spf.setNamespaceAware(true);
+ SAXParser saxParser = spf.newSAXParser();
+ XMLReader xmlReader = saxParser.getXMLReader();
+ xmlReader.setContentHandler(new DocxHandler());
+ xmlReader.parse(new InputSource(in));
+ } catch (ParserConfigurationException | SAXException | IOException e) {
+ throw new RuntimeException("Cannot parse document", e);
+ }
+ }
+
+ public List<String> getText() {
+ return text;
+ }
+
+ public List<Tbl> getTables() {
+ return tables;
+ }
+
+ public Map<String, byte[]> getMedia() {
+ return media;
+ }
+
+ public void load(ZipInputStream zIn) {
+ try {
+ ZipEntry entry = null;
+ while ((entry = zIn.getNextEntry()) != null) {
+ if ("word/document.xml".equals(entry.getName())) {
+ try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+ byte[] buffer = new byte[2048];
+ int len = 0;
+ while ((len = zIn.read(buffer)) > 0) {
+ out.write(buffer, 0, len);
+ }
+ try (Reader reader = new InputStreamReader(new ByteArrayInputStream(out.toByteArray()),
+ StandardCharsets.UTF_8)) {
+ parse(reader);
+ }
+ }
+ } else if (entry.getName().startsWith("word/media")) {
+ String fileName = entry.getName().substring(entry.getName().lastIndexOf('/') + 1);
+ int dotIndex = fileName.lastIndexOf('.');
+ String ext = fileName.substring(dotIndex + 1).toLowerCase();
+ // we ignore .jfif
+ if ("jpeg".equals(ext))
+ ext = "jpg";
+ fileName = fileName.substring(0, dotIndex) + "." + ext;
+ switch (ext) {
+ case "png":
+ case "jpg":
+ case "gif":
+ case "bmp":
+ case "tiff":
+ try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+ byte[] buffer = new byte[2048];
+ int len = 0;
+ while ((len = zIn.read(buffer)) > 0) {
+ out.write(buffer, 0, len);
+ }
+ byte[] bytes = out.toByteArray();
+ String digest = DigestUtils.digest(DigestUtils.MD5, bytes);
+ if (skipMedia(digest))
+ break;
+ if (!mediaDigests.contains(digest)) {
+ media.put(fileName, bytes);
+ mediaDigests.add(digest);
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ } else {
+ // System.out.println(entry.getName());
+ }
+ }
+ } catch (IOException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+ // throw new IllegalArgumentException("No document.xml found");
+
+ }
+
+// public static Reader extractDocumentXml(ZipInputStream zIn) throws IOException {
+// ZipEntry entry = null;
+// while ((entry = zIn.getNextEntry()) != null) {
+// if ("word/document.xml".equals(entry.getName())) {
+// try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
+// byte[] buffer = new byte[2048];
+// int len = 0;
+// while ((len = zIn.read(buffer)) > 0) {
+// out.write(buffer, 0, len);
+// }
+// return new InputStreamReader(new ByteArrayInputStream(out.toByteArray()), StandardCharsets.UTF_8);
+// }
+// } else {
+// System.out.println(entry.getName());
+// }
+// }
+// throw new IllegalArgumentException("No document.xml found");
+// }
+
+// protected static ZipInputStream openAsZip(String file) throws IOException {
+// ZipInputStream zIn;
+// Path path = Paths.get(file);
+// zIn = new ZipInputStream(Files.newInputStream(path));
+// return zIn;
+// }
+
+ public static void main(String[] args) throws IOException {
+ if (args.length == 0)
+ throw new IllegalArgumentException("Provide a file path");
+ Path p = Paths.get(args[0]);
+
+ DocxExtractor importer = new DocxExtractor();
+ try (ZipInputStream zIn = new ZipInputStream(Files.newInputStream(p))) {
+ importer.load(zIn);
+ }
+ // display
+ System.out.println("## TEXT");
+ for (int i = 0; i < importer.text.size(); i++) {
+ String str = importer.text.get(i);
+ System.out.println(str);
+ }
+
+ System.out.println("\n");
+
+ for (int i = 0; i < importer.tables.size(); i++) {
+ Tbl tbl = importer.tables.get(i);
+ System.out.println("## TABLE " + i);
+ System.out.println(tbl);
+ }
+
+ System.out.println("## MEDIA");
+ for (String fileName : importer.media.keySet()) {
+ int sizeKb = importer.media.get(fileName).length / 1024;
+ System.out.println(fileName + " " + sizeKb + " kB");
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.suite.util;
+
+import java.text.DateFormat;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryManager;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.apache.jackrabbit.util.ISO9075;
+
+/** Ease XPath generation for JCR requests */
+public class XPathUtils {
+ private final static Log log = LogFactory.getLog(XPathUtils.class);
+
+ private final static String QUERY_XPATH = "xpath";
+
+ public static String descendantFrom(String parentPath) {
+ if (notEmpty(parentPath)) {
+ if ("/".equals(parentPath))
+ parentPath = "";
+ // Hardcoded dependency to Jackrabbit. Remove
+ String result = "/jcr:root" + ISO9075.encodePath(parentPath);
+ if (log.isTraceEnabled()) {
+ String result2 = "/jcr:root" + parentPath;
+ if (!result2.equals(result))
+ log.warn("Encoded Path " + result2 + " --> " + result);
+ }
+ return result;
+ } else
+ return "";
+ }
+
+ public static String localAnd(String... conditions) {
+ StringBuilder builder = new StringBuilder();
+ for (String condition : conditions) {
+ if (notEmpty(condition)) {
+ builder.append(" ").append(condition).append(" and ");
+ }
+ }
+ if (builder.length() > 3)
+ return builder.substring(0, builder.length() - 4);
+ else
+ return "";
+ }
+
+ public static String xPathNot(String condition) {
+ if (notEmpty(condition))
+ return "not(" + condition + ")";
+ else
+ return "";
+ }
+
+ public static String getFreeTextConstraint(String filter) throws RepositoryException {
+ StringBuilder builder = new StringBuilder();
+ if (notEmpty(filter)) {
+ String[] strs = filter.trim().split(" ");
+ for (String token : strs) {
+ builder.append("jcr:contains(.,'*" + encodeXPathStringValue(token) + "*') and ");
+ }
+ return builder.substring(0, builder.length() - 4);
+ }
+ return "";
+ }
+
+ public static String getPropertyContains(String propertyName, String filter) throws RepositoryException {
+ if (notEmpty(filter))
+ return "jcr:contains(@" + propertyName + ",'*" + encodeXPathStringValue(filter) + "*')";
+ return "";
+ }
+
+ private final static DateFormat jcrRefFormatter = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss.SSS'+02:00'");
+
+ /**
+ * @param propertyName
+ * @param calendar the reference date
+ * @param lowerOrGreater "<", ">" TODO validate ">="
+ * @return
+ * @throws RepositoryException
+ */
+ public static String getPropertyDateComparaison(String propertyName, Calendar cal, String lowerOrGreater)
+ throws RepositoryException {
+ if (cal != null) {
+ String jcrDateStr = jcrRefFormatter.format(cal.getTime());
+
+ // jcrDateStr = "2015-08-03T05:00:03:000Z";
+ String result = "@" + propertyName + " " + lowerOrGreater + " xs:dateTime('" + jcrDateStr + "')";
+ return result;
+ }
+ return "";
+ }
+
+ public static String getPropertyEquals(String propertyName, String value) {
+ if (notEmpty(value))
+ return "@" + propertyName + "='" + encodeXPathStringValue(value) + "'";
+ return "";
+ }
+
+ public static String encodeXPathStringValue(String propertyValue) {
+ // TODO implement safer mechanism to escape invalid characters
+ // Also check why we have used this regex in ResourceSerrviceImpl l 474
+ // String cleanedKey = key.replaceAll("(?:')", "''");
+ String result = propertyValue.replaceAll("'", "''");
+ return result;
+ }
+
+ public static void andAppend(StringBuilder builder, String condition) {
+ if (notEmpty(condition)) {
+ builder.append(condition);
+ builder.append(" and ");
+ }
+ }
+
+ public static void appendOrderByProperties(StringBuilder builder, boolean ascending, String... propertyNames) {
+ if (propertyNames.length > 0) {
+ builder.append(" order by ");
+ for (String propName : propertyNames)
+ builder.append("@").append(propName).append(", ");
+ builder = builder.delete(builder.length() - 2, builder.length());
+ if (ascending)
+ builder.append(" ascending ");
+ else
+ builder.append(" descending ");
+ }
+ }
+
+ public static void appendAndPropStringCondition(StringBuilder builder, String propertyName, String filter)
+ throws RepositoryException {
+ if (notEmpty(filter)) {
+ andAppend(builder, getPropertyContains(propertyName, filter));
+ }
+ }
+
+ public static void appendAndNotPropStringCondition(StringBuilder builder, String propertyName, String filter)
+ throws RepositoryException {
+ if (notEmpty(filter)) {
+ String cond = getPropertyContains(propertyName, filter);
+ builder.append(xPathNot(cond));
+ builder.append(" and ");
+ }
+ }
+
+ public static Query createQuery(Session session, String queryString) throws RepositoryException {
+ QueryManager queryManager = session.getWorkspace().getQueryManager();
+ // Localise JCR properties for XPATH
+ queryString = localiseJcrItemNames(queryString);
+ return queryManager.createQuery(queryString, QUERY_XPATH);
+ }
+
+ private final static String NS_JCR = "\\{http://www.jcp.org/jcr/1.0\\}";
+ private final static String NS_NT = "\\{http://www.jcp.org/jcr/nt/1.0\\}";
+ private final static String NS_MIX = "\\{http://www.jcp.org/jcr/mix/1.0\\}";
+
+ /**
+ * Replace the generic namespace with the local "jcr:", "nt:", "mix:" values. It
+ * is a workaround that must be later cleaned
+ */
+ public static String localiseJcrItemNames(String name) {
+ name = name.replaceAll(NS_JCR, "jcr:");
+ name = name.replaceAll(NS_NT, "nt:");
+ name = name.replaceAll(NS_MIX, "mix:");
+ return name;
+ }
+
+ private static boolean notEmpty(String stringToTest) {
+ return !(stringToTest == null || "".equals(stringToTest.trim()));
+ }
+
+ /** Singleton. */
+ private XPathUtils() {
+
+ }
+}
--- /dev/null
+/bin/
+/target/
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.argeo.suite.theme.default</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.pde.ManifestBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.SchemaBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ds.core.builder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+/MANIFEST.MF
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="Argeo Suite Default Theme">
+ <implementation class="org.argeo.cms.ui.util.BundleCmsTheme"/>
+ <service>
+ <provide interface="org.argeo.cms.ui.CmsTheme"/>
+ </service>
+</scr:component>
--- /dev/null
+Service-Component:\
+OSGI-INF/cmsTheme.xml
+
+Import-Package:\
+org.argeo.cms.ui.util,\
+*
\ No newline at end of file
--- /dev/null
+bin.includes = META-INF/,\
+ icons/,\
+ OSGI-INF/
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.argeo.suite</groupId>
+ <artifactId>core</artifactId>
+ <version>2.1.18-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+ <artifactId>org.argeo.suite.theme.default</artifactId>
+ <name>Suite Default Theme</name>
+ <packaging>jar</packaging>
+ <dependencies>
+ </dependencies>
+</project>
--- /dev/null
+.argeo-suite-header {
+ color: white;
+ background-color: #00294b;
+}
+
+.argeo-suite-headerTitle {
+ font: bold 18px sans-serif;
+ color: white;
+ background-color: #00294b;
+}
+
+.argeo-suite-leadPane {
+ background-color: #eee;
+}
+
+Label.argeo-suite-leadPane {
+ font: 14px sans-serif;
+ color: #888;
+ background-color: #eee;
+}
+
+Button.argeo-suite-leadPane:hover {
+ cursor:pointer;
+}
+
+.argeo-suite-recentItems {
+ font: bold 14px sans-serif;
+ color: white;
+ background-color: #00294b;
+ padding: 8px 16px;
+}
+
+.argeo-suite-titleContainer {
+ background-color: #00294b;
+ padding: 6px 8px 4px 8px;
+}
+
+.argeo-suite-titleLabel {
+ font: bold 14px sans-serif;
+ color: white;
+ background-color: #00294b;
+}
+
+.argeo-suite-subTitleLabel {
+ font: italic 14px sans-serif;
+ color: #777;
+ padding: 4px 8px;
+}
+
+.argeo-suite-simpleLabel {
+ font: bold 14px sans-serif;
+ padding: 0px;
+}
+
+.argeo-suite-simpleText {
+ font: 14px sans-serif;
+ padding: 0px;
+}
+
+.argeo-suite-titleCell {
+ font: bold 14px sans-serif;
+ background-color: #ddd;
+}
+
+.argeo-suite-inlineButton {
+ padding: 0px 4px;
+ font: 12px sans-serif;
+ border: 1px solid white;
+ color: white;
+ background-image: none;
+ background-color: #00294b;
+}
+
+.argeo-suite-inlineButton:hover {
+ color: #00294b;
+ background-color: white;
+}
+
+Composite.argeo-suite-mainTabBody {
+ background-color: #eee;
+ border: 1px solid #bbb;
+}
+
+.argeo-suite-mainTab {
+ background-color: #eee;
+ border: 1px solid #888;
+}
+
+ToolItem.argeo-suite-mainTab {
+ border: none;
+ background-color: #eee;
+}
+
+ToolItem.argeo-suite-mainTab:hover {
+ background-color: #eee;
+}
+
+
+Button.argeo-suite-mainTab {
+ border: 1px solid #eee;
+ background-color: #eee;
+}
+
+.argeo-suite-mainTab:hover {
+ background-color: #eee;
+}
+
+Button.argeo-suite-mainTab:hover {
+ cursor: pointer;
+ background-color: #eee;
+}
+
+.argeo-suite-mainTabSelected {
+ font: bold 14px sans-serif;
+ color: white;
+ /*background-color: #00294b;*/
+ background-color: #5882b5;
+ border:1px solid #888;
+}
+
+ToolItem.argeo-suite-mainTabSelected {
+ border: none;
+}
+
+ToolItem.argeo-suite-mainTabSelected:hover {
+ background-color: #5882b5;
+}
+
+Button.argeo-suite-mainTabSelected {
+ border: none;
+}
+
+Sash {
+ border: 1px solid white;
+ background-image: none;
+ background-color: white;
+}
+
+Sash:hover {
+ border: 1px solid #5882b5;
+ background-color: #5882b5;
+}
+
+TreeItem{
+ background-color:#fff;
+}
+
+Tree-RowOverlay:selected {
+ color:#fff;
+ background-color:#5882b5;
+}
+
+TableItem{
+ background-color:#fff;
+}
+
+Table-RowOverlay:selected {
+ color:#fff;
+ background-color:#5882b5;
+}
+
+.argeo-suite-navigationBar{
+ background-color:#ddd;
+}
+
+.argeo-suite-navigationTitle{
+ background-color:#ddd;
+ font:bold 14px sans-serif;
+}
+
+.argeo-suite-navigationButton{
+ color:#777;
+ background-color:#ddd;
+ font:bold 14px sans-serif;
+}
+
+.argeo-suite-navigationButton:hover{
+ cursor:pointer;
+ color:#ddd;
+ background-color:#777;
+}
--- /dev/null
+.argeo-suite-header {
+ color: white;
+ background-color: #00294b;
+}
+
+.argeo-suite-headerTitle {
+ font: bold 14px sans-serif;
+ color: white;
+ background-color: #00294b;
+}
+
+.argeo-suite-leadPane {
+ background-color: #eee;
+}
+
+Label.argeo-suite-leadPane {
+ font: 11px sans-serif;
+ color: #888;
+ background-color: #eee;
+}
+
+Button.argeo-suite-leadPane:hover {
+ cursor: pointer;
+}
+
+.argeo-suite-recentItems {
+ font: bold 13px sans-serif;
+ color: white;
+ background-color: #00294b;
+ padding: 8px 16px;
+}
+
+.argeo-suite-titleContainer {
+ background-color: #00294b;
+}
+
+.argeo-suite-titleLabel {
+ font: bold 13px sans-serif;
+ margin: 6px 8px 4px 8px;
+ color: white;
+ background-color: #00294b;
+}
+
+.argeo-suite-subTitleLabel {
+ font: italic 14px sans-serif;
+ color: #777;
+ margin: 4px 8px;
+}
+
+.argeo-suite-formLine {
+ padding: 4px 8px 4px 16px;
+}
+
+.argeo-suite-simpleLabel {
+ font: normal 11px sans-serif;
+ border: 8px solid #eee;
+}
+
+.argeo-suite-simpleText {
+
+}
+
+.argeo-suite-simpleInput {
+ padding: 4px 8px 4px 8px;
+}
+
+.argeo-suite-titleCell {
+ font: bold 11px sans-serif;
+ background-color: #ddd;
+}
+
+.argeo-suite-inlineButton {
+ padding: 0px 4px;
+ font: 12px sans-serif;
+ border: 1px solid white;
+ color: white;
+ background-image: none;
+ background-color: #00294b;
+}
+
+.argeo-suite-inlineButton:hover {
+ color: #00294b;
+ background-color: white;
+}
+
+Composite.argeo-suite-mainTabBody {
+ background-color: #eee;
+ border: 1px solid #bbb;
+}
+
+.argeo-suite-mainTab {
+ background-color: #eee;
+ border: 1px solid #bbb;
+}
+
+ToolItem.argeo-suite-mainTab {
+ border: none;
+ background-color: #eee;
+}
+
+Button.argeo-suite-mainTab {
+ border: none;
+ background-color: #eee;
+}
+
+.argeo-suite-mainTab:hover {
+ background-color: #eee;
+}
+
+Button.argeo-suite-mainTab:hover {
+ cursor: pointer;
+ background-color: #eee;
+}
+
+.argeo-suite-mainTabSelected {
+ font: bold 14px sans-serif;
+ color: white;
+ /*background-color: #00294b;*/
+ background-color: #5882b5;
+ border: 1px solid #00294b;
+}
+
+ToolItem.argeo-suite-mainTabSelected {
+ border: none;
+}
+
+Button.argeo-suite-mainTabSelected {
+ border: none;
+}
\ No newline at end of file
--- /dev/null
+/bin/
+/target/
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.argeo.suite.ui.rap</name>
+ <comment></comment>
+ <projects>
+ </projects>
+ <buildSpec>
+ <buildCommand>
+ <name>org.eclipse.pde.ManifestBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.SchemaBuilder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ <buildCommand>
+ <name>org.eclipse.pde.ds.core.builder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+/MANIFEST.MF
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="Argeo Suite Web App">
+ <implementation class="org.argeo.cms.web.CmsWebApp"/>
+ <property name="contextName" type="String" value="argeo"/>
+ <reference bind="setCmsApp" cardinality="1..1" interface="org.argeo.cms.ui.CmsApp" name="CmsApp" policy="dynamic" target="(service.pid=argeo.suite.ui.app)" unbind="unsetCmsApp"/>
+ <reference bind="setEventAdmin" cardinality="1..1" interface="org.osgi.service.event.EventAdmin" name="EventAdmin" policy="static"/>
+</scr:component>
--- /dev/null
+Service-Component: OSGI-INF/cmsWebApp.xml
+
+Import-Package:\
+org.argeo.cms.web,\
+org.eclipse.rap.rwt.application,\
+*
--- /dev/null
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ OSGI-INF/
+source.. = src/
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.argeo.suite</groupId>
+ <artifactId>core</artifactId>
+ <version>2.1.18-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+ <artifactId>org.argeo.suite.ui.rap</artifactId>
+ <name>Suite UI RAP</name>
+ <packaging>jar</packaging>
+ <dependencies>
+ <dependency>
+ <groupId>org.argeo.suite</groupId>
+ <artifactId>org.argeo.suite.ui</artifactId>
+ <version>2.1.18-SNAPSHOT</version>
+ </dependency>
+
+ <!-- Eclipse E4 -->
+ <dependency>
+ <groupId>org.argeo.tp</groupId>
+ <artifactId>argeo-tp-rap-e4</artifactId>
+ <version>${version.argeo-tp}</version>
+ <type>pom</type>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+</project>
--- /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-11"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="output" path="bin"/>
+</classpath>
--- /dev/null
+/bin/
+/target/
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+ <name>org.argeo.suite.ui</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>
+ <buildCommand>
+ <name>org.eclipse.pde.ds.core.builder</name>
+ <arguments>
+ </arguments>
+ </buildCommand>
+ </buildSpec>
+ <natures>
+ <nature>org.eclipse.pde.PluginNature</nature>
+ <nature>org.eclipse.jdt.core.javanature</nature>
+ </natures>
+</projectDescription>
--- /dev/null
+/MANIFEST.MF
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" deactivate="destroy" name="Argeo Suite App">
+ <implementation class="org.argeo.suite.ui.SuiteApp"/>
+ <service>
+ <provide interface="org.argeo.cms.ui.CmsApp"/>
+ <provide interface="org.osgi.service.event.EventHandler"/>
+ </service>
+ <properties entry="config/cmsApp.properties"/>
+ <reference bind="addUiProvider" cardinality="0..n" interface="org.argeo.cms.ui.CmsUiProvider" name="CmsUiProvider" policy="dynamic" unbind="removeUiProvider"/>
+ <reference bind="addTheme" cardinality="1..n" interface="org.argeo.cms.ui.CmsTheme" name="CmsTheme" policy="dynamic" unbind="removeTheme"/>
+ <reference bind="setRepository" cardinality="1..1" interface="javax.jcr.Repository" name="Repository" policy="dynamic" target="(cn=ego)"/>
+ <reference bind="addLayer" cardinality="1..n" interface="org.argeo.suite.ui.SuiteLayer" name="SuiteLayer" policy="dynamic" unbind="removeLayer"/>
+ <reference bind="setCmsUserManager" cardinality="1..1" interface="org.argeo.cms.CmsUserManager" name="CmsUserManager" policy="static"/>
+</scr:component>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Default Dashboard">
+ <implementation class="org.argeo.suite.ui.DefaultDashboard"/>
+ <service>
+ <provide interface="org.argeo.cms.ui.CmsUiProvider"/>
+ </service>
+ <properties entry="config/dashboard.properties"/>
+</scr:component>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Dashboard Layer">
+ <implementation class="org.argeo.suite.ui.DefaultEditionLayer"/>
+ <service>
+ <provide interface="org.argeo.suite.ui.SuiteLayer"/>
+ </service>
+ <properties entry="config/dashboardLayer.properties"/>
+ <reference bind="setEntryArea" cardinality="1..1" interface="org.argeo.cms.ui.CmsUiProvider" name="CmsUiProvider" policy="dynamic" target="(service.pid=argeo.suite.ui.recentItems)"/>
+</scr:component>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" immediate="false" name="Default Work Header">
+ <implementation class="org.argeo.suite.ui.DefaultHeader"/>
+ <service>
+ <provide interface="org.argeo.cms.ui.CmsUiProvider"/>
+ <provide interface="org.osgi.service.cm.ManagedService"/>
+ </service>
+ <properties entry="config/header.properties"/>
+</scr:component>
--- /dev/null
+dashboard=dashboard
+people=contacts
+documents=documents
+locations=locations
+recentItems=recent items
+
+appTitle=Argeo Suite
+
+#
+# PEOPLE
+# org.argeo.people.ui.PeopleMsg
+#
+person=Person
+organisation=Organisation
+
+# NewPersonWizard
+firstName=First Name
+lastName=Last Name
+salutation=Salutation
+email=Email
+personWizardWindowTitle=New person
+personWizardPageTitle=Create a contact
+
+# NewOrgWizard
+legalName=Legal name
+legalForm=Legal form
+vatId=VAT ID
+orgWizardWindowTitle=New organisation
+orgWizardPageTitle=Create an organisation
+
+
+# ContextAddressComposite
+chooseAnOrganisation=Choose an organisation
+street=Street
+streetComplement=Street complement
+zipCode=Zip code
+city=City
+state=State
+country=Country
+geopoint=Geopoint
+
+# FilteredOrderableEntityTable
+filterHelp=Type filter criterion separated by a space
+
+# BankAccountComposite
+accountHolder=Account holder
+bankName=Bank name
+currency=Currency
+accountNumber=Account number
+bankNumber=Bank number
+BIC=BIC
+IBAN=IBAN
+
+# EditJobDialog
+position=Role
+chosenItem=Chose item
+department=Department
+isPrimary=Is primary
+searchAndChooseEntity=Search and choose a corresponding entity
+
+# ContactListCTab (e4)
+notes=Notes
+addAContact=Add a contact
+contactValue=Contact value
+linkedCompany=Linked company
+
+# OrgAdminInfoCTab (e4)
+paymentAccount=Payment account
+
+# OrgEditor (e4)
+orgDetails=Details
+orgActivityLog=Activity log
+team=Team
+orgAdmin=Admin.
+
+# PersonEditor (e4)
+personDetails=Contact details
+personActivityLog=Activity log
+personOrgs=Organisations
+personSecurity=Security
+
+# PersonSecurityCTab (e4)
+resetPassword=Reset password
+
+# Generic
+label=Label
+aCustomLabel=A custom label
+description=Description
+value=Value
+name=Name
+primary=Primary
+add=Add
+save=Save
+pickUp=Pick up
+
+# Tags
+confirmNewTag=Tag #{0} is not yet registered. Are you sure you want to create it?
+cannotCreateTag=Tag #{0} is not yet registered and you don't have enough rights to create it.
--- /dev/null
+dashboard=dashboard
+people=Kontakte
+documents=Dokumente
+locations=Orte
+recentItems=neulich
+
+appTitle=Argeo Suite
+
+#
+# PEOPLE
+# org.argeo.people.ui.PeopleMsg
+#
+person=Person
+organisation=Organisation
+
+# NewPersonWizard
+firstName=Vorname
+lastName=Nachname
+salutation=Salutation
+email=E-Mail
+personWizardWindowTitle=Neue Person
+personWizardPageTitle=Kontakt erstellen
+
+# NewOrgWizard
+legalName=Name
+legalForm=Geschäftsform
+vatId=Ust ID
+orgWizardWindowTitle=Neue Organisation
+orgWizardPageTitle=Organisation erstellen
+
+
+# ContextAddressComposite
+chooseAnOrganisation=Organisation wählen
+street=Strasse
+streetComplement=Strasse Zusatz
+zipCode=PLZ
+city=Stadt
+state=Bundesland
+country=Land
+geopoint=Geopoint
+
+# FilteredOrderableEntityTable
+filterHelp=Type filter criterion separated by a space
+
+# BankAccountComposite
+accountHolder=Kontoinhaber
+bankName=Name der Bank
+currency=Währung
+accountNumber=Kontonummer
+bankNumber=BLZ
+BIC=BIC
+IBAN=IBAN
+
+# EditJobDialog
+position=Rolle
+chosenItem=Auswahl
+department=Abteilung
+isPrimary=Ist Primär
+searchAndChooseEntity=Suche und wähle ein zugehöriges Objekt
+
+# ContactListCTab (e4)
+notes=Bemerkungen
+addAContact=Kontakt hinzufügen
+contactValue=Kontakt value
+linkedCompany=zugehörige Firma
+
+# OrgAdminInfoCTab (e4)
+paymentAccount=Geschäftskonto
+
+# OrgEditor (e4)
+orgDetails=Details
+orgActivityLog=Aktivitäten Log
+team=Team
+orgAdmin=Admin.
+
+# PersonEditor (e4)
+personDetails=Kontakt Daten
+personActivityLog=Aktivitäten Log
+personOrgs=Organisationen
+personSecurity=Sicherheit
+
+# PersonSecurityCTab (e4)
+resetPassword=Passwort zurücksetzen
+
+# Generic
+label=Beschriftung
+aCustomLabel=Eine spezifische Beschriftung
+description=Beschreibung
+value=Wert
+name=Name
+primary=Haupt-
+add=Hinzufügen
+save=Speichern
+pickUp=Aussuchen
+
+# Tags
+confirmNewTag=Das Hashtag '{0}' existiert noch nicht. WollenSie es hinzufügen?
+cannotCreateTag=Das Hashtag '{0}' existiert nicht uns Sie haben nicht die Rechte, um es hinzufügen.
--- /dev/null
+dashboard=dashboard
+people=contacts
+documents=documents
+locations=lieux
+recentItems=récent
+
+appTitle=Argeo Suite
+
+#
+# GENERIC
+#
+
+#
+# PEOPLE
+# org.argeo.people.ui.PeopleMsg
+#
+person=Personne
+organisation=Organisation
+
+# NewPersonWizard
+firstName=Prénom
+lastName=Nom
+salutation=Salutation
+email=Email
+personWizardWindowTitle=Nouvelle personne
+personWizardPageTitle=Créer un contact
+
+# NewOrgWizard
+legalName=Nom
+legalForm=Forme légale
+vatId=ID TVA
+orgWizardWindowTitle=Nouvelle organisation
+orgWizardPageTitle=Créer une organisation
+
+
+# ContextAddressComposite
+chooseAnOrganisation=Choisir une organisation
+street=Rue
+streetComplement=Complément rue
+zipCode=Code postal
+city=Ville
+state=État
+country=Pays
+geopoint=Géocoordonnées
+
+# FilteredOrderableEntityTable
+filterHelp=Sasir les critères de filtrage séparés par des espaces
+
+# BankAccountComposite
+accountHolder=Propriétaire du compte
+bankName=Nom de la banque
+currency=Devise
+accountNumber=Numéro de compte
+bankNumber=Numéro de banque
+BIC=BIC
+IBAN=IBAN
+
+# EditJobDialog
+position=Rôle
+chosenItem=Choisir une élément
+department=Service
+isPrimary=Principal
+searchAndChooseEntity=Cherhcer et choisir l'entitée correspondante
+
+# ContactListCTab (e4)
+notes=Notes
+addAContact=Ajouter un contact
+contactValue=Valeur
+linkedCompany=Entreprise liée
+
+# OrgAdminInfoCTab (e4)
+paymentAccount=Compte de paiement
+
+# OrgEditor (e4)
+orgDetails=Détails
+orgActivityLog=Activités
+team=Équipe
+orgAdmin=Admin.
+
+# PersonEditor (e4)
+personDetails=Détails du contact
+personActivityLog=Activités
+personOrgs=Organisations
+personSecurity=Accès
+
+# PersonSecurityCTab (e4)
+resetPassword=Force le mot de passe
+
+# Generic
+label=Étiquette
+aCustomLabel=Une étiquette spécifique
+description=Description
+value=Valeur
+name=Nom
+primary=Principal
+add=Ajouter
+save=Sauver
+pickUp=Choisir
+
+# Tags
+confirmNewTag=Le tag #{0} n'existe pas encore. Voulez-vous le créer?
+cannotCreateTag=Le tag #{0} n'existe pas encore et vous n'avez pas les droits pour le créer.
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" activate="init" immediate="false" name="Default Lead Pane">
+ <implementation class="org.argeo.suite.ui.DefaultLeadPane"/>
+ <service>
+ <provide interface="org.argeo.cms.ui.CmsUiProvider"/>
+ </service>
+ <properties entry="config/leadPane.properties"/>
+ <property name="defaultLayers" type="String">argeo.suite.ui.dashboardLayer
+argeo.documents.ui.documentsLayer
+ </property>
+ <reference bind="addLayer" cardinality="1..n" interface="org.argeo.suite.ui.SuiteLayer" name="SuiteLayer" policy="dynamic" unbind="removeLayer"/>
+</scr:component>
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<scr:component xmlns:scr="http://www.osgi.org/xmlns/scr/v1.1.0" name="Default Login Screen">
+ <implementation class="org.argeo.suite.ui.DefaultLoginScreen"/>
+ <properties entry="config/loginScreen.properties"/>
+ <service>
+ <provide interface="org.argeo.cms.ui.CmsUiProvider"/>
+ </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" activate="init" name="Default Recent Items">
+ <implementation class="org.argeo.suite.ui.RecentItems"/>
+ <service>
+ <provide interface="org.argeo.cms.ui.CmsUiProvider"/>
+ </service>
+ <properties entry="config/recentItems.properties"/>
+</scr:component>
--- /dev/null
+Service-Component:\
+OSGI-INF/cmsApp.xml,\
+OSGI-INF/header.xml,\
+OSGI-INF/leadPane.xml,\
+OSGI-INF/loginScreen.xml,\
+OSGI-INF/recentItems.xml,\
+OSGI-INF/dashboard.xml,\
+OSGI-INF/dashboardLayer.xml
+
+Import-Package:\
+org.argeo.api,\
+org.eclipse.swt,\
+org.osgi.framework,\
+org.argeo.entity,\
+org.eclipse.core.commands.common,\
+org.eclipse.jface.window,\
+org.eclipse.jface.dialogs,\
+*
--- /dev/null
+output.. = bin/
+bin.includes = META-INF/,\
+ .,\
+ OSGI-INF/,\
+ config/,\
+ OSGI-INF/loginScreen.xml,\
+ OSGI-INF/dashboard.xml,\
+ OSGI-INF/recentItems.xml,\
+ OSGI-INF/dashboardLayer.xml
+source.. = src/
--- /dev/null
+service.pid=argeo.suite.ui.app
+
+event.topics=argeo/suite/*
\ No newline at end of file
--- /dev/null
+service.pid=argeo.suite.ui.dashboard
--- /dev/null
+service.pid=argeo.suite.ui.dashboardLayer
+
+title=Dashboard
+icon=dashboard
\ No newline at end of file
--- /dev/null
+service.pid=argeo.suite.ui.header
+argeo.suite.ui=true
+
+argeo.suite.ui.header.title=%appTitle
\ No newline at end of file
--- /dev/null
+service.pid=argeo.suite.ui.leadPane
--- /dev/null
+service.pid=argeo.suite.ui.loginScreen
--- /dev/null
+service.pid=argeo.suite.ui.recentItems
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.argeo.suite</groupId>
+ <artifactId>core</artifactId>
+ <version>2.1.18-SNAPSHOT</version>
+ <relativePath>..</relativePath>
+ </parent>
+ <artifactId>org.argeo.suite.ui</artifactId>
+ <name>Suite UI</name>
+ <packaging>jar</packaging>
+ <dependencies>
+ <dependency>
+ <groupId>org.argeo.suite</groupId>
+ <artifactId>org.argeo.suite.core</artifactId>
+ <version>2.1.18-SNAPSHOT</version>
+ </dependency>
+ <dependency>
+ <groupId>org.argeo.suite</groupId>
+ <artifactId>org.argeo.entity.ui</artifactId>
+ <version>2.1.18-SNAPSHOT</version>
+ </dependency>
+
+ <!-- Argeo Commons -->
+ <dependency>
+ <groupId>org.argeo.commons</groupId>
+ <artifactId>org.argeo.eclipse.ui</artifactId>
+ <version>${version.argeo-commons}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.argeo.commons</groupId>
+ <artifactId>org.argeo.eclipse.ui.rap</artifactId>
+ <version>${version.argeo-commons}</version>
+ <scope>provided</scope>
+ </dependency>
+
+ <!-- Eclipse E4 -->
+ <dependency>
+ <groupId>org.argeo.tp</groupId>
+ <artifactId>argeo-tp-rap-e4</artifactId>
+ <version>${version.argeo-tp}</version>
+ <type>pom</type>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+</project>
--- /dev/null
+package org.argeo.suite.ui;
+
+import java.util.Set;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.CmsUserManager;
+import org.argeo.cms.ui.CmsTheme;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.argeo.cms.ui.CmsView;
+import org.argeo.cms.ui.dialogs.CmsWizardDialog;
+import org.argeo.cms.ui.util.CmsUiUtils;
+import org.argeo.eclipse.ui.Selected;
+import org.argeo.naming.LdapAttrs;
+import org.argeo.suite.SuiteRole;
+import org.argeo.suite.ui.dialogs.NewUserWizard;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.jface.viewers.DoubleClickEvent;
+import org.eclipse.jface.viewers.IDoubleClickListener;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.viewers.TableViewerColumn;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.jface.window.Window;
+import org.eclipse.jface.wizard.Wizard;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.ToolBar;
+import org.eclipse.swt.widgets.ToolItem;
+import org.osgi.service.useradmin.User;
+
+/** Entry to the admin area. */
+public class AdminEntryArea implements CmsUiProvider {
+
+ private CmsUserManager cmsUserManager;
+
+ @Override
+ public Control createUi(Composite parent, Node context) throws RepositoryException {
+ CmsTheme theme = CmsTheme.getCmsTheme(parent);
+ parent.setLayout(new GridLayout());
+ TableViewer usersViewer = new TableViewer(parent);
+ usersViewer.setContentProvider(new UsersContentProvider());
+
+ TableViewerColumn idCol = new TableViewerColumn(usersViewer, SWT.NONE);
+ idCol.getColumn().setWidth(70);
+ idCol.setLabelProvider(new ColumnLabelProvider() {
+
+ @Override
+ public String getText(Object element) {
+
+ return getUserProperty(element, LdapAttrs.uid.name());
+ }
+ });
+
+ TableViewerColumn givenNameCol = new TableViewerColumn(usersViewer, SWT.NONE);
+ givenNameCol.getColumn().setWidth(150);
+ givenNameCol.setLabelProvider(new ColumnLabelProvider() {
+
+ @Override
+ public String getText(Object element) {
+
+ return getUserProperty(element, LdapAttrs.givenName.name());
+ }
+ });
+
+ TableViewerColumn snCol = new TableViewerColumn(usersViewer, SWT.NONE);
+ snCol.getColumn().setWidth(150);
+ snCol.setLabelProvider(new ColumnLabelProvider() {
+
+ @Override
+ public String getText(Object element) {
+
+ return getUserProperty(element, LdapAttrs.sn.name());
+ }
+ });
+
+ TableViewerColumn mailCol = new TableViewerColumn(usersViewer, SWT.NONE);
+ mailCol.getColumn().setWidth(400);
+ mailCol.setLabelProvider(new ColumnLabelProvider() {
+
+ @Override
+ public String getText(Object element) {
+
+ return getUserProperty(element, LdapAttrs.mail.name());
+ }
+ });
+
+ Composite bottom = new Composite(parent, SWT.NONE);
+ bottom.setLayoutData(CmsUiUtils.fillWidth());
+ bottom.setLayout(CmsUiUtils.noSpaceGridLayout());
+ ToolBar bottomToolBar = new ToolBar(bottom, SWT.NONE);
+ bottomToolBar.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false));
+ ToolItem deleteItem = new ToolItem(bottomToolBar, SWT.FLAT);
+ deleteItem.setEnabled(false);
+// CmsUiUtils.style(deleteItem, SuiteStyle.recentItems);
+ deleteItem.setImage(SuiteIcon.delete.getSmallIcon(theme));
+ ToolItem addItem = new ToolItem(bottomToolBar, SWT.FLAT);
+ addItem.setImage(SuiteIcon.add.getSmallIcon(theme));
+ usersViewer.addDoubleClickListener(new IDoubleClickListener() {
+
+ @Override
+ public void doubleClick(DoubleClickEvent event) {
+ User user = (User) usersViewer.getStructuredSelection().getFirstElement();
+ if (user != null) {
+// Node userNode = getOrCreateUserNode(user, context);
+ CmsView.getCmsView(parent).sendEvent(SuiteEvent.openNewPart.topic(),
+ SuiteEvent.eventProperties(user));
+ }
+
+ }
+ });
+ usersViewer.addSelectionChangedListener(new ISelectionChangedListener() {
+ public void selectionChanged(SelectionChangedEvent event) {
+ User user = (User) usersViewer.getStructuredSelection().getFirstElement();
+ if (user != null) {
+// Node userNode = getOrCreateUserNode(user, context);
+ CmsView.getCmsView(parent).sendEvent(SuiteEvent.refreshPart.topic(),
+ SuiteEvent.eventProperties(user));
+ deleteItem.setEnabled(true);
+ } else {
+ deleteItem.setEnabled(false);
+ }
+ }
+ });
+
+ addItem.addSelectionListener((Selected) (e) -> {
+ // SuiteUtils.getOrCreateUserNode(adminSession, userDn);
+ Wizard wizard = new NewUserWizard(null);
+ CmsWizardDialog dialog = new CmsWizardDialog(parent.getShell(), wizard);
+ // WizardDialog dialog = new WizardDialog(shell, wizard);
+ if (dialog.open() == Window.OK) {
+ // TODO create
+ }
+ });
+
+ usersViewer.getTable().setLayoutData(CmsUiUtils.fillAll());
+ usersViewer.setInput(cmsUserManager);
+
+ return usersViewer.getTable();
+ }
+
+// private Node getOrCreateUserNode(User user, Node context) {
+// return JcrUtils.mkdirs(Jcr.getSession(context),
+// "/" + EntityType.user.name() + "/" + getUserProperty(user, LdapAttrs.uid.name()),
+// EntityType.user.get());
+// }
+
+ private String getUserProperty(Object element, String key) {
+ Object value = ((User) element).getProperties().get(key);
+ return value != null ? value.toString() : null;
+ }
+
+ class UsersContentProvider implements IStructuredContentProvider {
+
+ @Override
+ public Object[] getElements(Object inputElement) {
+ CmsUserManager cum = (CmsUserManager) inputElement;
+ Set<User> users = cum.listUsersInGroup(SuiteRole.coworker.dn(), null);
+ return users.toArray();
+ }
+
+ @Override
+ public void dispose() {
+ }
+
+ @Override
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ }
+
+ }
+
+ public void setCmsUserManager(CmsUserManager cmsUserManager) {
+ this.cmsUserManager = cmsUserManager;
+ }
+
+}
--- /dev/null
+package org.argeo.suite.ui;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.auth.CurrentUser;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.argeo.cms.ui.CmsView;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+
+/** Provides a dashboard. */
+public class DefaultDashboard implements CmsUiProvider {
+
+ @Override
+ public Control createUi(Composite parent, Node context) throws RepositoryException {
+ parent.setLayout(new GridLayout());
+ CmsView cmsView = CmsView.getCmsView(parent);
+ if (cmsView.isAnonymous())
+ throw new IllegalStateException("No user is not logged in");
+
+ Label lbl = new Label(parent, SWT.NONE);
+ lbl.setText("Welcome " + CurrentUser.getDisplayName() + "!");
+
+ return lbl;
+ }
+
+}
--- /dev/null
+package org.argeo.suite.ui;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.ui.CmsTheme;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.argeo.cms.ui.util.CmsUiUtils;
+import org.argeo.cms.ui.widgets.TabbedArea;
+import org.argeo.util.LangUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.custom.SashForm;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+/** An app layer based on an entry area and an editor area. */
+public class DefaultEditionLayer implements SuiteLayer {
+ private CmsUiProvider entryArea;
+ private CmsUiProvider workArea;
+ private List<String> weights = new ArrayList<>();
+ private boolean startMaximized = false;
+
+ @Override
+ public Control createUi(Composite parent, Node context) throws RepositoryException {
+ if (entryArea != null) {
+ SashFormEditionArea sashFormEditionArea = new SashFormEditionArea(parent, parent.getStyle());
+ entryArea.createUi(sashFormEditionArea.getEntryArea(), context);
+ if (this.workArea != null) {
+ this.workArea.createUi(sashFormEditionArea.getEditorArea(), context);
+ }
+ return sashFormEditionArea;
+ } else {
+ if (this.workArea != null) {
+ Composite area = new Composite(parent, SWT.NONE);
+ this.workArea.createUi(area, context);
+ return area;
+ }
+ CmsTheme theme = CmsTheme.getCmsTheme(parent);
+ TabbedArea tabbedArea = createTabbedArea(parent, theme);
+ return tabbedArea;
+ }
+ }
+
+ @Override
+ public void view(CmsUiProvider uiProvider, Composite workArea, Node context) {
+ TabbedArea tabbedArea;
+ if (workArea instanceof SashFormEditionArea) {
+ tabbedArea = ((SashFormEditionArea) workArea).getTabbedArea();
+ } else if (workArea instanceof TabbedArea) {
+ tabbedArea = (TabbedArea) workArea;
+ } else
+ throw new IllegalArgumentException("Unsupported work area " + workArea.getClass().getName());
+ tabbedArea.view(uiProvider, context);
+ }
+
+ @Override
+ public void open(CmsUiProvider uiProvider, Composite workArea, Node context) {
+ TabbedArea tabbedArea = ((SashFormEditionArea) workArea).getTabbedArea();
+ tabbedArea.open(uiProvider, context);
+ }
+
+ public void init(Map<String, Object> properties) {
+ weights = LangUtils.toStringList(properties.get(Property.weights.name()));
+ startMaximized = properties.containsKey(Property.startMaximized.name())
+ && "true".equals(properties.get(Property.startMaximized.name()));
+ }
+
+ public void setEntryArea(CmsUiProvider entryArea) {
+ this.entryArea = entryArea;
+ }
+
+ public void setWorkArea(CmsUiProvider workArea) {
+ this.workArea = workArea;
+ }
+
+ TabbedArea createTabbedArea(Composite parent, CmsTheme theme) {
+ TabbedArea tabbedArea = new TabbedArea(parent, SWT.NONE);
+ tabbedArea.setBodyStyle(SuiteStyle.mainTabBody.style());
+ tabbedArea.setTabStyle(SuiteStyle.mainTab.style());
+ tabbedArea.setTabSelectedStyle(SuiteStyle.mainTabSelected.style());
+ tabbedArea.setCloseIcon(SuiteIcon.close.getSmallIcon(theme));
+ tabbedArea.setLayoutData(CmsUiUtils.fillAll());
+ return tabbedArea;
+ }
+
+ /** A work area based on an entry area and and a tabbed area. */
+ class SashFormEditionArea extends SashForm {
+ private static final long serialVersionUID = 2219125778722702618L;
+ private CmsTheme theme;
+ private Composite entryArea;
+ private Composite editorArea;
+ private TabbedArea tabbedArea;
+
+ SashFormEditionArea(Composite parent, int style) {
+ super(parent, SWT.HORIZONTAL);
+ theme = CmsTheme.getCmsTheme(parent);
+
+ if (SWT.RIGHT_TO_LEFT == (style & SWT.RIGHT_TO_LEFT)) {// arabic, hebrew, etc.
+ editorArea = new Composite(this, SWT.BORDER);
+ entryArea = new Composite(this, SWT.BORDER);
+ } else {
+ entryArea = new Composite(this, SWT.NONE);
+ editorArea = new Composite(this, SWT.NONE);
+ }
+
+ if (weights.size() != 0) {
+ int[] actualWeight = new int[weights.size()];
+ for (int i = 0; i < weights.size(); i++) {
+ actualWeight[i] = Integer.parseInt(weights.get(i));
+ }
+ setWeights(actualWeight);
+ } else {
+ int[] actualWeights = new int[] { 3000, 7000 };
+ setWeights(actualWeights);
+ }
+ if (startMaximized)
+ setMaximizedControl(editorArea);
+ editorArea.setLayout(new GridLayout());
+
+ if (DefaultEditionLayer.this.workArea == null) {
+ tabbedArea = createTabbedArea(editorArea, theme);
+ }
+
+ }
+
+ Composite getEntryArea() {
+ return entryArea;
+ }
+
+ TabbedArea getTabbedArea() {
+ return tabbedArea;
+ }
+
+ Composite getEditorArea() {
+ return editorArea;
+ }
+
+ }
+}
\ No newline at end of file
--- /dev/null
+package org.argeo.suite.ui;
+
+import java.util.Dictionary;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.LocaleUtils;
+import org.argeo.cms.auth.CurrentUser;
+import org.argeo.cms.ui.CmsTheme;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.argeo.cms.ui.CmsView;
+import org.argeo.cms.ui.util.CmsUiUtils;
+import org.argeo.util.LangUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionAdapter;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.osgi.service.cm.ConfigurationException;
+import org.osgi.service.cm.ManagedService;
+
+public class DefaultHeader implements CmsUiProvider, ManagedService {
+ public final static String TITLE_PROPERTY = "argeo.suite.ui.header.title";
+ private Map<String, String> properties;
+
+ @Override
+ public Control createUi(Composite parent, Node context) throws RepositoryException {
+ CmsView cmsView = CmsView.getCmsView(parent);
+ CmsTheme theme = CmsTheme.getCmsTheme(parent);
+
+ parent.setLayout(CmsUiUtils.noSpaceGridLayout(new GridLayout(3, true)));
+
+ // TODO right to left
+ Composite lead = new Composite(parent, SWT.NONE);
+ CmsUiUtils.style(lead, SuiteStyle.header);
+ lead.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, true, false));
+ lead.setLayout(new GridLayout());
+ Label lbl = new Label(lead, SWT.NONE);
+ String title = properties.get(TITLE_PROPERTY);
+ lbl.setText(LocaleUtils.isLocaleKey(title) ? LocaleUtils.local(title, getClass().getClassLoader()).toString()
+ : title);
+ CmsUiUtils.style(lbl, SuiteStyle.headerTitle);
+ lbl.setLayoutData(CmsUiUtils.fillWidth());
+
+ Composite middle = new Composite(parent, SWT.NONE);
+ CmsUiUtils.style(middle, SuiteStyle.header);
+ middle.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false));
+ middle.setLayout(new GridLayout());
+
+ Composite end = new Composite(parent, SWT.NONE);
+ CmsUiUtils.style(end, SuiteStyle.header);
+ end.setLayoutData(new GridData(SWT.END, SWT.CENTER, true, false));
+
+ if (!cmsView.isAnonymous()) {
+ end.setLayout(new GridLayout(2, false));
+ Label userL = new Label(end, SWT.NONE);
+ CmsUiUtils.style(userL, SuiteStyle.header);
+ userL.setText(CurrentUser.getDisplayName());
+ Button logoutB = new Button(end, SWT.FLAT);
+// CmsUiUtils.style(logoutB, SuiteStyle.header);
+ logoutB.setImage(SuiteIcon.logout.getSmallIcon(theme));
+ logoutB.addSelectionListener(new SelectionAdapter() {
+ private static final long serialVersionUID = 7116760083964201233L;
+
+ @Override
+ public void widgetSelected(SelectionEvent e) {
+ cmsView.logout();
+ }
+
+ });
+ } else {
+ end.setLayout(new GridLayout(1, false));
+ // required in order to avoid wrong height after logout
+ new Label(end, SWT.NONE).setText("");
+
+ }
+ return lbl;
+ }
+
+ public void init(Map<String, String> properties) {
+ this.properties = new TreeMap<>(properties);
+ }
+
+ @Override
+ public void updated(Dictionary<String, ?> properties) throws ConfigurationException {
+ if (properties != null)
+ this.properties.putAll(LangUtils.dictToStringMap(properties));
+ }
+
+}
--- /dev/null
+package org.argeo.suite.ui;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Map;
+import java.util.TreeMap;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.api.NodeConstants;
+import org.argeo.cms.Localized;
+import org.argeo.cms.auth.CurrentUser;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.argeo.cms.ui.CmsView;
+import org.argeo.suite.RankedObject;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.osgi.framework.Constants;
+
+/** Side pane listing various perspectives. */
+public class DefaultLeadPane implements CmsUiProvider {
+ private final static Log log = LogFactory.getLog(DefaultLeadPane.class);
+
+ public static enum Property {
+ defaultLayers, adminLayers;
+ }
+
+ private Map<String, RankedObject<SuiteLayer>> layers = Collections.synchronizedSortedMap(new TreeMap<>());
+ private String[] defaultLayers;
+ private String[] adminLayers;
+
+ @Override
+ public Control createUi(Composite parent, Node node) throws RepositoryException {
+ CmsView cmsView = CmsView.getCmsView(parent);
+ GridLayout layout = new GridLayout();
+ layout.verticalSpacing = 10;
+ layout.marginTop = 10;
+ layout.marginLeft = 10;
+ layout.marginRight = 10;
+ parent.setLayout(layout);
+
+ Button first = null;
+ for (String layerId : defaultLayers) {
+ if (layers.containsKey(layerId)) {
+ RankedObject<SuiteLayer> layerObj = layers.get(layerId);
+
+ // TODO deal with i10n
+ String titleStr = (String) layerObj.getProperties().get(SuiteLayer.Property.title.name());
+ Localized title = null;
+ if (titleStr != null)
+ title = new Localized.Untranslated(titleStr);
+
+ String iconName = (String) layerObj.getProperties().get(SuiteLayer.Property.icon.name());
+ SuiteIcon icon = null;
+ if (iconName != null)
+ icon = SuiteIcon.valueOf(iconName);
+
+ Button b = SuiteUiUtils.createLayerButton(parent, layerId, title, icon);
+ if (first == null)
+ first = b;
+ }
+ }
+
+ // TODO factorise
+ boolean isAdmin = cmsView.doAs(() -> CurrentUser.isInRole(NodeConstants.ROLE_USER_ADMIN));
+ if (isAdmin && adminLayers != null)
+ for (String layerId : adminLayers) {
+ if (layers.containsKey(layerId)) {
+ RankedObject<SuiteLayer> layerObj = layers.get(layerId);
+
+ // TODO deal with i10n
+ String titleStr = (String) layerObj.getProperties().get(SuiteLayer.Property.title.name());
+ Localized title = null;
+ if (titleStr != null)
+ title = new Localized.Untranslated(titleStr);
+
+ String iconName = (String) layerObj.getProperties().get(SuiteLayer.Property.icon.name());
+ SuiteIcon icon = null;
+ if (iconName != null)
+ icon = SuiteIcon.valueOf(iconName);
+
+ Button b = SuiteUiUtils.createLayerButton(parent, layerId, title, icon);
+ if (first == null)
+ first = b;
+ }
+ }
+
+// Button dashboardB = createButton(parent, SuiteMsg.dashboard.name(), SuiteMsg.dashboard, SuiteIcon.dashboard);
+ if (!cmsView.isAnonymous()) {
+// createButton(parent, SuiteMsg.documents.name(), SuiteMsg.documents, SuiteIcon.documents);
+// createButton(parent, SuiteMsg.people.name(), SuiteMsg.people, SuiteIcon.people);
+// createButton(parent, SuiteMsg.locations.name(), SuiteMsg.locations, SuiteIcon.location);
+ }
+ return first;
+ }
+
+ public void init(Map<String, Object> properties) {
+ defaultLayers = (String[]) properties.get(Property.defaultLayers.toString());
+ if (defaultLayers == null)
+ throw new IllegalArgumentException("Default layers must be set.");
+ if (log.isDebugEnabled())
+ log.debug("Default layers: " + Arrays.asList(defaultLayers));
+ adminLayers = (String[]) properties.get(Property.adminLayers.toString());
+ if (log.isDebugEnabled() && adminLayers != null)
+ log.debug("Admin layers: " + Arrays.asList(adminLayers));
+ }
+
+ public void addLayer(SuiteLayer layer, Map<String, Object> properties) {
+ if (properties.containsKey(Constants.SERVICE_PID)) {
+ String pid = (String) properties.get(Constants.SERVICE_PID);
+ RankedObject.putIfHigherRank(layers, pid, layer, properties);
+ }
+ }
+
+ public void removeLayer(SuiteLayer layer, Map<String, Object> properties) {
+ if (properties.containsKey(Constants.SERVICE_PID)) {
+ String pid = (String) properties.get(Constants.SERVICE_PID);
+ if (layers.containsKey(pid)) {
+ if (layers.get(pid).equals(new RankedObject<SuiteLayer>(layer, properties))) {
+ layers.remove(pid);
+ }
+ }
+ }
+ }
+}
--- /dev/null
+package org.argeo.suite.ui;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+
+import org.argeo.cms.auth.CurrentUser;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.argeo.cms.ui.CmsView;
+import org.argeo.cms.ui.widgets.auth.CmsLogin;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+
+/** Provides a login screen. */
+public class DefaultLoginScreen implements CmsUiProvider {
+
+ @Override
+ public Control createUi(Composite parent, Node context) throws RepositoryException {
+ CmsView cmsView = CmsView.getCmsView(parent);
+ if (!cmsView.isAnonymous())
+ throw new IllegalStateException(CurrentUser.getUsername() + " is already logged in");
+
+ parent.setLayout(new GridLayout());
+ Composite loginArea = new Composite(parent, SWT.NONE);
+ loginArea.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true));
+
+ CmsLogin cmsLogin = new CmsLogin(cmsView);
+ cmsLogin.createUi(loginArea);
+ return cmsLogin.getCredentialsBlock();
+ }
+
+}
--- /dev/null
+package org.argeo.suite.ui;
+
+import static org.argeo.eclipse.ui.EclipseUiUtils.notEmpty;
+
+import java.util.List;
+import java.util.Map;
+
+import javax.jcr.Node;
+import javax.jcr.NodeIterator;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.observation.Event;
+import javax.jcr.observation.EventIterator;
+import javax.jcr.observation.EventListener;
+import javax.jcr.query.Query;
+import javax.jcr.query.QueryResult;
+
+import org.argeo.cms.ui.CmsTheme;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.argeo.cms.ui.CmsView;
+import org.argeo.cms.ui.util.CmsUiUtils;
+import org.argeo.eclipse.ui.EclipseUiUtils;
+import org.argeo.entity.EntityType;
+import org.argeo.jcr.Jcr;
+import org.argeo.jcr.JcrUtils;
+import org.argeo.suite.ui.widgets.DelayedText;
+import org.argeo.suite.util.XPathUtils;
+import org.eclipse.jface.layout.TableColumnLayout;
+import org.eclipse.jface.viewers.ColumnLabelProvider;
+import org.eclipse.jface.viewers.ColumnWeightData;
+import org.eclipse.jface.viewers.DoubleClickEvent;
+import org.eclipse.jface.viewers.IDoubleClickListener;
+import org.eclipse.jface.viewers.ILabelProvider;
+import org.eclipse.jface.viewers.ISelectionChangedListener;
+import org.eclipse.jface.viewers.IStructuredContentProvider;
+import org.eclipse.jface.viewers.SelectionChangedEvent;
+import org.eclipse.jface.viewers.TableViewer;
+import org.eclipse.jface.viewers.Viewer;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.KeyEvent;
+import org.eclipse.swt.events.KeyListener;
+import org.eclipse.swt.events.ModifyEvent;
+import org.eclipse.swt.events.ModifyListener;
+import org.eclipse.swt.layout.GridData;
+import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Table;
+import org.eclipse.swt.widgets.TableColumn;
+import org.eclipse.swt.widgets.Text;
+import org.eclipse.swt.widgets.ToolBar;
+import org.eclipse.swt.widgets.ToolItem;
+
+/** List recent items. */
+public class RecentItems implements CmsUiProvider {
+ private final static int SEARCH_TEXT_DELAY = 800;
+ private final static int SEARCH_DEFAULT_LIMIT = 100;
+
+ private CmsTheme theme;
+
+ private String entityType;
+
+ static enum Property {
+ entityTypes;
+ }
+
+ @Override
+ public Control createUi(Composite parent, Node context) throws RepositoryException {
+ theme = CmsTheme.getCmsTheme(parent);
+ parent.setLayout(new GridLayout());
+// parent.setLayout(CmsUiUtils.noSpaceGridLayout());
+ parent.setLayout(new GridLayout());
+
+// Composite top = new Composite(parent, SWT.BORDER);
+// CmsUiUtils.style(top, SuiteStyle.recentItems);
+// top.setLayoutData(CmsUiUtils.fillWidth());
+// top.setLayout(CmsUiUtils.noSpaceGridLayout(2));
+// Label lbl = new Label(top, SWT.FLAT);
+// lbl.setLayoutData(CmsUiUtils.fillWidth());
+// lbl.setText(SuiteMsg.recentItems.lead());
+// CmsUiUtils.style(lbl, SuiteStyle.recentItems);
+//
+// ToolBar topToolBar = new ToolBar(top, SWT.NONE);
+// ToolItem addItem = new ToolItem(topToolBar, SWT.FLAT);
+//// CmsUiUtils.style(addItem, SuiteStyle.recentItems);
+// addItem.setImage(SuiteIcon.add.getSmallIcon(theme));
+
+ if (context == null)
+ return null;
+ SingleEntityViewer entityViewer = new SingleEntityViewer(parent, SWT.NONE, context.getSession());
+ entityViewer.createUi();
+ entityViewer.getViewer().getTable().setLayoutData(CmsUiUtils.fillAll());
+
+ Composite bottom = new Composite(parent, SWT.NONE);
+ bottom.setLayoutData(CmsUiUtils.fillWidth());
+ bottom.setLayout(CmsUiUtils.noSpaceGridLayout());
+ ToolBar bottomToolBar = new ToolBar(bottom, SWT.NONE);
+ bottomToolBar.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false));
+ ToolItem deleteItem = new ToolItem(bottomToolBar, SWT.FLAT);
+ deleteItem.setEnabled(false);
+// CmsUiUtils.style(deleteItem, SuiteStyle.recentItems);
+ deleteItem.setImage(SuiteIcon.delete.getSmallIcon(theme));
+ ToolItem addItem = new ToolItem(bottomToolBar, SWT.FLAT);
+ addItem.setImage(SuiteIcon.add.getSmallIcon(theme));
+ entityViewer.getViewer().addDoubleClickListener(new IDoubleClickListener() {
+
+ @Override
+ public void doubleClick(DoubleClickEvent event) {
+ Node node = (Node) entityViewer.getViewer().getStructuredSelection().getFirstElement();
+ if (node != null)
+ CmsView.getCmsView(parent).sendEvent(SuiteEvent.openNewPart.topic(),
+ SuiteEvent.eventProperties(node));
+
+ }
+ });
+ entityViewer.getViewer().addSelectionChangedListener(new ISelectionChangedListener() {
+ public void selectionChanged(SelectionChangedEvent event) {
+ Node node = (Node) entityViewer.getViewer().getStructuredSelection().getFirstElement();
+ if (node != null) {
+ CmsView.getCmsView(parent).sendEvent(SuiteEvent.refreshPart.topic(),
+ SuiteEvent.eventProperties(node));
+ deleteItem.setEnabled(true);
+ } else {
+ deleteItem.setEnabled(false);
+ }
+ }
+ });
+
+ return entityViewer.filterTxt;
+
+ }
+
+ public void init(Map<String, String> properties) {
+ // TODO manage multiple entities
+ entityType = properties.get(Property.entityTypes.name());
+ }
+
+ class SingleEntityViewer {
+ Composite parent;
+ Text filterTxt;
+ TableViewer viewer;
+ Session session;
+
+ public SingleEntityViewer(Composite parent, int style, Session session) {
+ this.parent = parent;
+ this.session = session;
+ }
+
+ public void createUi() {
+ // MainLayout
+ addFilterPanel(parent);
+ viewer = createListPart(parent, new SingleEntityLabelProvider());
+ refreshFilteredList();
+
+ try {
+ String[] nodeTypes = entityType != null && entityType.contains(":") ? new String[] { entityType }
+ : null;
+ session.getWorkspace().getObservationManager().addEventListener(new EventListener() {
+
+ @Override
+ public void onEvent(EventIterator events) {
+ parent.getDisplay().asyncExec(() -> refreshFilteredList());
+ }
+ }, Event.PROPERTY_CHANGED | Event.NODE_ADDED | Event.NODE_REMOVED | Event.PROPERTY_ADDED, "/", true,
+ null, nodeTypes, false);
+ } catch (RepositoryException e) {
+ throw new IllegalStateException("Cannot add JCR observer", e);
+ }
+
+ }
+
+ private void addFilterPanel(Composite parent) {
+ // Use a delayed text: the query won't be done until the user stop
+ // typing for 800ms
+ int style = SWT.BORDER | SWT.SEARCH | SWT.ICON_CANCEL;
+ DelayedText delayedText = new DelayedText(parent, style, SEARCH_TEXT_DELAY);
+ filterTxt = delayedText.getText();
+ filterTxt.setLayoutData(EclipseUiUtils.fillWidth());
+
+ // final ServerPushSession pushSession = new ServerPushSession();
+ delayedText.addDelayedModifyListener(null, new ModifyListener() {
+ private static final long serialVersionUID = 5003010530960334977L;
+
+ public void modifyText(ModifyEvent event) {
+ delayedText.getText().getDisplay().asyncExec(new Runnable() {
+ @Override
+ public void run() {
+ refreshFilteredList();
+ }
+ });
+ // pushSession.stop();
+ }
+ });
+
+ // Jump to the first item of the list using the down arrow
+ filterTxt.addKeyListener(new KeyListener() {
+ private static final long serialVersionUID = -4523394262771183968L;
+
+ @Override
+ public void keyReleased(KeyEvent e) {
+ }
+
+ @Override
+ public void keyPressed(KeyEvent e) {
+ // boolean shiftPressed = (e.stateMask & SWT.SHIFT) != 0;
+ // boolean altPressed = (e.stateMask & SWT.ALT) != 0;
+ if (e.keyCode == SWT.ARROW_DOWN || e.keyCode == SWT.TAB) {
+// Object first = entityViewer.getElementAt(0);
+// if (first != null) {
+// entityViewer.getTable().setFocus();
+// entityViewer.setSelection(new StructuredSelection(first), true);
+// }
+ e.doit = false;
+ }
+ }
+ });
+
+ parent.addDisposeListener((e) -> {
+ delayedText.close();
+ });
+ }
+
+ protected TableViewer createListPart(Composite parent, ILabelProvider labelProvider) {
+// parent.setLayout(new GridLayout());
+// parent.setLayout(CmsUiUtils.noSpaceGridLayout());
+
+ Composite tableComposite = new Composite(parent, SWT.NONE);
+ GridData gd = new GridData(GridData.HORIZONTAL_ALIGN_FILL | GridData.GRAB_VERTICAL
+ | GridData.VERTICAL_ALIGN_FILL | GridData.GRAB_HORIZONTAL);
+ tableComposite.setLayoutData(gd);
+
+ TableViewer viewer = new TableViewer(tableComposite, SWT.SINGLE | SWT.H_SCROLL | SWT.V_SCROLL | SWT.BORDER);
+ viewer.setLabelProvider(labelProvider);
+
+ TableColumn singleColumn = new TableColumn(viewer.getTable(), SWT.V_SCROLL);
+ TableColumnLayout tableColumnLayout = new TableColumnLayout();
+ tableColumnLayout.setColumnData(singleColumn, new ColumnWeightData(85));
+ tableComposite.setLayout(tableColumnLayout);
+
+ // Corresponding table & style
+ Table table = viewer.getTable();
+// Listener[] mouseDownListeners = table.getListeners(SWT.MouseDown);
+// for (Listener listener : table.getListeners(SWT.MouseDown))
+// table.removeListener(SWT.MouseDown, listener);
+// for (Listener listener : table.getListeners(SWT.MouseUp))
+// table.removeListener(SWT.MouseUp, listener);
+// for (Listener listener : table.getListeners(SWT.MouseDoubleClick))
+// table.removeListener(SWT.MouseDoubleClick, listener);
+//
+// table.addMouseListener(new MouseListener() {
+//
+// @Override
+// public void mouseUp(MouseEvent e) {
+// System.out.println("Mouse up: "+e);
+// }
+//
+// @Override
+// public void mouseDown(MouseEvent e) {
+// System.out.println("Mouse down: "+e);
+// }
+//
+// @Override
+// public void mouseDoubleClick(MouseEvent e) {
+// System.out.println("Mouse double: "+e);
+//
+// }
+// });
+ table.setLinesVisible(true);
+ table.setHeaderVisible(false);
+ // CmsUiUtils.markup(table);
+ // CmsUiUtils.setItemHeight(table, 26);
+
+ viewer.setContentProvider(new BasicNodeListContentProvider());
+ return viewer;
+ }
+
+// public boolean setFocus() {
+// refreshFilteredList();
+// return parent.setFocus();
+// }
+
+ public void forceRefresh(Object object) {
+ refreshFilteredList();
+ }
+
+ protected void refreshFilteredList() {
+ try {
+ String filter = filterTxt.getText();
+ // Prevents the query on the full repository
+ // if (isEmpty(filter)) {
+ // entityViewer.setInput(null);
+ // return;
+ // }
+
+ // XPATH Query
+ String xpathQueryStr;
+ if (entityType != null) {
+ int indexColumn = entityType.indexOf(':');
+ if (indexColumn > 0) {// JCR node type
+ xpathQueryStr = "//element(*, " + entityType + ") order by @jcr:created descending";
+ } else {
+ xpathQueryStr = entityType.contains(":") ? "//element(*, " + entityType + ")"
+ : "//element(*, " + EntityType.entity.get() + ")[@entity:type='" + entityType + "']";
+ }
+ } else {
+ xpathQueryStr = "//element(*, " + EntityType.entity.get() + ")";
+ }
+// String xpathQueryStr = "//element(*, " + ConnectTypes.CONNECT_ENTITY + ")";
+ String xpathFilter = XPathUtils.getFreeTextConstraint(filter);
+ if (notEmpty(xpathFilter))
+ xpathQueryStr += "[" + xpathFilter + "]";
+
+// long begin = System.currentTimeMillis();
+ // session.refresh(false);
+ Query xpathQuery = XPathUtils.createQuery(session, xpathQueryStr);
+
+ xpathQuery.setLimit(SEARCH_DEFAULT_LIMIT);
+ QueryResult result = xpathQuery.execute();
+
+ NodeIterator nit = result.getNodes();
+ viewer.setInput(JcrUtils.nodeIteratorToList(nit));
+// if (log.isTraceEnabled()) {
+// long end = System.currentTimeMillis();
+// log.trace("Quick Search - Found: " + nit.getSize() + " in " + (end - begin)
+// + " ms by executing XPath query (" + xpathQueryStr + ").");
+// }
+ } catch (RepositoryException e) {
+ throw new IllegalStateException("Unable to list entities", e);
+ }
+ }
+
+ public TableViewer getViewer() {
+ return viewer;
+ }
+
+ class SingleEntityLabelProvider extends ColumnLabelProvider {
+ private static final long serialVersionUID = -2209337675781795677L;
+
+ @Override
+ public String getText(Object element) {
+ return Jcr.getTitle((Node) element);
+ }
+
+ }
+
+ class BasicNodeListContentProvider implements IStructuredContentProvider {
+ private static final long serialVersionUID = 1L;
+ // keep a cache of the Nodes in the content provider to be able to
+ // manage long request
+ private List<Node> nodes;
+
+ public void dispose() {
+ }
+
+ /** Expects a list of nodes as a new input */
+ @SuppressWarnings("unchecked")
+ public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
+ nodes = (List<Node>) newInput;
+ }
+
+ public Object[] getElements(Object arg0) {
+ return nodes.toArray();
+ }
+ }
+ }
+}
--- /dev/null
+package org.argeo.suite.ui;
+
+import static org.argeo.cms.ui.CmsView.CMS_VIEW_UID_PROPERTY;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeMap;
+import java.util.TreeSet;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.jcr.Session;
+import javax.jcr.nodetype.NodeType;
+import javax.naming.InvalidNameException;
+import javax.naming.ldap.LdapName;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.api.NodeUtils;
+import org.argeo.cms.CmsUserManager;
+import org.argeo.cms.LocaleUtils;
+import org.argeo.cms.auth.CmsSession;
+import org.argeo.cms.ui.AbstractCmsApp;
+import org.argeo.cms.ui.CmsTheme;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.argeo.cms.ui.CmsView;
+import org.argeo.cms.ui.dialogs.CmsFeedback;
+import org.argeo.cms.ui.util.CmsEvent;
+import org.argeo.cms.ui.util.CmsUiUtils;
+import org.argeo.eclipse.ui.specific.UiContext;
+import org.argeo.entity.EntityConstants;
+import org.argeo.entity.EntityNames;
+import org.argeo.entity.EntityType;
+import org.argeo.jcr.Jcr;
+import org.argeo.suite.RankedObject;
+import org.argeo.suite.SuiteUtils;
+import org.argeo.util.LangUtils;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.widgets.Composite;
+import org.osgi.framework.Constants;
+import org.osgi.service.event.Event;
+import org.osgi.service.event.EventHandler;
+import org.osgi.service.useradmin.User;
+
+/** The Argeo Suite App. */
+public class SuiteApp extends AbstractCmsApp implements EventHandler {
+ private final static Log log = LogFactory.getLog(SuiteApp.class);
+
+ public final static String PUBLIC_BASE_PATH_PROPERTY = "publicBasePath";
+ public final static String DEFAULT_UI_NAME_PROPERTY = "defaultUiName";
+ public final static String DEFAULT_THEME_ID_PROPERTY = "defaultThemeId";
+
+ private String publicBasePath = null;
+
+ private String pidPrefix;
+ private String headerPid;
+ private String leadPanePid;
+ private String loginScreenPid;
+// private String DASHBOARD_PID = pidPrefix + "dashboard";
+// private String RECENT_ITEMS_PID = pidPrefix + "recentItems";
+
+ private String defaultUiName = "app";
+ private String defaultThemeId = "org.argeo.suite.theme.default";
+
+ private Map<String, RankedObject<CmsUiProvider>> uiProvidersByPid = Collections.synchronizedMap(new HashMap<>());
+ private Map<String, RankedObject<CmsUiProvider>> uiProvidersByType = Collections.synchronizedMap(new HashMap<>());
+ private Map<String, RankedObject<SuiteLayer>> layersByPid = Collections.synchronizedSortedMap(new TreeMap<>());
+ private Map<String, RankedObject<SuiteLayer>> layersByType = Collections.synchronizedSortedMap(new TreeMap<>());
+
+ private CmsUserManager cmsUserManager;
+
+ // TODO make more optimal or via CmsSession/CmsView
+ private Map<String, SuiteUi> managedUis = new HashMap<>();
+
+// private CmsUiProvider headerPart = null;
+
+ public void init(Map<String, Object> properties) {
+ if (log.isDebugEnabled())
+ log.info("Argeo Suite App started");
+
+ if (properties.containsKey(DEFAULT_UI_NAME_PROPERTY))
+ defaultUiName = LangUtils.get(properties, DEFAULT_UI_NAME_PROPERTY);
+ if (properties.containsKey(DEFAULT_THEME_ID_PROPERTY))
+ defaultThemeId = LangUtils.get(properties, DEFAULT_THEME_ID_PROPERTY);
+ publicBasePath = LangUtils.get(properties, PUBLIC_BASE_PATH_PROPERTY);
+
+ if (properties.containsKey(Constants.SERVICE_PID)) {
+ String servicePid = properties.get(Constants.SERVICE_PID).toString();
+ if (servicePid.endsWith(".app")) {
+ pidPrefix = servicePid.substring(0, servicePid.length() - "app".length());
+ }
+ }
+
+ if (pidPrefix == null)
+ throw new IllegalArgumentException("PID prefix must be set.");
+
+ headerPid = pidPrefix + "header";
+ leadPanePid = pidPrefix + "leadPane";
+ loginScreenPid = pidPrefix + "loginScreen";
+ }
+
+ public void destroy(Map<String, Object> properties) {
+ for (SuiteUi ui : managedUis.values())
+ if (!ui.isDisposed())
+ ui.dispose();
+ if (log.isDebugEnabled())
+ log.info("Argeo Suite App stopped");
+
+ }
+
+ @Override
+ public Set<String> getUiNames() {
+ HashSet<String> uiNames = new HashSet<>();
+ uiNames.add(defaultUiName);
+ return uiNames;
+ }
+
+ @Override
+ public Composite initUi(Composite parent) {
+ String uiName = parent.getData(UI_NAME_PROPERTY) != null ? parent.getData(UI_NAME_PROPERTY).toString() : null;
+ CmsView cmsView = CmsView.getCmsView(parent);
+ if (cmsView == null)
+ throw new IllegalStateException("No CMS view is registered.");
+ CmsTheme theme = getTheme(uiName);
+ if (theme != null)
+ CmsTheme.registerCmsTheme(parent.getShell(), theme);
+ SuiteUi argeoSuiteUi = new SuiteUi(parent, SWT.INHERIT_DEFAULT);
+ String uid = cmsView.getUid();
+ managedUis.put(uid, argeoSuiteUi);
+ argeoSuiteUi.addDisposeListener((e) -> {
+ managedUis.remove(uid);
+ if (log.isDebugEnabled())
+ log.debug("Suite UI " + uid + " has been disposed.");
+ });
+ refreshUi(argeoSuiteUi, null);
+ return argeoSuiteUi;
+ }
+
+ @Override
+ public String getThemeId(String uiName) {
+ return defaultThemeId;
+ }
+
+ @Override
+ public void refreshUi(Composite parent, String state) {
+ try {
+ Node context = null;
+ SuiteUi ui = (SuiteUi) parent;
+ CmsView cmsView = CmsView.getCmsView(parent);
+ if (cmsView.isAnonymous() && publicBasePath == null) {// internal app, must login
+ ui.logout();
+ refreshPart(findUiProvider(headerPid), ui.getHeader(), context);
+ ui.refreshBelowHeader(false);
+ refreshPart(findUiProvider(loginScreenPid), ui.getBelowHeader(), context);
+ ui.layout(true, true);
+ } else {
+ CmsSession cmsSession = cmsView.getCmsSession();
+ if (ui.getUserDir() == null) {
+ if (cmsView.isAnonymous()) {
+ assert publicBasePath != null;
+ ui.initSessions(getRepository(), publicBasePath);
+ } else {
+ Session adminSession = null;
+ try {
+ adminSession = NodeUtils.openDataAdminSession(getRepository(), null);
+ Node userDir = SuiteUtils.getOrCreateCmsSessionNode(adminSession, cmsSession);
+ ui.initSessions(getRepository(), userDir.getPath());
+ } finally {
+ Jcr.logout(adminSession);
+ }
+ }
+ }
+ initLocale(cmsSession);
+ context = stateToNode(ui, state);
+ if (context == null)
+ context = ui.getUserDir();
+
+ refreshPart(findUiProvider(headerPid), ui.getHeader(), context);
+ ui.refreshBelowHeader(true);
+ for (String key : layersByPid.keySet()) {
+ SuiteLayer layer = layersByPid.get(key).get();
+ ui.addLayer(key, layer);
+ }
+ refreshPart(findUiProvider(leadPanePid), ui.getLeadPane(), context);
+ ui.layout(true, true);
+ setState(parent, state);
+ }
+ } catch (Exception e) {
+ CmsFeedback.show("Unexpected exception", e);
+ }
+ }
+
+ private void initLocale(CmsSession cmsSession) {
+ if (cmsSession == null)
+ return;
+ Locale locale = cmsSession.getLocale();
+ UiContext.setLocale(locale);
+ LocaleUtils.setThreadLocale(locale);
+
+ }
+
+ private void refreshPart(CmsUiProvider uiProvider, Composite part, Node context) {
+ CmsUiUtils.clear(part);
+ uiProvider.createUiPart(part, context);
+ }
+
+ private CmsUiProvider findUiProvider(String pid) {
+ if (!uiProvidersByPid.containsKey(pid))
+ throw new IllegalArgumentException("No UI provider registered as " + pid);
+ return uiProvidersByPid.get(pid).get();
+ }
+
+ private <T> T findByType(Map<String, RankedObject<T>> byType, Node context) {
+ if (context == null)
+ throw new IllegalArgumentException("A node should be provided");
+ try {
+ // mixins
+ Set<String> types = new TreeSet<>();
+ for (NodeType nodeType : context.getMixinNodeTypes()) {
+ String typeName = nodeType.getName();
+ if (byType.containsKey(typeName)) {
+ types.add(typeName);
+ }
+ }
+ // primary node type
+ {
+ NodeType nodeType = context.getPrimaryNodeType();
+ String typeName = nodeType.getName();
+ if (byType.containsKey(typeName)) {
+ types.add(typeName);
+ }
+ for (NodeType mixin : nodeType.getDeclaredSupertypes()) {
+ if (byType.containsKey(mixin.getName())) {
+ types.add(mixin.getName());
+ }
+ }
+ }
+ // entity type
+ if (context.isNodeType(EntityType.entity.get())) {
+ if (context.hasProperty(EntityNames.ENTITY_TYPE)) {
+ String typeName = context.getProperty(EntityNames.ENTITY_TYPE).getString();
+ if (byType.containsKey(typeName)) {
+ types.add(typeName);
+ }
+ }
+ }
+
+// if (context.getPath().equals("/")) {// root node
+// types.add("nt:folder");
+// }
+ if (NodeUtils.isUserHome(context) && byType.containsKey("nt:folder")) {// home node
+ types.add("nt:folder");
+ }
+
+ if (types.size() == 0)
+ throw new IllegalArgumentException("No type found for " + context);
+ String type = types.iterator().next();
+ if (!byType.containsKey(type))
+ throw new IllegalArgumentException("No component found for " + context + " with type " + type);
+ return byType.get(type).get();
+ } catch (RepositoryException e) {
+ throw new IllegalStateException(e);
+ }
+ }
+
+ @Override
+ public void setState(Composite parent, String state) {
+ if (state == null || state.equals("~"))
+ return;
+ if (!state.startsWith("/") && !state.equals("~")) {
+ if (parent instanceof SuiteUi) {
+ SuiteUi ui = (SuiteUi) parent;
+ String currentLayerId = ui.getCurrentLayerId();
+ if (state.equals(currentLayerId))
+ return; // does nothing
+ else {
+ Map<String, Object> properties = new HashMap<>();
+ properties.put(SuiteEvent.LAYER, state);
+ ui.getCmsView().sendEvent(SuiteEvent.switchLayer.topic(), properties);
+ }
+ }
+ return;
+ }
+ SuiteUi suiteUi = (SuiteUi) parent;
+ Node node = stateToNode(suiteUi, state);
+ if (node == null) {
+ suiteUi.getCmsView().navigateTo("~");
+ } else {
+ suiteUi.getCmsView().sendEvent(SuiteEvent.switchLayer.topic(), SuiteEvent.eventProperties(node));
+ suiteUi.getCmsView().sendEvent(SuiteEvent.refreshPart.topic(), SuiteEvent.eventProperties(node));
+ }
+ }
+
+ private String nodeToState(Node node) {
+ return '/' + Jcr.getWorkspaceName(node) + Jcr.getPath(node);
+ }
+
+ private Node stateToNode(SuiteUi suiteUi, String state) {
+ if (suiteUi == null)
+ return null;
+ if (state == null || !state.startsWith("/"))
+ return null;
+
+ String path = state.substring(1);
+ String workspace;
+ if (path.equals("")) {
+ workspace = null;
+ path = "/";
+ } else {
+ int index = path.indexOf('/');
+ if (index == 0) {
+ log.error("Cannot interpret " + state);
+// cmsView.navigateTo("~");
+ return null;
+ } else if (index > 0) {
+ workspace = path.substring(0, index);
+ path = path.substring(index);
+ } else {// index<0, assuming root node
+ workspace = path;
+ path = "/";
+ }
+ }
+ Session session = suiteUi.getSession(workspace);
+ if (session == null)
+ return null;
+ Node node = Jcr.getNode(session, path);
+ return node;
+ }
+
+ /*
+ * Events management
+ */
+
+ @Override
+ public void handleEvent(Event event) {
+
+ // Specific UI related events
+ SuiteUi ui = getRelatedUi(event);
+ if (ui == null)
+ return;
+ try {
+// String currentLayerId = ui.getCurrentLayerId();
+// SuiteLayer currentLayer = currentLayerId != null ? layersByPid.get(currentLayerId).get() : null;
+ if (isTopic(event, SuiteEvent.refreshPart)) {
+ Node node = getNode(ui, event);
+ if (node == null)
+ return;
+ CmsUiProvider uiProvider = findByType(uiProvidersByType, node);
+ SuiteLayer layer = findByType(layersByType, node);
+ ui.switchToLayer(layer, node);
+ ui.getCmsView().runAs(() -> layer.view(uiProvider, ui.getCurrentWorkArea(), node));
+ ui.getCmsView().stateChanged(nodeToState(node), Jcr.getTitle(node));
+ } else if (isTopic(event, SuiteEvent.openNewPart)) {
+ Node node = getNode(ui, event);
+ if (node == null)
+ return;
+ CmsUiProvider uiProvider = findByType(uiProvidersByType, node);
+ SuiteLayer layer = findByType(layersByType, node);
+ ui.switchToLayer(layer, node);
+ ui.getCmsView().runAs(() -> layer.open(uiProvider, ui.getCurrentWorkArea(), node));
+ ui.getCmsView().stateChanged(nodeToState(node), Jcr.getTitle(node));
+ } else if (isTopic(event, SuiteEvent.switchLayer)) {
+ String layerId = get(event, SuiteEvent.LAYER);
+ if (layerId != null) {
+// ui.switchToLayer(layerId, ui.getUserDir());
+ ui.getCmsView().runAs(() -> ui.switchToLayer(layerId, ui.getUserDir()));
+ ui.getCmsView().navigateTo(layerId);
+ } else {
+ Node node = getNode(ui, event);
+ if (node != null) {
+ SuiteLayer layer = findByType(layersByType, node);
+ ui.getCmsView().runAs(() -> ui.switchToLayer(layer, node));
+ }
+ }
+ }
+ } catch (Exception e) {
+ log.error("Cannot handle event " + event, e);
+// CmsView.getCmsView(ui).exception(e);
+ }
+
+ }
+
+ private Node getNode(SuiteUi ui, Event event) {
+ String nodePath = get(event, SuiteEvent.NODE_PATH);
+ String workspaceName = get(event, SuiteEvent.WORKSPACE);
+ Session session = ui.getSession(workspaceName);
+ Node node;
+ if (nodePath == null) {
+ // look for a user
+ String username = get(event, SuiteEvent.USERNAME);
+ if (username == null)
+ return null;
+ User user = cmsUserManager.getUser(username);
+ if (user == null)
+ return null;
+ LdapName userDn;
+ try {
+ userDn = new LdapName(user.getName());
+