From: Mathieu Baudier Date: Fri, 28 Jan 2022 10:27:24 +0000 (+0100) Subject: Put Eclipse and JCR components in subdirs in order to clarify indirect X-Git-Tag: argeo-commons-2.3.5~51 X-Git-Url: https://git.argeo.org/?p=lgpl%2Fargeo-commons.git;a=commitdiff_plain;h=8282011b0e20e80704b209ad55fa9fb132e16280 Put Eclipse and JCR components in subdirs in order to clarify indirect licensing issues with GPL. --- diff --git a/Makefile b/Makefile index 21af0b9d0..ad9590407 100644 --- a/Makefile +++ b/Makefile @@ -61,10 +61,10 @@ $(SDK_SRC_BASE)/org.argeo.cms/bin:$\ rcp: base $(JVM) -jar $(ECJ_JAR) -11 -nowarn -time -cp $(RCP_CLASSPATH) \ - $(SDK_SRC_BASE)/org.argeo.cms.servlet/src[-d $(SDK_SRC_BASE)/org.argeo.cms.servlet/bin] \ + $(SDK_SRC_BASE)/eclipse/org.argeo.cms.servlet/src[-d $(SDK_SRC_BASE)/eclipse/org.argeo.cms.servlet/bin] \ $(SDK_SRC_BASE)/rcp/org.argeo.swt.minidesktop/src[-d $(SDK_SRC_BASE)/rcp/org.argeo.swt.minidesktop/bin] \ $(SDK_SRC_BASE)/rcp/org.argeo.swt.specific.rcp/src[-d $(SDK_SRC_BASE)/rcp/org.argeo.swt.specific.rcp/bin] \ - $(SDK_SRC_BASE)/org.argeo.cms.swt/src[-d $(SDK_SRC_BASE)/org.argeo.cms.swt/bin] \ + $(SDK_SRC_BASE)/eclipse/org.argeo.cms.swt/src[-d $(SDK_SRC_BASE)/eclipse/org.argeo.cms.swt/bin] \ $(SDK_SRC_BASE)/rcp/org.argeo.cms.ui.rcp/src[-d $(SDK_SRC_BASE)/rcp/org.argeo.cms.ui.rcp/bin] \ diff --git a/eclipse/cnf/maven.bnd b/eclipse/cnf/maven.bnd new file mode 100644 index 000000000..4bd5c0cfe --- /dev/null +++ b/eclipse/cnf/maven.bnd @@ -0,0 +1 @@ +-include: ../../cnf/maven.bnd \ No newline at end of file diff --git a/eclipse/org.argeo.cms.e4/.classpath b/eclipse/org.argeo.cms.e4/.classpath new file mode 100644 index 000000000..e801ebfb4 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/eclipse/org.argeo.cms.e4/.project b/eclipse/org.argeo.cms.e4/.project new file mode 100644 index 000000000..0c0406952 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/.project @@ -0,0 +1,33 @@ + + + org.argeo.cms.e4 + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + org.eclipse.pde.ds.core.builder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/eclipse/org.argeo.cms.e4/META-INF/.gitignore b/eclipse/org.argeo.cms.e4/META-INF/.gitignore new file mode 100644 index 000000000..4854a41b9 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/META-INF/.gitignore @@ -0,0 +1 @@ +/MANIFEST.MF diff --git a/eclipse/org.argeo.cms.e4/OSGI-INF/defaultCallbackHandler.xml b/eclipse/org.argeo.cms.e4/OSGI-INF/defaultCallbackHandler.xml new file mode 100644 index 000000000..fcd3ae5cb --- /dev/null +++ b/eclipse/org.argeo.cms.e4/OSGI-INF/defaultCallbackHandler.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/eclipse/org.argeo.cms.e4/OSGI-INF/homeRepository.xml b/eclipse/org.argeo.cms.e4/OSGI-INF/homeRepository.xml new file mode 100644 index 000000000..65690f262 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/OSGI-INF/homeRepository.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/eclipse/org.argeo.cms.e4/OSGI-INF/userAdminWrapper.xml b/eclipse/org.argeo.cms.e4/OSGI-INF/userAdminWrapper.xml new file mode 100644 index 000000000..a267aa519 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/OSGI-INF/userAdminWrapper.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/eclipse/org.argeo.cms.e4/bnd.bnd b/eclipse/org.argeo.cms.e4/bnd.bnd new file mode 100644 index 000000000..e4a4519f0 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/bnd.bnd @@ -0,0 +1,15 @@ +Service-Component: OSGI-INF/homeRepository.xml,\ +OSGI-INF/userAdminWrapper.xml,\ +OSGI-INF/defaultCallbackHandler.xml +Bundle-ActivationPolicy: lazy + +Import-Package: org.eclipse.swt,\ +org.eclipse.swt.widgets;version="0.0.0",\ +org.eclipse.e4.ui.model.application.ui,\ +org.eclipse.e4.ui.model.application,\ +javax.jcr.nodetype,\ +org.argeo.cms,\ +org.eclipse.core.commands.common,\ +org.eclipse.jface.window,\ +org.argeo.cms.swt.auth,\ +* diff --git a/eclipse/org.argeo.cms.e4/build.properties b/eclipse/org.argeo.cms.e4/build.properties new file mode 100644 index 000000000..e46a7baee --- /dev/null +++ b/eclipse/org.argeo.cms.e4/build.properties @@ -0,0 +1,9 @@ +output.. = bin/ +bin.includes = META-INF/,\ + OSGI-INF/,\ + .,\ + OSGI-INF/homeRepository.xml,\ + OSGI-INF/userAdminWrapper.xml,\ + OSGI-INF/defaultCallbackHandler.xml,\ + e4xmi/cms-demo.e4xmi +source.. = src/ diff --git a/eclipse/org.argeo.cms.e4/e4xmi/cms-devops.e4xmi b/eclipse/org.argeo.cms.e4/e4xmi/cms-devops.e4xmi new file mode 100644 index 000000000..89bcc376d --- /dev/null +++ b/eclipse/org.argeo.cms.e4/e4xmi/cms-devops.e4xmi @@ -0,0 +1,129 @@ + + + + + shellMaximized + auth.cn=admin,ou=roles,ou=node + + + auth.cn=admin,ou=roles,ou=node + + + + + + + + + + + + + usersEditorArea + + + + + + + + + + + + + + + + + + + + + + + + + ViewMenu + + + + + + + + + + dataExplorer + + + + + + + + + + + + + + + + + + + + + + + + + auth.cn=admin,ou=roles,ou=node + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/eclipse/org.argeo.cms.e4/e4xmi/cms-ego.e4xmi b/eclipse/org.argeo.cms.e4/e4xmi/cms-ego.e4xmi new file mode 100644 index 000000000..ef659fcc7 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/e4xmi/cms-ego.e4xmi @@ -0,0 +1,67 @@ + + + + + shellMaximized + auth.cn=user,ou=roles,ou=node + + + + + + + + + + + ViewMenu + + + + + + + + + + dataExplorer + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/eclipse/org.argeo.cms.e4/pom.xml b/eclipse/org.argeo.cms.e4/pom.xml new file mode 100644 index 000000000..e3189313a --- /dev/null +++ b/eclipse/org.argeo.cms.e4/pom.xml @@ -0,0 +1,35 @@ + + + 4.0.0 + + org.argeo.commons + eclipse + 2.3-SNAPSHOT + .. + + org.argeo.cms.e4 + CMS E4 + jar + + + org.argeo.commons + org.argeo.cms.ui + 2.3-SNAPSHOT + + + + + org.argeo.commons.rap + org.argeo.swt.specific.rap + 2.3-SNAPSHOT + provided + + + org.argeo.tp + argeo-tp-rap-e4 + ${version.argeo-tp} + pom + provided + + + \ No newline at end of file diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/CmsE4Utils.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/CmsE4Utils.java new file mode 100644 index 000000000..21abf5828 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/CmsE4Utils.java @@ -0,0 +1,77 @@ +package org.argeo.cms.e4; + +import java.util.List; + +import org.argeo.cms.CmsException; +import org.eclipse.e4.ui.model.application.MApplication; +import org.eclipse.e4.ui.model.application.commands.MCommand; +import org.eclipse.e4.ui.model.application.ui.basic.MPart; +import org.eclipse.e4.ui.model.application.ui.menu.MDirectMenuItem; +import org.eclipse.e4.ui.model.application.ui.menu.MHandledMenuItem; +import org.eclipse.e4.ui.workbench.modeling.EModelService; +import org.eclipse.e4.ui.workbench.modeling.EPartService; +import org.eclipse.e4.ui.workbench.modeling.EPartService.PartState; +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; + +/** Static utilities simplifying recurring Eclipse 4 patterns. */ +public class CmsE4Utils { + /** Open an editor based on its id. */ + public static void openEditor(EPartService partService, String editorId, String key, String state) { + for (MPart part : partService.getParts()) { + String id = part.getPersistedState().get(key); + if (id != null && state.equals(id)) { + partService.showPart(part, PartState.ACTIVATE); + return; + } + } + + // new part + MPart part = partService.createPart(editorId); + if (part == null) + throw new CmsException("No editor found with id " + editorId); + part.getPersistedState().put(key, state); + partService.showPart(part, PartState.ACTIVATE); + } + + /** Dynamically creates an handled menu item from a command ID. */ + public static MHandledMenuItem createHandledMenuItem(EModelService modelService, MApplication app, + String commandId) { + MCommand command = findCommand(modelService, app, commandId); + if (command == null) + return null; + MHandledMenuItem handledItem = modelService.createModelElement(MHandledMenuItem.class); + handledItem.setCommand(command); + return handledItem; + + } + + /** + * Finds a command by ID. + * + * @return the {@link MCommand} or null if not found. + */ + public static MCommand findCommand(EModelService modelService, MApplication app, String commandId) { + List cmds = modelService.findElements(app, null, MCommand.class, null); + for (MCommand cmd : cmds) { + if (cmd.getElementId().equals(commandId)) { + return cmd; + } + } + return null; + } + + /** Dynamically creates a direct menu item from a class. */ + public static MDirectMenuItem createDirectMenuItem(EModelService modelService, Class clss, String label) { + MDirectMenuItem dynamicItem = modelService.createModelElement(MDirectMenuItem.class); + dynamicItem.setLabel(label); + Bundle bundle = FrameworkUtil.getBundle(clss); + dynamicItem.setContributionURI("bundleclass://" + bundle.getSymbolicName() + "/" + clss.getName()); + return dynamicItem; + } + + /** Singleton. */ + private CmsE4Utils() { + } + +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/OsgiFilterContextFunction.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/OsgiFilterContextFunction.java new file mode 100644 index 000000000..c42a02a14 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/OsgiFilterContextFunction.java @@ -0,0 +1,33 @@ +package org.argeo.cms.e4; + +import org.argeo.cms.CmsException; +import org.eclipse.e4.core.contexts.ContextFunction; +import org.eclipse.e4.core.contexts.IEclipseContext; +import org.eclipse.e4.core.di.IInjector; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; + +/** An Eclipse 4 {@link ContextFunction} based on an OSGi filter. */ +public class OsgiFilterContextFunction extends ContextFunction { + + private BundleContext bc = FrameworkUtil.getBundle(OsgiFilterContextFunction.class).getBundleContext(); + + @Override + public Object compute(IEclipseContext context, String contextKey) { + ServiceReference[] srs; + try { + srs = bc.getServiceReferences((String) null, contextKey); + } catch (InvalidSyntaxException e) { + throw new CmsException("Context key " + contextKey + " must be a valid osgi filter", e); + } + if (srs == null || srs.length == 0) { + return IInjector.NOT_A_VALUE; + } else { + // return the first one + return bc.getService(srs[0]); + } + } + +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/PrivilegedJob.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/PrivilegedJob.java new file mode 100644 index 000000000..89055d2ff --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/PrivilegedJob.java @@ -0,0 +1,49 @@ +package org.argeo.cms.e4; + +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.PrivilegedAction; + +import javax.security.auth.Subject; + +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.core.runtime.IStatus; +import org.eclipse.core.runtime.jobs.Job; + +/** + * Propagate authentication to an eclipse job. Typically to execute a privileged + * action outside the UI thread + */ +public abstract class PrivilegedJob extends Job { + private final Subject subject; + + public PrivilegedJob(String jobName) { + this(jobName, AccessController.getContext()); + } + + public PrivilegedJob(String jobName, + AccessControlContext accessControlContext) { + super(jobName); + subject = Subject.getSubject(accessControlContext); + + // Must be called *before* the job is scheduled, + // it is required for the progress window to appear + setUser(true); + } + + @Override + protected IStatus run(final IProgressMonitor progressMonitor) { + PrivilegedAction privilegedAction = new PrivilegedAction() { + public IStatus run() { + return doRun(progressMonitor); + } + }; + return Subject.doAs(subject, privilegedAction); + } + + /** + * Implement here what should be executed with default context + * authentication + */ + protected abstract IStatus doRun(IProgressMonitor progressMonitor); +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/AuthAddon.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/AuthAddon.java new file mode 100644 index 000000000..3d57e1659 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/AuthAddon.java @@ -0,0 +1,104 @@ +package org.argeo.cms.e4.addons; + +import java.security.AccessController; +import java.util.Iterator; + +import javax.annotation.PostConstruct; +import javax.security.auth.Subject; +import javax.servlet.http.HttpServletRequest; + +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.CmsException; +import org.argeo.cms.auth.CurrentUser; +import org.eclipse.e4.ui.model.application.MApplication; +import org.eclipse.e4.ui.model.application.ui.MElementContainer; +import org.eclipse.e4.ui.model.application.ui.MUIElement; +import org.eclipse.e4.ui.model.application.ui.basic.MTrimBar; +import org.eclipse.e4.ui.model.application.ui.basic.MTrimmedWindow; +import org.eclipse.e4.ui.model.application.ui.basic.MWindow; + +public class AuthAddon { + private final static CmsLog log = CmsLog.getLog(AuthAddon.class); + + public final static String AUTH = "auth."; + + @PostConstruct + void init(MApplication application) { + Iterator windows = application.getChildren().iterator(); + boolean atLeastOneTopLevelWindowVisible = false; + windows: while (windows.hasNext()) { + MWindow window = windows.next(); + // main window + boolean windowVisible = process(window); + if (!windowVisible) { +// windows.remove(); + continue windows; + } + atLeastOneTopLevelWindowVisible = true; + // trim bars + if (window instanceof MTrimmedWindow) { + Iterator trimBars = ((MTrimmedWindow) window).getTrimBars().iterator(); + while (trimBars.hasNext()) { + MTrimBar trimBar = trimBars.next(); + if (!process(trimBar)) { + trimBars.remove(); + } + } + } + } + + if (!atLeastOneTopLevelWindowVisible) { + log.warn("No top-level window is authorized for user " + CurrentUser.getUsername() + ", logging out.."); + logout(); + } + } + + protected boolean process(MUIElement element) { + for (String tag : element.getTags()) { + if (tag.startsWith(AUTH)) { + String role = tag.substring(AUTH.length(), tag.length()); + if (!CurrentUser.isInRole(role)) { + element.setVisible(false); + element.setToBeRendered(false); + return false; + } + } + } + + // children + if (element instanceof MElementContainer) { + @SuppressWarnings("unchecked") + MElementContainer container = (MElementContainer) element; + Iterator children = container.getChildren().iterator(); + while (children.hasNext()) { + MUIElement child = children.next(); + boolean visible = process(child); + if (!visible) + children.remove(); + } + + for (Object child : container.getChildren()) { + if (child instanceof MUIElement) { + boolean visible = process((MUIElement) child); + if (!visible) + container.getChildren().remove(child); + } + } + } + + return true; + } + + protected void logout() { + Subject subject = Subject.getSubject(AccessController.getContext()); + try { + CurrentUser.logoutCmsSession(subject); + } catch (Exception e) { + throw new CmsException("Cannot log out", e); + } + HttpServletRequest request = org.argeo.eclipse.ui.specific.UiContext.getHttpRequest(); + if (request != null) + request.getSession().setMaxInactiveInterval(0); + } + +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/LocaleAddon.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/LocaleAddon.java new file mode 100644 index 000000000..5bc0d6936 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/LocaleAddon.java @@ -0,0 +1,51 @@ +package org.argeo.cms.e4.addons; + +import java.security.AccessController; +import java.util.List; +import java.util.Locale; +import java.util.Set; + +import javax.annotation.PostConstruct; +import javax.security.auth.Subject; + +import org.argeo.eclipse.ui.specific.UiContext; +import org.eclipse.e4.core.services.nls.ILocaleChangeService; +import org.eclipse.e4.ui.model.application.MApplication; +import org.eclipse.e4.ui.model.application.ui.basic.MWindow; +import org.eclipse.e4.ui.workbench.modeling.EModelService; +import org.eclipse.e4.ui.workbench.modeling.ElementMatcher; +import org.eclipse.swt.SWT; + +/** Integrate workbench with the locale provided at log in. */ +public class LocaleAddon { + private final static String STYLE_OVERRIDE = "styleOverride"; + + // Right to left languages + private final static String ARABIC = "ar"; + private final static String HEBREW = "he"; + + @PostConstruct + public void init(ILocaleChangeService localeChangeService, EModelService modelService, MApplication application) { + Subject subject = Subject.getSubject(AccessController.getContext()); + Set locales = subject.getPublicCredentials(Locale.class); + if (!locales.isEmpty()) { + Locale locale = locales.iterator().next(); + localeChangeService.changeApplicationLocale(locale); + UiContext.setLocale(locale); + + if (locale.getLanguage().equals(ARABIC) || locale.getLanguage().equals(HEBREW)) { + List windows = modelService.findElements(application, MWindow.class, EModelService.ANYWHERE, + new ElementMatcher(null, null, (String) null)); + for (MWindow window : windows) { + String currentStyle = window.getPersistedState().get(STYLE_OVERRIDE); + int style = 0; + if (currentStyle != null) { + style = Integer.parseInt(currentStyle); + } + style = style | SWT.RIGHT_TO_LEFT; + window.getPersistedState().put(STYLE_OVERRIDE, Integer.toString(style)); + } + } + } + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/package-info.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/package-info.java new file mode 100644 index 000000000..6367b42d5 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/package-info.java @@ -0,0 +1,2 @@ +/** Eclipse 4 addons to integrate with Argeo CMS. */ +package org.argeo.cms.e4.addons; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/files/NodeFsBrowserView.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/files/NodeFsBrowserView.java new file mode 100644 index 000000000..cb9f9b97a --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/files/NodeFsBrowserView.java @@ -0,0 +1,50 @@ +package org.argeo.cms.e4.files; + +import java.net.URI; +import java.nio.file.FileSystem; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.spi.FileSystemProvider; + +import javax.annotation.PostConstruct; +import javax.inject.Inject; + +import org.argeo.cms.CmsException; +import org.argeo.cms.jcr.CmsJcrUtils; +import org.argeo.eclipse.ui.fs.AdvancedFsBrowser; +import org.argeo.eclipse.ui.fs.SimpleFsBrowser; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; + +/** Browse the node file system. */ +public class NodeFsBrowserView { + // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + + // ".nodeFsBrowserView"; + + @Inject + FileSystemProvider nodeFileSystemProvider; + + @PostConstruct + public void createPartControl(Composite parent) { + try { + //URI uri = new URI("node://root:demo@localhost:7070/"); + URI uri = new URI("node:///"); + FileSystem fileSystem = nodeFileSystemProvider.getFileSystem(uri); + if (fileSystem == null) + fileSystem = nodeFileSystemProvider.newFileSystem(uri, null); + Path nodePath = fileSystem.getPath("/"); + + Path localPath = Paths.get(System.getProperty("user.home")); + + SimpleFsBrowser browser = new SimpleFsBrowser(parent, SWT.NO_FOCUS); + browser.setInput(nodePath, localPath); +// AdvancedFsBrowser browser = new AdvancedFsBrowser(); +// browser.createUi(parent, localPath); + } catch (Exception e) { + throw new CmsException("Cannot open file system browser", e); + } + } + + public void setFocus() { + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/files/package-info.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/files/package-info.java new file mode 100644 index 000000000..b481dd48a --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/files/package-info.java @@ -0,0 +1,2 @@ +/** Files browser perspective. */ +package org.argeo.cms.e4.files; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangeLanguage.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangeLanguage.java new file mode 100644 index 000000000..416df7df1 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangeLanguage.java @@ -0,0 +1,13 @@ +package org.argeo.cms.e4.handlers; + +import java.util.Locale; + +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.e4.core.services.nls.ILocaleChangeService; + +public class ChangeLanguage { + @Execute + public void execute(ILocaleChangeService localeChangeService) { + localeChangeService.changeApplicationLocale(Locale.FRENCH); + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangePassword.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangePassword.java new file mode 100644 index 000000000..0ecd0a155 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangePassword.java @@ -0,0 +1,137 @@ +package org.argeo.cms.e4.handlers; + +import static org.argeo.cms.CmsMsg.changePassword; +import static org.argeo.cms.CmsMsg.currentPassword; +import static org.argeo.cms.CmsMsg.newPassword; +import static org.argeo.cms.CmsMsg.passwordChanged; +import static org.argeo.cms.CmsMsg.repeatNewPassword; + +import java.util.Arrays; + +import javax.inject.Inject; +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; + +import org.argeo.cms.CmsException; +import org.argeo.cms.auth.CurrentUser; +import org.argeo.cms.security.CryptoKeyring; +import org.argeo.cms.swt.dialogs.CmsMessageDialog; +import org.argeo.eclipse.ui.dialogs.ErrorFeedback; +import org.argeo.osgi.transaction.WorkTransaction; +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.e4.core.di.annotations.Optional; +import org.eclipse.jface.dialogs.Dialog; +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.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; +import org.osgi.service.useradmin.User; +import org.osgi.service.useradmin.UserAdmin; + +/** Change the password of the logged-in user. */ +public class ChangePassword { + @Inject + private UserAdmin userAdmin; + @Inject + private WorkTransaction userTransaction; + @Inject + @Optional + private CryptoKeyring keyring = null; + + @Execute + public void execute() { + ChangePasswordDialog dialog = new ChangePasswordDialog(Display.getCurrent().getActiveShell(), userAdmin); + if (dialog.open() == Dialog.OK) { + new CmsMessageDialog(Display.getCurrent().getActiveShell(), passwordChanged.lead(), + CmsMessageDialog.INFORMATION).open(); + } + } + + protected void changePassword(char[] oldPassword, char[] newPassword) { + String name = CurrentUser.getUsername(); + LdapName dn; + try { + dn = new LdapName(name); + } catch (InvalidNameException e) { + throw new CmsException("Invalid user dn " + name, e); + } + User user = (User) userAdmin.getRole(dn.toString()); + if (!user.hasCredential(null, oldPassword)) + throw new CmsException("Invalid password"); + if (Arrays.equals(newPassword, new char[0])) + throw new CmsException("New password empty"); + try { + userTransaction.begin(); + user.getCredentials().put(null, newPassword); + if (keyring != null) { + keyring.changePassword(oldPassword, newPassword); + // TODO change secret keys in the CMS session + } + userTransaction.commit(); + } catch (Exception e) { + try { + userTransaction.rollback(); + } catch (Exception e1) { + e1.printStackTrace(); + } + if (e instanceof RuntimeException) + throw (RuntimeException) e; + else + throw new CmsException("Cannot change password", e); + } + } + + class ChangePasswordDialog extends CmsMessageDialog { + private Text oldPassword, newPassword1, newPassword2; + + public ChangePasswordDialog(Shell parentShell, UserAdmin securityService) { + super(parentShell, changePassword.lead(), CONFIRM); + } + +// protected Point getInitialSize() { +// return new Point(400, 450); +// } + + protected Control createDialogArea(Composite parent) { + Composite dialogarea = (Composite) super.createDialogArea(parent); + dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + Composite composite = new Composite(dialogarea, SWT.NONE); + composite.setLayout(new GridLayout(2, false)); + composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + oldPassword = createLP(composite, currentPassword.lead()); + newPassword1 = createLP(composite, newPassword.lead()); + newPassword2 = createLP(composite, repeatNewPassword.lead()); + +// parent.pack(); + oldPassword.setFocus(); + return composite; + } + + @Override + protected void okPressed() { + try { + if (!newPassword1.getText().equals(newPassword2.getText())) + throw new CmsException("New passwords are different"); + changePassword(oldPassword.getTextChars(), newPassword1.getTextChars()); + closeShell(OK); + } catch (Exception e) { + ErrorFeedback.show("Cannot change password", e); + } + } + + /** Creates label and password. */ + protected Text createLP(Composite parent, String label) { + new Label(parent, SWT.NONE).setText(label); + Text text = new Text(parent, SWT.SINGLE | SWT.LEAD | SWT.PASSWORD | SWT.BORDER); + text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + return text; + } + + } + +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseAllParts.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseAllParts.java new file mode 100644 index 000000000..d11c0412c --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseAllParts.java @@ -0,0 +1,37 @@ +package org.argeo.cms.e4.handlers; + +import org.eclipse.e4.core.di.annotations.CanExecute; +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.e4.ui.model.application.ui.basic.MPart; +import org.eclipse.e4.ui.workbench.modeling.EPartService; + +public class CloseAllParts { + + @Execute + void execute(EPartService partService) { + for (MPart part : partService.getParts()) { + if (part.isCloseable()) { + if (part.isDirty()) { + if (partService.savePart(part, true)) { + partService.hidePart(part, true); + } + } else { + partService.hidePart(part, true); + } + } + } + } + + @CanExecute + boolean canExecute(EPartService partService) { + boolean atLeastOnePart = false; + for (MPart part : partService.getParts()) { + if (part.isVisible() && part.isCloseable()) { + atLeastOnePart = true; + break; + } + } + return atLeastOnePart; + } + +} \ No newline at end of file diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseWorkbench.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseWorkbench.java new file mode 100644 index 000000000..a365f3d7d --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseWorkbench.java @@ -0,0 +1,28 @@ +package org.argeo.cms.e4.handlers; + +import java.security.AccessController; + +import javax.security.auth.Subject; + +import org.argeo.cms.CmsException; +import org.argeo.cms.auth.CurrentUser; +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.e4.ui.workbench.IWorkbench; + +public class CloseWorkbench { + @Execute + public void execute(IWorkbench workbench) { + logout(); + workbench.close(); + } + + protected void logout() { + Subject subject = Subject.getSubject(AccessController.getContext()); + try { + CurrentUser.logoutCmsSession(subject); + } catch (Exception e) { + throw new CmsException("Cannot log out", e); + } + } + +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/DoNothing.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/DoNothing.java new file mode 100644 index 000000000..358494c5b --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/DoNothing.java @@ -0,0 +1,10 @@ +package org.argeo.cms.e4.handlers; + +import org.eclipse.e4.core.di.annotations.Execute; + +public class DoNothing { + @Execute + public void execute() { + + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/LanguageMenuContribution.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/LanguageMenuContribution.java new file mode 100644 index 000000000..ac825bb0d --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/LanguageMenuContribution.java @@ -0,0 +1,29 @@ + +package org.argeo.cms.e4.handlers; + +import java.util.Date; +import java.util.List; + +import org.eclipse.e4.ui.di.AboutToHide; +import org.eclipse.e4.ui.di.AboutToShow; +import org.eclipse.e4.ui.model.application.ui.menu.MDirectMenuItem; +import org.eclipse.e4.ui.model.application.ui.menu.MMenuElement; +import org.eclipse.e4.ui.workbench.modeling.EModelService; + +public class LanguageMenuContribution { + @AboutToShow + public void aboutToShow(List items, EModelService modelService) { + MDirectMenuItem dynamicItem = modelService.createModelElement(MDirectMenuItem.class); + dynamicItem.setLabel("Dynamic Menu Item (" + new Date() + ")"); + //dynamicItem.setContributorURI("platform:/plugin/org.argeo.cms.e4"); + //dynamicItem.setContributionURI("bundleclass://org.argeo.cms.e4/" + ChangeLanguage.class.getName()); + dynamicItem.setEnabled(true); + dynamicItem.setContributionURI("bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.handlers.ChangeLanguage"); + items.add(dynamicItem); + } + + @AboutToHide + public void aboutToHide() { + + } +} \ No newline at end of file diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/OpenPerspective.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/OpenPerspective.java new file mode 100644 index 000000000..ac544b107 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/OpenPerspective.java @@ -0,0 +1,31 @@ +package org.argeo.cms.e4.handlers; + +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Named; + +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.e4.ui.model.application.MApplication; +import org.eclipse.e4.ui.model.application.ui.advanced.MPerspective; +import org.eclipse.e4.ui.workbench.modeling.EModelService; +import org.eclipse.e4.ui.workbench.modeling.EPartService; + +public class OpenPerspective { + @Inject + MApplication application; + @Inject + EPartService partService; + @Inject + EModelService modelService; + + @Execute + public void execute(@Named("perspectiveId") String perspectiveId) { + List perspectives = modelService.findElements(application, perspectiveId, MPerspective.class, + null); + if (perspectives.size() == 0) + return; + MPerspective perspective = perspectives.get(0); + partService.switchPerspective(perspective); + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SaveAllParts.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SaveAllParts.java new file mode 100644 index 000000000..3b60abd7e --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SaveAllParts.java @@ -0,0 +1,19 @@ +package org.argeo.cms.e4.handlers; + +import org.eclipse.e4.core.di.annotations.CanExecute; +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.e4.ui.workbench.modeling.EPartService; + +public class SaveAllParts { + + @Execute + void execute(EPartService partService) { + partService.saveAll(false); + } + + @CanExecute + boolean canExecute(EPartService partService) { + return partService.getDirtyParts().size() > 0; + } + +} \ No newline at end of file diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SavePart.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SavePart.java new file mode 100644 index 000000000..73486f363 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SavePart.java @@ -0,0 +1,18 @@ +package org.argeo.cms.e4.handlers; + +import org.eclipse.e4.core.di.annotations.CanExecute; +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.e4.ui.model.application.ui.basic.MPart; +import org.eclipse.e4.ui.workbench.modeling.EPartService; + +public class SavePart { + @Execute + void execute(EPartService partService, MPart part) { + partService.savePart(part, false); + } + + @CanExecute + boolean canExecute(MPart part) { + return part.isDirty(); + } +} \ No newline at end of file diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/package-info.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/package-info.java new file mode 100644 index 000000000..a44ca9056 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/package-info.java @@ -0,0 +1,2 @@ +/** Generic Eclipse 4 handlers. */ +package org.argeo.cms.e4.handlers; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/GenericPropertyPage.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/GenericPropertyPage.java new file mode 100644 index 000000000..e17f17bb7 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/GenericPropertyPage.java @@ -0,0 +1,141 @@ +package org.argeo.cms.e4.jcr; + +import java.util.ArrayList; +import java.util.List; + +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.PropertyIterator; +import javax.jcr.RepositoryException; + +import org.argeo.cms.ui.jcr.PropertyLabelProvider; +import org.argeo.eclipse.ui.EclipseUiException; +import org.eclipse.jface.layout.TreeColumnLayout; +import org.eclipse.jface.viewers.ColumnWeightData; +import org.eclipse.jface.viewers.IBaseLabelProvider; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.ScrolledComposite; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.TreeColumn; + +/** + * Generic editor property page. Lists all properties of current node as a + * complex tree. TODO: enable editing + */ +public class GenericPropertyPage { + + // Main business Objects + private Node currentNode; + + public GenericPropertyPage(Node currentNode) { + this.currentNode = currentNode; + } + + protected void createFormContent(Composite parent) { + Composite innerBox = new Composite(parent, SWT.NONE); + // Composite innerBox = new Composite(body, SWT.NO_FOCUS); + FillLayout layout = new FillLayout(); + layout.marginHeight = 5; + layout.marginWidth = 5; + innerBox.setLayout(layout); + createComplexTree(innerBox); + // TODO TreeColumnLayout triggers a scroll issue with the form: + // The inside body is always to big and a scroll bar is shown + // Composite tableCmp = new Composite(body, SWT.NO_FOCUS); + // createComplexTree(tableCmp); + } + + private TreeViewer createComplexTree(Composite parent) { + int style = SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION; + Tree tree = new Tree(parent, style); + TreeColumnLayout tableColumnLayout = new TreeColumnLayout(); + + createColumn(tree, tableColumnLayout, "Property", SWT.LEFT, 200, 30); + createColumn(tree, tableColumnLayout, "Value(s)", SWT.LEFT, 300, 60); + createColumn(tree, tableColumnLayout, "Type", SWT.LEFT, 75, 10); + createColumn(tree, tableColumnLayout, "Attributes", SWT.LEFT, 75, 0); + // Do not apply the treeColumnLayout it does not work yet + // parent.setLayout(tableColumnLayout); + + tree.setLinesVisible(true); + tree.setHeaderVisible(true); + + TreeViewer treeViewer = new TreeViewer(tree); + treeViewer.setContentProvider(new TreeContentProvider()); + treeViewer.setLabelProvider((IBaseLabelProvider) new PropertyLabelProvider()); + treeViewer.setInput(currentNode); + treeViewer.expandAll(); + return treeViewer; + } + + private static TreeColumn createColumn(Tree parent, TreeColumnLayout tableColumnLayout, String name, int style, + int width, int weight) { + TreeColumn column = new TreeColumn(parent, style); + column.setText(name); + column.setWidth(width); + column.setMoveable(true); + column.setResizable(true); + tableColumnLayout.setColumnData(column, new ColumnWeightData(weight, width, true)); + return column; + } + + private class TreeContentProvider implements ITreeContentProvider { + private static final long serialVersionUID = -6162736530019406214L; + + public Object[] getElements(Object parent) { + Object[] props = null; + try { + + if (parent instanceof Node) { + Node node = (Node) parent; + PropertyIterator pi; + pi = node.getProperties(); + List propList = new ArrayList(); + while (pi.hasNext()) { + propList.add(pi.nextProperty()); + } + props = propList.toArray(); + } + } catch (RepositoryException e) { + throw new EclipseUiException("Unexpected exception while listing node properties", e); + } + return props; + } + + public Object getParent(Object child) { + return null; + } + + public Object[] getChildren(Object parent) { + if (parent instanceof Property) { + Property prop = (Property) parent; + try { + if (prop.isMultiple()) + return prop.getValues(); + } catch (RepositoryException e) { + throw new EclipseUiException("Cannot get multi-prop values on " + prop, e); + } + } + return null; + } + + public boolean hasChildren(Object parent) { + try { + return (parent instanceof Property && ((Property) parent).isMultiple()); + } catch (RepositoryException e) { + throw new EclipseUiException("Cannot check if property is multiple for " + parent, e); + } + } + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + + public void dispose() { + } + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrBrowserView.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrBrowserView.java new file mode 100644 index 000000000..98e80936d --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrBrowserView.java @@ -0,0 +1,349 @@ +package org.argeo.cms.e4.jcr; + +import java.util.List; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.inject.Inject; +import javax.jcr.Property; +import javax.jcr.PropertyType; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.RepositoryFactory; +import javax.jcr.Session; +import javax.jcr.Value; +import javax.jcr.observation.Event; +import javax.jcr.observation.EventListener; +import javax.jcr.observation.ObservationManager; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.cms.CmsException; +import org.argeo.cms.security.CryptoKeyring; +import org.argeo.cms.security.Keyring; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.ui.jcr.JcrBrowserUtils; +import org.argeo.cms.ui.jcr.NodeContentProvider; +import org.argeo.cms.ui.jcr.NodeLabelProvider; +import org.argeo.cms.ui.jcr.OsgiRepositoryRegister; +import org.argeo.cms.ui.jcr.PropertiesContentProvider; +import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem; +import org.argeo.eclipse.ui.EclipseUiException; +import org.argeo.eclipse.ui.TreeParent; +import org.argeo.eclipse.ui.jcr.AsyncUiEventListener; +import org.argeo.eclipse.ui.jcr.util.NodeViewerComparer; +import org.argeo.jcr.JcrUtils; +import org.eclipse.e4.core.contexts.IEclipseContext; +import org.eclipse.e4.core.di.annotations.Optional; +import org.eclipse.e4.ui.services.EMenuService; +import org.eclipse.e4.ui.workbench.modeling.EPartService; +import org.eclipse.e4.ui.workbench.modeling.ESelectionService; +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.jface.viewers.IBaseLabelProvider; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.SashForm; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; + +/** + * Basic View to display a sash form to browse a JCR compliant multiple + * repository environment + */ +public class JcrBrowserView { + final static String ID = "org.argeo.cms.e4.jcrbrowser"; + final static String NODE_VIEWER_POPUP_MENU_ID = "org.argeo.cms.e4.popupmenu.nodeViewer"; + + private boolean sortChildNodes = true; + + /* DEPENDENCY INJECTION */ + @Inject + @Optional + private Keyring keyring; + @Inject + private RepositoryFactory repositoryFactory; + @Inject + private Repository nodeRepository; + + // Current user session on the home repository default workspace + private Session userSession; + + private OsgiRepositoryRegister repositoryRegister = new OsgiRepositoryRegister(); + + // This page widgets + private TreeViewer nodesViewer; + private NodeContentProvider nodeContentProvider; + private TableViewer propertiesViewer; + private EventListener resultsObserver; + + @PostConstruct + public void createPartControl(Composite parent, IEclipseContext context, EPartService partService, + ESelectionService selectionService, EMenuService menuService) { + repositoryRegister.init(); + + parent.setLayout(new FillLayout()); + SashForm sashForm = new SashForm(parent, SWT.VERTICAL); + // sashForm.setSashWidth(4); + // sashForm.setLayout(new FillLayout()); + + // Create the tree on top of the view + Composite top = new Composite(sashForm, SWT.NONE); + // GridLayout gl = new GridLayout(1, false); + top.setLayout(CmsSwtUtils.noSpaceGridLayout()); + + try { + this.userSession = this.nodeRepository.login(CmsConstants.HOME_WORKSPACE); + } catch (RepositoryException e) { + throw new CmsException("Cannot open user session", e); + } + + nodeContentProvider = new NodeContentProvider(userSession, keyring, repositoryRegister, repositoryFactory, + sortChildNodes); + + // nodes viewer + nodesViewer = createNodeViewer(top, nodeContentProvider); + + // context menu : it is completely defined in the plugin.xml file. + // MenuManager menuManager = new MenuManager(); + // Menu menu = menuManager.createContextMenu(nodesViewer.getTree()); + + // nodesViewer.getTree().setMenu(menu); + + nodesViewer.setInput(""); + + // Create the property viewer on the bottom + Composite bottom = new Composite(sashForm, SWT.NONE); + bottom.setLayout(CmsSwtUtils.noSpaceGridLayout()); + propertiesViewer = createPropertiesViewer(bottom); + + sashForm.setWeights(getWeights()); + nodesViewer.setComparer(new NodeViewerComparer()); + nodesViewer.addSelectionChangedListener(new ISelectionChangedListener() { + public void selectionChanged(SelectionChangedEvent event) { + IStructuredSelection selection = (IStructuredSelection) event.getSelection(); + selectionService.setSelection(selection.toList()); + } + }); + nodesViewer.addDoubleClickListener(new JcrE4DClickListener(nodesViewer, partService)); + menuService.registerContextMenu(nodesViewer.getControl(), NODE_VIEWER_POPUP_MENU_ID); + // getSite().registerContextMenu(menuManager, nodesViewer); + // getSite().setSelectionProvider(nodesViewer); + } + + @PreDestroy + public void dispose() { + JcrUtils.logoutQuietly(userSession); + repositoryRegister.destroy(); + } + + public void refresh(Object obj) { + // Enable full refresh from a command when no element of the tree is + // selected + if (obj == null) { + Object[] elements = nodeContentProvider.getElements(null); + for (Object el : elements) { + if (el instanceof TreeParent) + JcrBrowserUtils.forceRefreshIfNeeded((TreeParent) el); + getNodeViewer().refresh(el); + } + } else + getNodeViewer().refresh(obj); + } + + /** + * To be overridden to adapt size of form and result frames. + */ + protected int[] getWeights() { + return new int[] { 70, 30 }; + } + + protected TreeViewer createNodeViewer(Composite parent, final ITreeContentProvider nodeContentProvider) { + + final TreeViewer tmpNodeViewer = new TreeViewer(parent, SWT.MULTI); + + tmpNodeViewer.getTree().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + tmpNodeViewer.setContentProvider(nodeContentProvider); + tmpNodeViewer.setLabelProvider((IBaseLabelProvider) new NodeLabelProvider()); + tmpNodeViewer.addSelectionChangedListener(new ISelectionChangedListener() { + public void selectionChanged(SelectionChangedEvent event) { + if (!event.getSelection().isEmpty()) { + IStructuredSelection sel = (IStructuredSelection) event.getSelection(); + Object firstItem = sel.getFirstElement(); + if (firstItem instanceof SingleJcrNodeElem) + propertiesViewer.setInput(((SingleJcrNodeElem) firstItem).getNode()); + } else { + propertiesViewer.setInput(""); + } + } + }); + + resultsObserver = new TreeObserver(tmpNodeViewer.getTree().getDisplay()); + if (keyring != null) + try { + ObservationManager observationManager = userSession.getWorkspace().getObservationManager(); + observationManager.addEventListener(resultsObserver, Event.PROPERTY_ADDED | Event.PROPERTY_CHANGED, "/", + true, null, null, false); + } catch (RepositoryException e) { + throw new EclipseUiException("Cannot register listeners", e); + } + + // tmpNodeViewer.addDoubleClickListener(new JcrDClickListener(tmpNodeViewer)); + return tmpNodeViewer; + } + + protected TableViewer createPropertiesViewer(Composite parent) { + propertiesViewer = new TableViewer(parent, SWT.NONE); + propertiesViewer.getTable().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + propertiesViewer.getTable().setHeaderVisible(true); + propertiesViewer.setContentProvider(new PropertiesContentProvider()); + TableViewerColumn col = new TableViewerColumn(propertiesViewer, SWT.NONE); + col.getColumn().setText("Name"); + col.getColumn().setWidth(200); + col.setLabelProvider(new ColumnLabelProvider() { + private static final long serialVersionUID = -6684361063107478595L; + + public String getText(Object element) { + try { + return ((Property) element).getName(); + } catch (RepositoryException e) { + throw new EclipseUiException("Unexpected exception in label provider", e); + } + } + }); + col = new TableViewerColumn(propertiesViewer, SWT.NONE); + col.getColumn().setText("Value"); + col.getColumn().setWidth(400); + col.setLabelProvider(new ColumnLabelProvider() { + private static final long serialVersionUID = -8201994187693336657L; + + public String getText(Object element) { + try { + Property property = (Property) element; + if (property.getType() == PropertyType.BINARY) + return ""; + else if (property.isMultiple()) { + StringBuffer buf = new StringBuffer("["); + Value[] values = property.getValues(); + for (int i = 0; i < values.length; i++) { + if (i != 0) + buf.append(", "); + buf.append(values[i].getString()); + } + buf.append(']'); + return buf.toString(); + } else + return property.getValue().getString(); + } catch (RepositoryException e) { + throw new EclipseUiException("Unexpected exception in label provider", e); + } + } + }); + col = new TableViewerColumn(propertiesViewer, SWT.NONE); + col.getColumn().setText("Type"); + col.getColumn().setWidth(200); + col.setLabelProvider(new ColumnLabelProvider() { + private static final long serialVersionUID = -6009599998150286070L; + + public String getText(Object element) { + return JcrBrowserUtils.getPropertyTypeAsString((Property) element); + } + }); + propertiesViewer.setInput(""); + return propertiesViewer; + } + + protected TreeViewer getNodeViewer() { + return nodesViewer; + } + + /** + * Resets the tree content provider + * + * @param sortChildNodes if true the content provider will use a comparer to + * sort nodes that might slow down the display + */ + public void setSortChildNodes(boolean sortChildNodes) { + this.sortChildNodes = sortChildNodes; + ((NodeContentProvider) nodesViewer.getContentProvider()).setSortChildren(sortChildNodes); + nodesViewer.setInput(""); + } + + /** Notifies the current view that a node has been added */ + public void nodeAdded(TreeParent parentNode) { + // insure that Ui objects have been correctly created: + JcrBrowserUtils.forceRefreshIfNeeded(parentNode); + getNodeViewer().refresh(parentNode); + getNodeViewer().expandToLevel(parentNode, 1); + } + + /** Notifies the current view that a node has been removed */ + public void nodeRemoved(TreeParent parentNode) { + IStructuredSelection newSel = new StructuredSelection(parentNode); + getNodeViewer().setSelection(newSel, true); + // Force refresh + IStructuredSelection tmpSel = (IStructuredSelection) getNodeViewer().getSelection(); + getNodeViewer().refresh(tmpSel.getFirstElement()); + } + + class TreeObserver extends AsyncUiEventListener { + + public TreeObserver(Display display) { + super(display); + } + + @Override + protected Boolean willProcessInUiThread(List events) throws RepositoryException { + for (Event event : events) { + if (getLog().isTraceEnabled()) + getLog().debug("Received event " + event); + String path = event.getPath(); + int index = path.lastIndexOf('/'); + String propertyName = path.substring(index + 1); + if (getLog().isTraceEnabled()) + getLog().debug("Concerned property " + propertyName); + } + return false; + } + + protected void onEventInUiThread(List events) throws RepositoryException { + if (getLog().isTraceEnabled()) + getLog().trace("Refresh result list"); + nodesViewer.refresh(); + } + + } + + public boolean getSortChildNodes() { + return sortChildNodes; + } + + public void setFocus() { + getNodeViewer().getTree().setFocus(); + } + + /* DEPENDENCY INJECTION */ + // public void setRepositoryRegister(RepositoryRegister repositoryRegister) { + // this.repositoryRegister = repositoryRegister; + // } + + public void setKeyring(CryptoKeyring keyring) { + this.keyring = keyring; + } + + public void setRepositoryFactory(RepositoryFactory repositoryFactory) { + this.repositoryFactory = repositoryFactory; + } + + public void setNodeRepository(Repository nodeRepository) { + this.nodeRepository = nodeRepository; + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrE4DClickListener.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrE4DClickListener.java new file mode 100644 index 000000000..ad6a547da --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrE4DClickListener.java @@ -0,0 +1,36 @@ +package org.argeo.cms.e4.jcr; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.cms.CmsException; +import org.argeo.cms.ui.jcr.JcrDClickListener; +import org.eclipse.e4.ui.model.application.ui.basic.MPart; +import org.eclipse.e4.ui.workbench.modeling.EPartService; +import org.eclipse.e4.ui.workbench.modeling.EPartService.PartState; +import org.eclipse.jface.viewers.TreeViewer; + +public class JcrE4DClickListener extends JcrDClickListener { + EPartService partService; + + public JcrE4DClickListener(TreeViewer nodeViewer, EPartService partService) { + super(nodeViewer); + this.partService = partService; + } + + @Override + protected void openNode(Node node) { + MPart part = partService.createPart(JcrNodeEditor.DESCRIPTOR_ID); + try { + part.setLabel(node.getName()); + part.getPersistedState().put("nodeWorkspace", node.getSession().getWorkspace().getName()); + part.getPersistedState().put("nodePath", node.getPath()); + } catch (RepositoryException e) { + throw new CmsException("Cannot open " + node, e); + } + + // the provided part is be shown + partService.showPart(part, PartState.ACTIVATE); + } + +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrNodeEditor.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrNodeEditor.java new file mode 100644 index 000000000..ae2b325f5 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrNodeEditor.java @@ -0,0 +1,26 @@ +package org.argeo.cms.e4.jcr; + +import java.util.List; + +import javax.annotation.PostConstruct; +import javax.jcr.Node; + +import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem; +import org.eclipse.e4.ui.model.application.ui.basic.MPart; +import org.eclipse.e4.ui.workbench.modeling.ESelectionService; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.widgets.Composite; + +public class JcrNodeEditor { + final static String DESCRIPTOR_ID = "org.argeo.cms.e4.partdescriptor.nodeEditor"; + + @PostConstruct + public void createUi(Composite parent, MPart part, ESelectionService selectionService) { + parent.setLayout(new FillLayout()); + List selection = (List) selectionService.getSelection(); + Node node = ((SingleJcrNodeElem) selection.get(0)).getNode(); + GenericPropertyPage propertyPage = new GenericPropertyPage(node); + propertyPage.createFormContent(parent); + } + +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/SimplePart.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/SimplePart.java new file mode 100644 index 000000000..17d8d2a23 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/SimplePart.java @@ -0,0 +1,19 @@ +package org.argeo.cms.e4.jcr; + +import javax.annotation.PostConstruct; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; + +public class SimplePart { + + @PostConstruct + void init(Composite parent) { + parent.setLayout(new GridLayout()); + Label label = new Label(parent, SWT.NONE); + label.setText("Hello e4 World"); + } + +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/AddFolderNode.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/AddFolderNode.java new file mode 100644 index 000000000..8f5bc36e1 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/AddFolderNode.java @@ -0,0 +1,67 @@ +package org.argeo.cms.e4.jcr.handlers; + +import java.util.List; + +import javax.inject.Named; +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.nodetype.NodeType; + +import org.argeo.cms.e4.jcr.JcrBrowserView; +import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem; +import org.argeo.cms.ui.jcr.model.WorkspaceElem; +import org.argeo.eclipse.ui.TreeParent; +import org.argeo.eclipse.ui.dialogs.ErrorFeedback; +import org.argeo.eclipse.ui.dialogs.SingleValue; +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.e4.ui.model.application.ui.basic.MPart; +import org.eclipse.e4.ui.services.IServiceConstants; +import org.eclipse.e4.ui.workbench.modeling.ESelectionService; + +/** + * Adds a node of type nt:folder, only on {@link SingleJcrNodeElem} and + * {@link WorkspaceElem} TreeObject types. + * + * This handler assumes that a selection provider is available and picks only + * first selected item. It is UI's job to enable the command only when the + * selection contains one and only one element. Thus no parameter is passed + * through the command. + */ +public class AddFolderNode { + @Execute + public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part, ESelectionService selectionService) { + List selection = (List) selectionService.getSelection(); + JcrBrowserView view = (JcrBrowserView) part.getObject(); + + if (selection != null && selection.size() == 1) { + TreeParent treeParentNode = null; + Node jcrParentNode = null; + Object obj = selection.get(0); + + if (obj instanceof SingleJcrNodeElem) { + treeParentNode = (TreeParent) obj; + jcrParentNode = ((SingleJcrNodeElem) treeParentNode).getNode(); + } else if (obj instanceof WorkspaceElem) { + treeParentNode = (TreeParent) obj; + jcrParentNode = ((WorkspaceElem) treeParentNode).getRootNode(); + } else + return; + + String folderName = SingleValue.ask("Folder name", "Enter folder name"); + if (folderName != null) { + try { + jcrParentNode.addNode(folderName, NodeType.NT_FOLDER); + jcrParentNode.getSession().save(); + view.nodeAdded(treeParentNode); + } catch (RepositoryException e) { + ErrorFeedback.show("Cannot create folder " + folderName + " under " + treeParentNode, e); + } + } + } else { + // ErrorFeedback.show(WorkbenchUiPlugin + // .getMessage("errorUnvalidNtFolderNodeType")); + ErrorFeedback.show("Invalid NT folder node type"); + } + } + +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/AddRemoteRepository.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/AddRemoteRepository.java new file mode 100644 index 000000000..dc47f6edf --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/AddRemoteRepository.java @@ -0,0 +1,210 @@ +package org.argeo.cms.e4.jcr.handlers; + +import java.net.URI; +import java.util.Hashtable; + +import javax.inject.Inject; +import javax.inject.Named; +import javax.jcr.Node; +import javax.jcr.Repository; +import javax.jcr.RepositoryFactory; +import javax.jcr.Session; +import javax.jcr.SimpleCredentials; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.cms.ArgeoNames; +import org.argeo.cms.ArgeoTypes; +import org.argeo.cms.e4.jcr.JcrBrowserView; +import org.argeo.cms.jcr.CmsJcrUtils; +import org.argeo.cms.security.Keyring; +import org.argeo.eclipse.ui.EclipseUiException; +import org.argeo.eclipse.ui.dialogs.ErrorFeedback; +import org.argeo.jcr.JcrUtils; +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.e4.core.di.annotations.Optional; +import org.eclipse.e4.ui.model.application.ui.basic.MPart; +import org.eclipse.e4.ui.services.IServiceConstants; +import org.eclipse.jface.dialogs.Dialog; +import org.eclipse.jface.dialogs.IMessageProvider; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.dialogs.TitleAreaDialog; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Point; +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.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +/** + * Connect to a remote repository and, if successful publish it as an OSGi + * service. + */ +public class AddRemoteRepository { + + @Inject + private RepositoryFactory repositoryFactory; + @Inject + private Repository nodeRepository; + @Inject + @Optional + private Keyring keyring; + + @Execute + public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part) { + JcrBrowserView view = (JcrBrowserView) part.getObject(); + RemoteRepositoryLoginDialog dlg = new RemoteRepositoryLoginDialog(Display.getDefault().getActiveShell()); + if (dlg.open() == Dialog.OK) { + view.refresh(null); + } + } + + // public void setRepositoryFactory(RepositoryFactory repositoryFactory) { + // this.repositoryFactory = repositoryFactory; + // } + // + // public void setKeyring(Keyring keyring) { + // this.keyring = keyring; + // } + // + // public void setNodeRepository(Repository nodeRepository) { + // this.nodeRepository = nodeRepository; + // } + + class RemoteRepositoryLoginDialog extends TitleAreaDialog { + private static final long serialVersionUID = 2234006887750103399L; + private Text name; + private Text uri; + private Text username; + private Text password; + private Button saveInKeyring; + + public RemoteRepositoryLoginDialog(Shell parentShell) { + super(parentShell); + } + + protected Point getInitialSize() { + return new Point(600, 400); + } + + protected Control createDialogArea(Composite parent) { + Composite dialogarea = (Composite) super.createDialogArea(parent); + dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + Composite composite = new Composite(dialogarea, SWT.NONE); + composite.setLayout(new GridLayout(2, false)); + composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + setMessage("Login to remote repository", IMessageProvider.NONE); + name = createLT(composite, "Name", "remoteRepository"); + uri = createLT(composite, "URI", "http://localhost:7070/jcr/node"); + username = createLT(composite, "User", ""); + password = createLP(composite, "Password"); + + saveInKeyring = createLC(composite, "Remember password", false); + parent.pack(); + return composite; + } + + @Override + protected void createButtonsForButtonBar(Composite parent) { + super.createButtonsForButtonBar(parent); + Button test = createButton(parent, 2, "Test", false); + test.addSelectionListener(new SelectionAdapter() { + private static final long serialVersionUID = -1829962269440419560L; + + public void widgetSelected(SelectionEvent arg0) { + testConnection(); + } + }); + } + + void testConnection() { + Session session = null; + try { + URI checkedUri = new URI(uri.getText()); + String checkedUriStr = checkedUri.toString(); + + Hashtable params = new Hashtable(); + params.put(CmsConstants.LABELED_URI, checkedUriStr); + Repository repository = repositoryFactory.getRepository(params); + if (username.getText().trim().equals("")) {// anonymous + // FIXME make it more generic + session = repository.login(CmsConstants.SYS_WORKSPACE); + } else { + // FIXME use getTextChars() when upgrading to 3.7 + // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=297412 + char[] pwd = password.getText().toCharArray(); + SimpleCredentials sc = new SimpleCredentials(username.getText(), pwd); + session = repository.login(sc, "main"); + MessageDialog.openInformation(getParentShell(), "Success", + "Connection to '" + uri.getText() + "' successful"); + } + } catch (Exception e) { + ErrorFeedback.show("Connection test failed for " + uri.getText(), e); + } finally { + JcrUtils.logoutQuietly(session); + } + } + + @Override + protected void okPressed() { + Session nodeSession = null; + try { + nodeSession = nodeRepository.login(); + Node home = CmsJcrUtils.getUserHome(nodeSession); + + Node remote = home.hasNode(ArgeoNames.ARGEO_REMOTE) ? home.getNode(ArgeoNames.ARGEO_REMOTE) + : home.addNode(ArgeoNames.ARGEO_REMOTE); + if (remote.hasNode(name.getText())) + throw new EclipseUiException("There is already a remote repository named " + name.getText()); + Node remoteRepository = remote.addNode(name.getText(), ArgeoTypes.ARGEO_REMOTE_REPOSITORY); + remoteRepository.setProperty(ArgeoNames.ARGEO_URI, uri.getText()); + remoteRepository.setProperty(ArgeoNames.ARGEO_USER_ID, username.getText()); + nodeSession.save(); + if (saveInKeyring.getSelection()) { + String pwdPath = remoteRepository.getPath() + '/' + ArgeoNames.ARGEO_PASSWORD; + keyring.set(pwdPath, password.getText().toCharArray()); + } + nodeSession.save(); + MessageDialog.openInformation(getParentShell(), "Repository Added", + "Remote repository '" + username.getText() + "@" + uri.getText() + "' added"); + + super.okPressed(); + } catch (Exception e) { + ErrorFeedback.show("Cannot add remote repository", e); + } finally { + JcrUtils.logoutQuietly(nodeSession); + } + } + + /** Creates label and text. */ + protected Text createLT(Composite parent, String label, String initial) { + new Label(parent, SWT.NONE).setText(label); + Text text = new Text(parent, SWT.SINGLE | SWT.LEAD | SWT.BORDER); + text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + text.setText(initial); + return text; + } + + /** Creates label and check. */ + protected Button createLC(Composite parent, String label, Boolean initial) { + new Label(parent, SWT.NONE).setText(label); + Button check = new Button(parent, SWT.CHECK); + check.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + check.setSelection(initial); + return check; + } + + protected Text createLP(Composite parent, String label) { + new Label(parent, SWT.NONE).setText(label); + Text text = new Text(parent, SWT.SINGLE | SWT.LEAD | SWT.BORDER | SWT.PASSWORD); + text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + return text; + } + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/DeleteNodes.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/DeleteNodes.java new file mode 100644 index 000000000..1974e4d44 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/DeleteNodes.java @@ -0,0 +1,95 @@ +package org.argeo.cms.e4.jcr.handlers; + +import java.util.List; + +import javax.inject.Named; +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.cms.e4.jcr.JcrBrowserView; +import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem; +import org.argeo.cms.ui.jcr.model.WorkspaceElem; +import org.argeo.eclipse.ui.EclipseUiException; +import org.argeo.eclipse.ui.TreeParent; +import org.argeo.eclipse.ui.dialogs.ErrorFeedback; +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.e4.ui.model.application.ui.basic.MPart; +import org.eclipse.e4.ui.services.IServiceConstants; +import org.eclipse.e4.ui.workbench.modeling.ESelectionService; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.swt.widgets.Display; + +/** + * Delete the selected nodes: both in the JCR repository and in the UI view. + * Warning no check is done, except implementation dependent native checks, + * handle with care. + * + * This handler is still 'hard linked' to a GenericJcrBrowser view to enable + * correct tree refresh when a node is added. This must be corrected in future + * versions. + */ +public class DeleteNodes { + @Execute + public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part, ESelectionService selectionService) { + List selection = (List) selectionService.getSelection(); + if (selection == null) + return; + + JcrBrowserView view = (JcrBrowserView) part.getObject(); + + // confirmation + StringBuffer buf = new StringBuffer(""); + for (Object o : selection) { + SingleJcrNodeElem sjn = (SingleJcrNodeElem) o; + buf.append(sjn.getName()).append(' '); + } + Boolean doRemove = MessageDialog.openConfirm(Display.getCurrent().getActiveShell(), "Confirm deletion", + "Do you want to delete " + buf + "?"); + + // operation + if (doRemove) { + SingleJcrNodeElem ancestor = null; + WorkspaceElem rootAncestor = null; + try { + for (Object obj : selection) { + if (obj instanceof SingleJcrNodeElem) { + // Cache objects + SingleJcrNodeElem sjn = (SingleJcrNodeElem) obj; + TreeParent tp = (TreeParent) sjn.getParent(); + Node node = sjn.getNode(); + + // Jcr Remove + node.remove(); + node.getSession().save(); + // UI remove + tp.removeChild(sjn); + + // Check if the parent is the root node + if (tp instanceof WorkspaceElem) + rootAncestor = (WorkspaceElem) tp; + else + ancestor = getOlder(ancestor, (SingleJcrNodeElem) tp); + } + } + if (rootAncestor != null) + view.nodeRemoved(rootAncestor); + else if (ancestor != null) + view.nodeRemoved(ancestor); + } catch (Exception e) { + ErrorFeedback.show("Cannot delete selected node ", e); + } + } + } + + private SingleJcrNodeElem getOlder(SingleJcrNodeElem A, SingleJcrNodeElem B) { + try { + if (A == null) + return B == null ? null : B; + // Todo enhanced this method + else + return A.getNode().getDepth() <= B.getNode().getDepth() ? A : B; + } catch (RepositoryException re) { + throw new EclipseUiException("Cannot find ancestor", re); + } + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/Refresh.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/Refresh.java new file mode 100644 index 000000000..4ae072cb5 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/Refresh.java @@ -0,0 +1,43 @@ +package org.argeo.cms.e4.jcr.handlers; + +import java.util.List; + +import javax.inject.Named; + +import org.argeo.cms.e4.jcr.JcrBrowserView; +import org.argeo.cms.ui.jcr.JcrBrowserUtils; +import org.argeo.eclipse.ui.TreeParent; +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.e4.ui.model.application.ui.basic.MPart; +import org.eclipse.e4.ui.services.IServiceConstants; +import org.eclipse.e4.ui.workbench.modeling.EPartService; +import org.eclipse.e4.ui.workbench.modeling.ESelectionService; + +/** + * Force the selected objects of the active view to be refreshed doing the + * following: + *
    + *
  1. The model objects are recomputed
  2. + *
  3. the view is refreshed
  4. + *
+ */ +public class Refresh { + + @Execute + public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part, EPartService partService, + ESelectionService selectionService) { + + JcrBrowserView view = (JcrBrowserView) part.getObject(); + List selection = (List) selectionService.getSelection(); + + if (selection != null && !selection.isEmpty()) { + for (Object obj : selection) + if (obj instanceof TreeParent) { + TreeParent tp = (TreeParent) obj; + JcrBrowserUtils.forceRefreshIfNeeded(tp); + view.refresh(obj); + } + } else if (view instanceof JcrBrowserView) + view.refresh(null); // force full refresh + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/RenameNode.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/RenameNode.java new file mode 100644 index 000000000..97674abc2 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/RenameNode.java @@ -0,0 +1,59 @@ +package org.argeo.cms.e4.jcr.handlers; + +import java.util.List; + +import javax.inject.Named; +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.argeo.cms.e4.jcr.JcrBrowserView; +import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem; +import org.argeo.eclipse.ui.EclipseUiException; +import org.argeo.eclipse.ui.dialogs.SingleValue; +import org.argeo.jcr.JcrUtils; +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.e4.ui.model.application.ui.basic.MPart; +import org.eclipse.e4.ui.services.IServiceConstants; +import org.eclipse.e4.ui.workbench.modeling.EPartService; +import org.eclipse.e4.ui.workbench.modeling.ESelectionService; + +/** + * Canonically call JCR Session#move(String, String) on the first element + * returned by HandlerUtil#getActiveWorkbenchWindow() + * (...getActivePage().getSelection()), if it is a {@link SingleJcrNodeElem}. + * The user must then fill a new name in and confirm + */ +public class RenameNode { + @Execute + public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part, EPartService partService, + ESelectionService selectionService) { + List selection = (List) selectionService.getSelection(); + if (selection == null || selection.size() != 1) + return; + JcrBrowserView view = (JcrBrowserView) part.getObject(); + + Object element = selection.get(0); + if (element instanceof SingleJcrNodeElem) { + SingleJcrNodeElem sjn = (SingleJcrNodeElem) element; + Node node = sjn.getNode(); + Session session = null; + String newName = null; + String oldPath = null; + try { + newName = SingleValue.ask("New node name", "Please provide a new name for [" + node.getName() + "]"); + // TODO sanity check and user feedback + newName = JcrUtils.replaceInvalidChars(newName); + oldPath = node.getPath(); + session = node.getSession(); + session.move(oldPath, JcrUtils.parentPath(oldPath) + "/" + newName); + session.save(); + + // Manually refresh the browser view. Must be enhanced + view.refresh(sjn); + } catch (RepositoryException e) { + throw new EclipseUiException("Unable to rename " + node + " to " + newName, e); + } + } + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/package-info.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/package-info.java new file mode 100644 index 000000000..4e075e2b1 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/package-info.java @@ -0,0 +1,2 @@ +/** JCR browser handlers. */ +package org.argeo.cms.e4.jcr.handlers; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/package-info.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/package-info.java new file mode 100644 index 000000000..3e92fb04d --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/package-info.java @@ -0,0 +1,2 @@ +/** JCR browser perspective. */ +package org.argeo.cms.e4.jcr; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/AbstractOsgiComposite.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/AbstractOsgiComposite.java new file mode 100644 index 000000000..4fd1d68dc --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/AbstractOsgiComposite.java @@ -0,0 +1,41 @@ +package org.argeo.cms.e4.maintenance; + +import java.util.Collection; + +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.swt.CmsSwtUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; + +abstract class AbstractOsgiComposite extends Composite { + private static final long serialVersionUID = -4097415973477517137L; + protected final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); + protected final CmsLog log = CmsLog.getLog(getClass()); + + public AbstractOsgiComposite(Composite parent, int style) { + super(parent, style); + parent.setLayout(CmsSwtUtils.noSpaceGridLayout()); + setLayout(CmsSwtUtils.noSpaceGridLayout()); + setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + initUi(style); + } + + protected abstract void initUi(int style); + + protected T getService(Class clazz) { + return bc.getService(bc.getServiceReference(clazz)); + } + + protected Collection> getServiceReferences(Class clazz, String filter) { + try { + return bc.getServiceReferences(clazz, filter); + } catch (InvalidSyntaxException e) { + throw new IllegalArgumentException("Filter " + filter + " is invalid", e); + } + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/Browse.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/Browse.java new file mode 100644 index 000000000..260a114cd --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/Browse.java @@ -0,0 +1,576 @@ +package org.argeo.cms.e4.maintenance; + +import static org.eclipse.swt.SWT.RIGHT; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.LinkedHashMap; + +import javax.jcr.ItemNotFoundException; +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.Property; +import javax.jcr.PropertyIterator; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; +import javax.jcr.Value; + +import org.argeo.api.cms.Cms2DSize; +import org.argeo.cms.CmsException; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.ui.CmsUiProvider; +import org.argeo.cms.ui.util.CmsLink; +import org.argeo.cms.ui.widgets.EditableImage; +import org.argeo.cms.ui.widgets.Img; +import org.argeo.jcr.JcrUtils; +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.jface.viewers.ILazyContentProvider; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.ScrolledComposite; +import org.eclipse.swt.events.ControlAdapter; +import org.eclipse.swt.events.ControlEvent; +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.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +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.Label; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.swt.widgets.Text; + +public class Browse implements CmsUiProvider { + + // Some local constants to experiment. should be cleaned + private final static String BROWSE_PREFIX = "browse#"; + private final static int THUMBNAIL_WIDTH = 400; + private final static int COLUMN_WIDTH = 160; + private DateFormat timeFormatter = new SimpleDateFormat("dd-MM-yyyy', 'HH:mm"); + + // keep a cache of the opened nodes + // Key is the path + private LinkedHashMap browserCols = new LinkedHashMap(); + private Composite nodeDisplayParent; + private Composite colViewer; + private ScrolledComposite scrolledCmp; + private Text parentPathTxt; + private Text filterTxt; + private Node currEdited; + + private String initialPath; + + @Override + public Control createUi(Composite parent, Node context) throws RepositoryException { + if (context == null) + // return null; + throw new CmsException("Context cannot be null"); + GridLayout layout = CmsSwtUtils.noSpaceGridLayout(); + layout.numColumns = 2; + parent.setLayout(layout); + + // Left + Composite leftCmp = new Composite(parent, SWT.NO_FOCUS); + leftCmp.setLayoutData(CmsSwtUtils.fillAll()); + createBrowserPart(leftCmp, context); + + // Right + nodeDisplayParent = new Composite(parent, SWT.NO_FOCUS | SWT.BORDER); + GridData gd = new GridData(SWT.RIGHT, SWT.FILL, false, true); + gd.widthHint = THUMBNAIL_WIDTH; + nodeDisplayParent.setLayoutData(gd); + createNodeView(nodeDisplayParent, context); + + // INIT + setEdited(context); + initialPath = context.getPath(); + + // Workaround we don't yet manage the delete to display parent of the + // initial context node + + return null; + } + + private void createBrowserPart(Composite parent, Node context) throws RepositoryException { + GridLayout layout = CmsSwtUtils.noSpaceGridLayout(); + parent.setLayout(layout); + Composite filterCmp = new Composite(parent, SWT.NO_FOCUS); + filterCmp.setLayoutData(CmsSwtUtils.fillWidth()); + + // top filter + addFilterPanel(filterCmp); + + // scrolled composite + scrolledCmp = new ScrolledComposite(parent, SWT.H_SCROLL | SWT.BORDER | SWT.NO_FOCUS); + scrolledCmp.setLayoutData(CmsSwtUtils.fillAll()); + scrolledCmp.setExpandVertical(true); + scrolledCmp.setExpandHorizontal(true); + scrolledCmp.setShowFocusedControl(true); + + colViewer = new Composite(scrolledCmp, SWT.NO_FOCUS); + scrolledCmp.setContent(colViewer); + scrolledCmp.addControlListener(new ControlAdapter() { + private static final long serialVersionUID = 6589392045145698201L; + + @Override + public void controlResized(ControlEvent e) { + Rectangle r = scrolledCmp.getClientArea(); + scrolledCmp.setMinSize(colViewer.computeSize(SWT.DEFAULT, r.height)); + } + }); + initExplorer(colViewer, context); + } + + private Control initExplorer(Composite parent, Node context) throws RepositoryException { + parent.setLayout(CmsSwtUtils.noSpaceGridLayout()); + createBrowserColumn(parent, context); + return null; + } + + private Control createBrowserColumn(Composite parent, Node context) throws RepositoryException { + // TODO style is not correctly managed. + FilterEntitiesVirtualTable table = new FilterEntitiesVirtualTable(parent, SWT.BORDER | SWT.NO_FOCUS, context); + // CmsUiUtils.style(table, ArgeoOrgStyle.browserColumn.style()); + table.filterList("*"); + table.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, false, true)); + browserCols.put(context.getPath(), table); + return null; + } + + public void addFilterPanel(Composite parent) { + + parent.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(2, false))); + + // Text Area for the filter + parentPathTxt = new Text(parent, SWT.NO_FOCUS); + parentPathTxt.setEditable(false); + filterTxt = new Text(parent, SWT.SEARCH | SWT.ICON_CANCEL); + filterTxt.setMessage("Filter current list"); + filterTxt.setLayoutData(CmsSwtUtils.fillWidth()); + filterTxt.addModifyListener(new ModifyListener() { + private static final long serialVersionUID = 7709303319740056286L; + + public void modifyText(ModifyEvent event) { + modifyFilter(false); + } + }); + + 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; + FilterEntitiesVirtualTable currTable = null; + if (currEdited != null) { + FilterEntitiesVirtualTable table = browserCols.get(getPath(currEdited)); + if (table != null && !table.isDisposed()) + currTable = table; + } + + try { + if (e.keyCode == SWT.ARROW_DOWN) + currTable.setFocus(); + else if (e.keyCode == SWT.BS) { + if (filterTxt.getText().equals("") + && !(getPath(currEdited).equals("/") || getPath(currEdited).equals(initialPath))) { + setEdited(currEdited.getParent()); + e.doit = false; + filterTxt.setFocus(); + } + } else if (e.keyCode == SWT.TAB && !shiftPressed) { + if (currEdited.getNodes(filterTxt.getText() + "*").getSize() == 1) { + setEdited(currEdited.getNodes(filterTxt.getText() + "*").nextNode()); + } + filterTxt.setFocus(); + e.doit = false; + } + } catch (RepositoryException e1) { + throw new CmsException("Unexpected error in key management for " + currEdited + "with filter " + + filterTxt.getText(), e1); + } + + } + }); + } + + private void setEdited(Node node) { + try { + currEdited = node; + CmsSwtUtils.clear(nodeDisplayParent); + createNodeView(nodeDisplayParent, currEdited); + nodeDisplayParent.layout(); + refreshFilters(node); + refreshBrowser(node); + } catch (RepositoryException re) { + throw new CmsException("Unable to update browser for " + node, re); + } + } + + private void refreshFilters(Node node) throws RepositoryException { + String currNodePath = node.getPath(); + parentPathTxt.setText(currNodePath); + filterTxt.setText(""); + filterTxt.getParent().layout(); + } + + private void refreshBrowser(Node node) throws RepositoryException { + + // Retrieve + String currNodePath = node.getPath(); + String currParPath = ""; + if (!"/".equals(currNodePath)) + currParPath = JcrUtils.parentPath(currNodePath); + if ("".equals(currParPath)) + currParPath = "/"; + + Object[][] colMatrix = new Object[browserCols.size()][2]; + + int i = 0, j = -1, k = -1; + for (String path : browserCols.keySet()) { + colMatrix[i][0] = path; + colMatrix[i][1] = browserCols.get(path); + if (j >= 0 && k < 0 && !currNodePath.equals("/")) { + boolean leaveOpened = path.startsWith(currNodePath); + + // workaround for same name siblings + // fix me weird side effect when we go left or click on anb + // already selected, unfocused node + if (leaveOpened && (path.lastIndexOf("/") == 0 && currNodePath.lastIndexOf("/") == 0 + || JcrUtils.parentPath(path).equals(JcrUtils.parentPath(currNodePath)))) + leaveOpened = JcrUtils.lastPathElement(path).equals(JcrUtils.lastPathElement(currNodePath)); + + if (!leaveOpened) + k = i; + } + if (currParPath.equals(path)) + j = i; + i++; + } + + if (j >= 0 && k >= 0) + // remove useless cols + for (int l = i - 1; l >= k; l--) { + browserCols.remove(colMatrix[l][0]); + ((FilterEntitiesVirtualTable) colMatrix[l][1]).dispose(); + } + + // Remove disposed columns + // TODO investigate and fix the mechanism that leave them there after + // disposal + if (browserCols.containsKey(currNodePath)) { + FilterEntitiesVirtualTable currCol = browserCols.get(currNodePath); + if (currCol.isDisposed()) + browserCols.remove(currNodePath); + } + + if (!browserCols.containsKey(currNodePath)) + createBrowserColumn(colViewer, node); + + colViewer.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(browserCols.size(), false))); + // colViewer.pack(); + colViewer.layout(); + // also resize the scrolled composite + scrolledCmp.layout(); + scrolledCmp.getShowFocusedControl(); + // colViewer.getParent().layout(); + // if (JcrUtils.parentPath(currNodePath).equals(currBrowserKey)) { + // } else { + // } + } + + private void modifyFilter(boolean fromOutside) { + if (!fromOutside) + if (currEdited != null) { + String filter = filterTxt.getText() + "*"; + FilterEntitiesVirtualTable table = browserCols.get(getPath(currEdited)); + if (table != null && !table.isDisposed()) + table.filterList(filter); + } + + } + + private String getPath(Node node) { + try { + return node.getPath(); + } catch (RepositoryException e) { + throw new CmsException("Unable to get path for node " + node, e); + } + } + + private Cms2DSize imageWidth = new Cms2DSize(250, 0); + + /** + * Recreates the content of the box that displays information about the current + * selected node. + */ + private Control createNodeView(Composite parent, Node context) throws RepositoryException { + + parent.setLayout(new GridLayout(2, false)); + + if (isImg(context)) { + EditableImage image = new Img(parent, RIGHT, context, imageWidth); + image.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false, 2, 1)); + } + + // Name and primary type + Label contextL = new Label(parent, SWT.NONE); + CmsSwtUtils.markup(contextL); + contextL.setText("" + context.getName() + ""); + new Label(parent, SWT.NONE).setText(context.getPrimaryNodeType().getName()); + + // Children + for (NodeIterator nIt = context.getNodes(); nIt.hasNext();) { + Node child = nIt.nextNode(); + new CmsLink(child.getName(), BROWSE_PREFIX + child.getPath()).createUi(parent, context); + new Label(parent, SWT.NONE).setText(child.getPrimaryNodeType().getName()); + } + + // Properties + for (PropertyIterator pIt = context.getProperties(); pIt.hasNext();) { + Property property = pIt.nextProperty(); + Label label = new Label(parent, SWT.NONE); + label.setText(property.getName()); + label.setToolTipText(JcrUtils.getPropertyDefinitionAsString(property)); + new Label(parent, SWT.NONE).setText(getPropAsString(property)); + } + + return null; + } + + private boolean isImg(Node node) throws RepositoryException { + // TODO support images + return false; +// return node.hasNode(JCR_CONTENT) && node.isNodeType(CmsTypes.CMS_IMAGE); + } + + private String getPropAsString(Property property) throws RepositoryException { + String result = ""; + if (property.isMultiple()) { + result = getMultiAsString(property, ", "); + } else { + Value value = property.getValue(); + if (value.getType() == PropertyType.BINARY) + result = ""; + else if (value.getType() == PropertyType.DATE) + result = timeFormatter.format(value.getDate().getTime()); + else + result = value.getString(); + } + return result; + } + + private String getMultiAsString(Property property, String separator) throws RepositoryException { + if (separator == null) + separator = "; "; + Value[] values = property.getValues(); + StringBuilder builder = new StringBuilder(); + for (Value val : values) { + String currStr = val.getString(); + if (!"".equals(currStr.trim())) + builder.append(currStr).append(separator); + } + if (builder.lastIndexOf(separator) >= 0) + return builder.substring(0, builder.length() - separator.length()); + else + return builder.toString(); + } + + /** Almost canonical implementation of a table that display entities */ + private class FilterEntitiesVirtualTable extends Composite { + private static final long serialVersionUID = 8798147431706283824L; + + // Context + private Node context; + + // UI Objects + private TableViewer entityViewer; + + // enable management of multiple columns + Node getNode() { + return context; + } + + @Override + public boolean setFocus() { + if (entityViewer.getTable().isDisposed()) + return false; + if (entityViewer.getSelection().isEmpty()) { + Object first = entityViewer.getElementAt(0); + if (first != null) { + entityViewer.setSelection(new StructuredSelection(first), true); + } + } + return entityViewer.getTable().setFocus(); + } + + void filterList(String filter) { + try { + NodeIterator nit = context.getNodes(filter); + refreshFilteredList(nit); + } catch (RepositoryException e) { + throw new CmsException("Unable to filter " + getNode() + " children with filter " + filter, e); + } + + } + + public FilterEntitiesVirtualTable(Composite parent, int style, Node context) { + super(parent, SWT.NO_FOCUS); + this.context = context; + populate(); + } + + protected void populate() { + Composite parent = this; + GridLayout layout = CmsSwtUtils.noSpaceGridLayout(); + + this.setLayout(layout); + createTableViewer(parent); + } + + private void createTableViewer(final Composite parent) { + // the list + // We must limit the size of the table otherwise the full list is + // loaded + // before the layout happens + Composite listCmp = new Composite(parent, SWT.NO_FOCUS); + GridData gd = new GridData(SWT.LEFT, SWT.FILL, false, true); + gd.widthHint = COLUMN_WIDTH; + listCmp.setLayoutData(gd); + listCmp.setLayout(CmsSwtUtils.noSpaceGridLayout()); + + entityViewer = new TableViewer(listCmp, SWT.VIRTUAL | SWT.SINGLE); + Table table = entityViewer.getTable(); + + table.setLayoutData(CmsSwtUtils.fillAll()); + table.setLinesVisible(true); + table.setHeaderVisible(false); + CmsSwtUtils.markup(table); + + CmsSwtUtils.style(table, MaintenanceStyles.BROWSER_COLUMN); + + // first column + TableViewerColumn column = new TableViewerColumn(entityViewer, SWT.NONE); + TableColumn tcol = column.getColumn(); + tcol.setWidth(COLUMN_WIDTH); + tcol.setResizable(true); + column.setLabelProvider(new SimpleNameLP()); + + entityViewer.setContentProvider(new MyLazyCP(entityViewer)); + entityViewer.addSelectionChangedListener(new ISelectionChangedListener() { + + @Override + public void selectionChanged(SelectionChangedEvent event) { + IStructuredSelection selection = (IStructuredSelection) entityViewer.getSelection(); + if (selection.isEmpty()) + return; + else + setEdited((Node) selection.getFirstElement()); + + } + }); + + table.addKeyListener(new KeyListener() { + private static final long serialVersionUID = -330694313896036230L; + + @Override + public void keyReleased(KeyEvent e) { + } + + @Override + public void keyPressed(KeyEvent e) { + + IStructuredSelection selection = (IStructuredSelection) entityViewer.getSelection(); + Node selected = null; + if (!selection.isEmpty()) + selected = ((Node) selection.getFirstElement()); + try { + if (e.keyCode == SWT.ARROW_RIGHT) { + if (selected != null) { + setEdited(selected); + browserCols.get(selected.getPath()).setFocus(); + } + } else if (e.keyCode == SWT.ARROW_LEFT) { + try { + selected = getNode().getParent(); + String newPath = selected.getPath(); // getNode().getParent() + setEdited(selected); + if (browserCols.containsKey(newPath)) + browserCols.get(newPath).setFocus(); + } catch (ItemNotFoundException ie) { + // root silent + } + } + } catch (RepositoryException ie) { + throw new CmsException("Error while managing arrow " + "events in the browser for " + selected, + ie); + } + } + }); + } + + private class MyLazyCP implements ILazyContentProvider { + private static final long serialVersionUID = 1L; + private TableViewer viewer; + private Object[] elements; + + public MyLazyCP(TableViewer viewer) { + this.viewer = viewer; + } + + public void dispose() { + } + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + // IMPORTANT: don't forget this: an exception will be thrown if + // a selected object is not part of the results anymore. + viewer.setSelection(null); + this.elements = (Object[]) newInput; + } + + public void updateElement(int index) { + viewer.replace(elements[index], index); + } + } + + protected void refreshFilteredList(NodeIterator children) { + Object[] rows = JcrUtils.nodeIteratorToList(children).toArray(); + entityViewer.setInput(rows); + entityViewer.setItemCount(rows.length); + entityViewer.refresh(); + } + + public class SimpleNameLP extends ColumnLabelProvider { + private static final long serialVersionUID = 2465059387875338553L; + + @Override + public String getText(Object element) { + if (element instanceof Node) { + Node curr = ((Node) element); + try { + return curr.getName(); + } catch (RepositoryException e) { + throw new CmsException("Unable to get name for" + curr); + } + } + return super.getText(element); + } + } + } +} \ No newline at end of file diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/ConnectivityDeploymentUi.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/ConnectivityDeploymentUi.java new file mode 100644 index 000000000..97f3e6713 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/ConnectivityDeploymentUi.java @@ -0,0 +1,48 @@ +package org.argeo.cms.e4.maintenance; + +import org.argeo.cms.swt.CmsSwtUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.osgi.framework.ServiceReference; +import org.osgi.service.http.HttpService; +import org.osgi.service.useradmin.UserAdmin; + +class ConnectivityDeploymentUi extends AbstractOsgiComposite { + private static final long serialVersionUID = 590221539553514693L; + + public ConnectivityDeploymentUi(Composite parent, int style) { + super(parent, style); + } + + @Override + protected void initUi(int style) { + StringBuffer text = new StringBuffer(); + text.append("Provided Servers
"); + + ServiceReference userAdminRef = bc.getServiceReference(HttpService.class); + if (userAdminRef != null) { + // FIXME use constants + Object httpPort = userAdminRef.getProperty("http.port"); + Object httpsPort = userAdminRef.getProperty("https.port"); + if (httpPort != null) + text.append("http ").append(httpPort).append("
"); + if (httpsPort != null) + text.append("https ").append(httpsPort).append("
"); + + } + + text.append("
"); + text.append("Referenced Servers
"); + + Label label = new Label(this, SWT.NONE); + label.setData(new GridData(SWT.FILL, SWT.FILL, false, false)); + CmsSwtUtils.markup(label); + label.setText(text.toString()); + } + + protected boolean isDeployed() { + return bc.getServiceReference(UserAdmin.class) != null; + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DataDeploymentUi.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DataDeploymentUi.java new file mode 100644 index 000000000..ef95bde64 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DataDeploymentUi.java @@ -0,0 +1,139 @@ +package org.argeo.cms.e4.maintenance; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileStore; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Collection; + +import org.apache.jackrabbit.core.RepositoryContext; +import org.apache.jackrabbit.core.config.RepositoryConfig; +import org.argeo.api.cms.CmsConstants; +import org.argeo.cms.swt.CmsSwtUtils; +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.Label; +import org.osgi.framework.ServiceReference; + +class DataDeploymentUi extends AbstractOsgiComposite { + private static final long serialVersionUID = 590221539553514693L; + + public DataDeploymentUi(Composite parent, int style) { + super(parent, style); + } + + @Override + protected void initUi(int style) { + if (isDeployed()) { + initCurrentUi(this); + } else { + initNewUi(this); + } + } + + private void initNewUi(Composite parent) { +// try { +// ConfigurationAdmin confAdmin = bc.getService(bc.getServiceReference(ConfigurationAdmin.class)); +// Configuration[] confs = confAdmin.listConfigurations( +// "(" + ConfigurationAdmin.SERVICE_FACTORYPID + "=" + NodeConstants.NODE_REPOS_FACTORY_PID + ")"); +// if (confs == null || confs.length == 0) { +// Group buttonGroup = new Group(parent, SWT.NONE); +// buttonGroup.setText("Repository Type"); +// buttonGroup.setLayout(new GridLayout(2, true)); +// buttonGroup.setLayoutData(new GridData(GridData.FILL_VERTICAL)); +// +// SelectionListener selectionListener = new SelectionAdapter() { +// private static final long serialVersionUID = 6247064348421088092L; +// +// public void widgetSelected(SelectionEvent event) { +// Button radio = (Button) event.widget; +// if (!radio.getSelection()) +// return; +// log.debug(event); +// JackrabbitType nodeType = (JackrabbitType) radio.getData(); +// if (log.isDebugEnabled()) +// log.debug(" selected = " + nodeType.name()); +// }; +// }; +// +// for (JackrabbitType nodeType : JackrabbitType.values()) { +// Button radio = new Button(buttonGroup, SWT.RADIO); +// radio.setText(nodeType.name()); +// radio.setData(nodeType); +// if (nodeType.equals(JackrabbitType.localfs)) +// radio.setSelection(true); +// radio.addSelectionListener(selectionListener); +// } +// +// } else if (confs.length == 1) { +// +// } else { +// throw new CmsException("Multiple repos not yet supported"); +// } +// } catch (Exception e) { +// throw new CmsException("Cannot initialize UI", e); +// } + + } + + private void initCurrentUi(Composite parent) { + parent.setLayout(new GridLayout()); + Collection> contexts = getServiceReferences(RepositoryContext.class, + "(" + CmsConstants.CN + "=*)"); + StringBuffer text = new StringBuffer(); + text.append("Jackrabbit Repositories
"); + for (ServiceReference sr : contexts) { + RepositoryContext repositoryContext = bc.getService(sr); + String alias = sr.getProperty(CmsConstants.CN).toString(); + String rootNodeId = repositoryContext.getRootNodeId().toString(); + RepositoryConfig repositoryConfig = repositoryContext.getRepositoryConfig(); + Path repoHomePath = new File(repositoryConfig.getHomeDir()).toPath().toAbsolutePath(); + // TODO check data store + + text.append("" + alias + "
"); + text.append("rootNodeId: " + rootNodeId + "
"); + try { + FileStore fileStore = Files.getFileStore(repoHomePath); + text.append("partition: " + fileStore.toString() + "
"); + text.append( + percentUsed(fileStore) + " used (" + humanReadable(fileStore.getUsableSpace()) + " free)
"); + } catch (IOException e) { + log.error("Cannot check fileStore for " + repoHomePath, e); + } + } + Label label = new Label(parent, SWT.NONE); + label.setData(new GridData(SWT.FILL, SWT.FILL, false, false)); + CmsSwtUtils.markup(label); + label.setText("" + text.toString() + ""); + } + + private String humanReadable(long bytes) { + long mb = bytes / (1024 * 1024); + return mb >= 2048 ? Long.toString(mb / 1024) + " GB" : Long.toString(mb) + " MB"; + } + + private String percentUsed(FileStore fs) throws IOException { + long used = fs.getTotalSpace() - fs.getUnallocatedSpace(); + long percent = used * 100 / fs.getTotalSpace(); + if (log.isTraceEnabled()) { + // output identical to `df -B 1`) + log.trace(fs.getTotalSpace() + "," + used + "," + fs.getUsableSpace()); + } + String span; + if (percent < 80) + span = ""; + else if (percent < 95) + span = ""; + else + span = ""; + return span + percent + "%"; + } + + protected boolean isDeployed() { + return bc.getServiceReference(RepositoryContext.class) != null; + } + +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DeploymentEntryPoint.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DeploymentEntryPoint.java new file mode 100644 index 000000000..e713f53e1 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DeploymentEntryPoint.java @@ -0,0 +1,96 @@ +package org.argeo.cms.e4.maintenance; + +import java.util.GregorianCalendar; +import java.util.TimeZone; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.api.cms.CmsContext; +import org.argeo.api.cms.CmsDeployment; +import org.argeo.api.cms.CmsState; +import org.argeo.cms.swt.CmsSwtUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Group; +import org.eclipse.swt.widgets.Label; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; + +class DeploymentEntryPoint { + private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); + + protected void createContents(Composite parent) { + // FIXME manage authentication if needed + // if (!CurrentUser.roles().contains(AuthConstants.ROLE_ADMIN)) + // return; + + // parent.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + if (isDesktop()) { + parent.setLayout(new GridLayout(2, true)); + } else { + // TODO add scrolling + parent.setLayout(new GridLayout(1, true)); + } + + initHighLevelSummary(parent); + + Group securityGroup = createHighLevelGroup(parent, "Security"); + securityGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + new SecurityDeploymentUi(securityGroup, SWT.NONE); + + Group dataGroup = createHighLevelGroup(parent, "Data"); + dataGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + new DataDeploymentUi(dataGroup, SWT.NONE); + + Group logGroup = createHighLevelGroup(parent, "Notifications"); + logGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true)); + new LogDeploymentUi(logGroup, SWT.NONE); + + Group connectivityGroup = createHighLevelGroup(parent, "Connectivity"); + new ConnectivityDeploymentUi(connectivityGroup, SWT.NONE); + connectivityGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true)); + + } + + private void initHighLevelSummary(Composite parent) { + Composite composite = new Composite(parent, SWT.NONE); + GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false); + if (isDesktop()) + gridData.horizontalSpan = 3; + composite.setLayoutData(gridData); + composite.setLayout(new FillLayout()); + + ServiceReference nodeStateRef = bc.getServiceReference(CmsState.class); + if (nodeStateRef == null) + throw new IllegalStateException("No CMS state available"); + CmsState nodeState = bc.getService(nodeStateRef); + ServiceReference nodeDeploymentRef = bc.getServiceReference(CmsContext.class); + Label label = new Label(composite, SWT.WRAP); + CmsSwtUtils.markup(label); + if (nodeDeploymentRef == null) { + label.setText("Not yet deployed on
" + nodeState.getHostname() + "
, please configure below."); + } else { + Object stateUuid = nodeStateRef.getProperty(CmsConstants.CN); + CmsContext nodeDeployment = bc.getService(nodeDeploymentRef); + GregorianCalendar calendar = new GregorianCalendar(); + calendar.setTimeInMillis(nodeDeployment.getAvailableSince()); + calendar.setTimeZone(TimeZone.getDefault()); + label.setText("[" + "" + nodeState.getHostname() + "]# " + "Deployment state " + stateUuid + + ", available since " + calendar.getTime() + ""); + } + } + + private static Group createHighLevelGroup(Composite parent, String text) { + Group group = new Group(parent, SWT.NONE); + group.setText(text); + CmsSwtUtils.markup(group); + return group; + } + + private boolean isDesktop() { + return true; + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/LogDeploymentUi.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/LogDeploymentUi.java new file mode 100644 index 000000000..fa5d3dae3 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/LogDeploymentUi.java @@ -0,0 +1,73 @@ +package org.argeo.cms.e4.maintenance; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Enumeration; +import java.util.GregorianCalendar; +import java.util.TimeZone; + +import org.argeo.cms.swt.CmsSwtUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Text; +import org.osgi.service.log.LogEntry; +import org.osgi.service.log.LogListener; +import org.osgi.service.log.LogReaderService; + +class LogDeploymentUi extends AbstractOsgiComposite implements LogListener { + private static final long serialVersionUID = 590221539553514693L; + + private DateFormat dateFormat = new SimpleDateFormat("MMdd HH:mm"); + + private Display display; + private Text logDisplay; + + public LogDeploymentUi(Composite parent, int style) { + super(parent, style); + } + + @Override + protected void initUi(int style) { + LogReaderService logReader = getService(LogReaderService.class); + // FIXME use server push + // logReader.addLogListener(this); + this.display = getDisplay(); + this.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + logDisplay = new Text(this, SWT.WRAP | SWT.MULTI | SWT.READ_ONLY); + logDisplay.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + CmsSwtUtils.markup(logDisplay); + Enumeration logEntries = (Enumeration) logReader.getLog(); + while (logEntries.hasMoreElements()) + logDisplay.append(printEntry(logEntries.nextElement())); + } + + private String printEntry(LogEntry entry) { + StringBuilder sb = new StringBuilder(); + GregorianCalendar calendar = new GregorianCalendar(TimeZone.getDefault()); + calendar.setTimeInMillis(entry.getTime()); + sb.append(dateFormat.format(calendar.getTime())).append(' '); + sb.append(entry.getMessage()); + sb.append('\n'); + return sb.toString(); + } + + @Override + public void logged(LogEntry entry) { + if (display.isDisposed()) + return; + display.asyncExec(() -> { + if (logDisplay.isDisposed()) + return; + logDisplay.append(printEntry(entry)); + }); + display.wake(); + } + + // @Override + // public void dispose() { + // super.dispose(); + // getService(LogReaderService.class).removeLogListener(this); + // } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/MaintenanceStyles.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/MaintenanceStyles.java new file mode 100644 index 000000000..df1be51ad --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/MaintenanceStyles.java @@ -0,0 +1,10 @@ +package org.argeo.cms.e4.maintenance; + +/** Specific styles used by the various maintenance pages . */ +public interface MaintenanceStyles { + // General + public final static String PREFIX = "maintenance_"; + + // Browser + public final static String BROWSER_COLUMN = "browser_column"; + } diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/NonAdminPage.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/NonAdminPage.java new file mode 100644 index 000000000..cb38ce899 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/NonAdminPage.java @@ -0,0 +1,30 @@ +package org.argeo.cms.e4.maintenance; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.ui.CmsUiProvider; +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.Label; + +public class NonAdminPage implements CmsUiProvider{ + + @Override + public Control createUi(Composite parent, Node context) + throws RepositoryException { + Composite body = new Composite(parent, SWT.NO_FOCUS); + body.setLayoutData(CmsSwtUtils.fillAll()); + body.setLayout(new GridLayout()); + Label label = new Label(body, SWT.NONE); + label.setText("You should be an admin to perform maintenance operations. " + + "Are you sure you are logged in?"); + label.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true)); + return null; + } + +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/SecurityDeploymentUi.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/SecurityDeploymentUi.java new file mode 100644 index 000000000..3492c5499 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/SecurityDeploymentUi.java @@ -0,0 +1,85 @@ +package org.argeo.cms.e4.maintenance; + +import java.net.URI; + +import org.argeo.cms.swt.CmsSwtUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.UserAdmin; + +class SecurityDeploymentUi extends AbstractOsgiComposite { + private static final long serialVersionUID = 590221539553514693L; + + public SecurityDeploymentUi(Composite parent, int style) { + super(parent, style); + } + + @Override + protected void initUi(int style) { + if (isDeployed()) { + initCurrentUi(this); + } else { + initNewUi(this); + } + } + + private void initNewUi(Composite parent) { + new Label(parent, SWT.NONE).setText("Security is not configured"); + } + + private void initCurrentUi(Composite parent) { + ServiceReference userAdminRef = bc.getServiceReference(UserAdmin.class); + UserAdmin userAdmin = bc.getService(userAdminRef); + StringBuffer text = new StringBuffer(); + text.append("Domains
"); + domains: for (String key : userAdminRef.getPropertyKeys()) { + if (!key.startsWith("/")) + continue domains; + URI uri; + try { + uri = new URI(key); + } catch (Exception e) { + // ignore non URI keys + continue domains; + } + + String rootDn = uri.getPath().substring(1, uri.getPath().length()); + // FIXME make reading query options more robust, using utils + boolean readOnly = uri.getQuery().equals("readOnly=true"); + if (readOnly) + text.append(""); + else + text.append(""); + + text.append(rootDn); + text.append("
"); + try { + Role[] roles = userAdmin.getRoles("(dn=*," + rootDn + ")"); + long userCount = 0; + long groupCount = 0; + for (Role role : roles) { + if (role.getType() == Role.USER) + userCount++; + else + groupCount++; + } + text.append(" " + userCount + " users, " + groupCount +" groups.
"); + } catch (InvalidSyntaxException e) { + log.error("Invalid syntax", e); + } + } + Label label = new Label(parent, SWT.NONE); + label.setData(new GridData(SWT.FILL, SWT.FILL, false, false)); + CmsSwtUtils.markup(label); + label.setText(text.toString()); + } + + protected boolean isDeployed() { + return bc.getServiceReference(UserAdmin.class) != null; + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/package-info.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/package-info.java new file mode 100644 index 000000000..e4d2ad4c3 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/package-info.java @@ -0,0 +1,2 @@ +/** Maintenance perspective. */ +package org.argeo.cms.e4.maintenance; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/BundleNode.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/BundleNode.java new file mode 100644 index 000000000..962ad386e --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/BundleNode.java @@ -0,0 +1,46 @@ +package org.argeo.cms.e4.monitoring; + +import org.argeo.eclipse.ui.TreeParent; +import org.osgi.framework.Bundle; +import org.osgi.framework.ServiceReference; + +/** A tree element representing a {@link Bundle} */ +class BundleNode extends TreeParent { + private final Bundle bundle; + + public BundleNode(Bundle bundle) { + this(bundle, false); + } + + @SuppressWarnings("rawtypes") + public BundleNode(Bundle bundle, boolean hasChildren) { + super(bundle.getSymbolicName()); + this.bundle = bundle; + + if (hasChildren) { + // REFERENCES + ServiceReference[] usedServices = bundle.getServicesInUse(); + if (usedServices != null) { + for (ServiceReference sr : usedServices) { + if (sr != null) + addChild(new ServiceReferenceNode(sr, false)); + } + } + + // SERVICES + ServiceReference[] registeredServices = bundle + .getRegisteredServices(); + if (registeredServices != null) { + for (ServiceReference sr : registeredServices) { + if (sr != null) + addChild(new ServiceReferenceNode(sr, true)); + } + } + } + + } + + Bundle getBundle() { + return bundle; + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/BundlesView.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/BundlesView.java new file mode 100644 index 000000000..c639255b6 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/BundlesView.java @@ -0,0 +1,114 @@ +//package org.argeo.eclipse.ui.workbench.osgi; +//public class BundlesView {} + +package org.argeo.cms.e4.monitoring; + +import javax.annotation.PostConstruct; + +import org.argeo.eclipse.ui.ColumnViewerComparator; +import org.argeo.eclipse.ui.specific.EclipseUiSpecificUtils; +import org.eclipse.e4.ui.di.Focus; +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; + +/** + * Overview of the bundles as a table. Equivalent to Equinox 'ss' console + * command. + */ +public class BundlesView { + private final static BundleContext bc = FrameworkUtil.getBundle(BundlesView.class).getBundleContext(); + private TableViewer viewer; + + @PostConstruct + public void createPartControl(Composite parent) { + viewer = new TableViewer(parent); + viewer.setContentProvider(new BundleContentProvider()); + viewer.getTable().setHeaderVisible(true); + + EclipseUiSpecificUtils.enableToolTipSupport(viewer); + + // ID + TableViewerColumn column = new TableViewerColumn(viewer, SWT.NONE); + column.getColumn().setWidth(30); + column.getColumn().setText("ID"); + column.getColumn().setAlignment(SWT.RIGHT); + column.setLabelProvider(new ColumnLabelProvider() { + private static final long serialVersionUID = -3122136344359358605L; + + public String getText(Object element) { + return Long.toString(((Bundle) element).getBundleId()); + } + }); + new ColumnViewerComparator(column); + + // State + column = new TableViewerColumn(viewer, SWT.NONE); + column.getColumn().setWidth(18); + column.getColumn().setText("State"); + column.setLabelProvider(new StateLabelProvider()); + new ColumnViewerComparator(column); + + // Symbolic name + column = new TableViewerColumn(viewer, SWT.NONE); + column.getColumn().setWidth(250); + column.getColumn().setText("Symbolic Name"); + column.setLabelProvider(new ColumnLabelProvider() { + private static final long serialVersionUID = -4280840684440451080L; + + public String getText(Object element) { + return ((Bundle) element).getSymbolicName(); + } + }); + new ColumnViewerComparator(column); + + // Version + column = new TableViewerColumn(viewer, SWT.NONE); + column.getColumn().setWidth(250); + column.getColumn().setText("Version"); + column.setLabelProvider(new ColumnLabelProvider() { + private static final long serialVersionUID = 6871926308708629989L; + + public String getText(Object element) { + Bundle bundle = (org.osgi.framework.Bundle) element; + return bundle.getVersion().toString(); + } + }); + new ColumnViewerComparator(column); + + viewer.setInput(bc); + + } + + @Focus + public void setFocus() { + if (viewer != null) + viewer.getControl().setFocus(); + } + + /** Content provider managing the array of bundles */ + private static class BundleContentProvider implements IStructuredContentProvider { + private static final long serialVersionUID = -8533792785725875977L; + + public Object[] getElements(Object inputElement) { + if (inputElement instanceof BundleContext) { + BundleContext bc = (BundleContext) inputElement; + return bc.getBundles(); + } + return null; + } + + public void dispose() { + } + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/CmsSessionsView.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/CmsSessionsView.java new file mode 100644 index 000000000..8a3605095 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/CmsSessionsView.java @@ -0,0 +1,173 @@ +//package org.argeo.eclipse.ui.workbench.osgi; +//public class BundlesView {} + +package org.argeo.cms.e4.monitoring; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import javax.annotation.PostConstruct; +import javax.naming.ldap.LdapName; + +import org.argeo.api.cms.CmsSession; +import org.argeo.eclipse.ui.ColumnViewerComparator; +import org.argeo.eclipse.ui.specific.EclipseUiSpecificUtils; +import org.argeo.util.LangUtils; +import org.eclipse.e4.ui.di.Focus; +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; + +/** + * Overview of the active CMS sessions. + */ +public class CmsSessionsView { + private final static BundleContext bc = FrameworkUtil.getBundle(CmsSessionsView.class).getBundleContext(); + + private TableViewer viewer; + + @PostConstruct + public void createPartControl(Composite parent) { + viewer = new TableViewer(parent); + viewer.setContentProvider(new CmsSessionContentProvider()); + viewer.getTable().setHeaderVisible(true); + + EclipseUiSpecificUtils.enableToolTipSupport(viewer); + + int longColWidth = 150; + int smallColWidth = 100; + + // Display name + TableViewerColumn column = new TableViewerColumn(viewer, SWT.NONE); + column.getColumn().setWidth(longColWidth); + column.getColumn().setText("User"); + column.setLabelProvider(new ColumnLabelProvider() { + private static final long serialVersionUID = -5234573509093747505L; + + public String getText(Object element) { + return ((CmsSession) element).getDisplayName(); + } + + public String getToolTipText(Object element) { + return ((CmsSession) element).getUserDn().toString(); + } + }); + new ColumnViewerComparator(column); + + // Creation time + column = new TableViewerColumn(viewer, SWT.NONE); + column.getColumn().setWidth(smallColWidth); + column.getColumn().setText("Since"); + column.setLabelProvider(new ColumnLabelProvider() { + private static final long serialVersionUID = -5234573509093747505L; + + public String getText(Object element) { + return LangUtils.since(((CmsSession) element).getCreationTime()); + } + + public String getToolTipText(Object element) { + return ((CmsSession) element).getCreationTime().toString(); + } + }); + new ColumnViewerComparator(column); + + // Username + column = new TableViewerColumn(viewer, SWT.NONE); + column.getColumn().setWidth(smallColWidth); + column.getColumn().setText("Username"); + column.setLabelProvider(new ColumnLabelProvider() { + private static final long serialVersionUID = -5234573509093747505L; + + public String getText(Object element) { + LdapName userDn = ((CmsSession) element).getUserDn(); + return userDn.getRdn(userDn.size() - 1).getValue().toString(); + } + + public String getToolTipText(Object element) { + return ((CmsSession) element).getUserDn().toString(); + } + }); + new ColumnViewerComparator(column); + + // UUID + column = new TableViewerColumn(viewer, SWT.NONE); + column.getColumn().setWidth(smallColWidth); + column.getColumn().setText("UUID"); + column.setLabelProvider(new ColumnLabelProvider() { + private static final long serialVersionUID = -5234573509093747505L; + + public String getText(Object element) { + return ((CmsSession) element).getUuid().toString(); + } + + public String getToolTipText(Object element) { + return getText(element); + } + }); + new ColumnViewerComparator(column); + + // Local ID + column = new TableViewerColumn(viewer, SWT.NONE); + column.getColumn().setWidth(smallColWidth); + column.getColumn().setText("Local ID"); + column.setLabelProvider(new ColumnLabelProvider() { + private static final long serialVersionUID = -5234573509093747505L; + + public String getText(Object element) { + return ((CmsSession) element).getLocalId(); + } + + public String getToolTipText(Object element) { + return getText(element); + } + }); + new ColumnViewerComparator(column); + + viewer.setInput(bc); + + } + + @Focus + public void setFocus() { + if (viewer != null) + viewer.getControl().setFocus(); + } + + /** Content provider managing the array of bundles */ + private static class CmsSessionContentProvider implements IStructuredContentProvider { + private static final long serialVersionUID = -8533792785725875977L; + + public Object[] getElements(Object inputElement) { + if (inputElement instanceof BundleContext) { + BundleContext bc = (BundleContext) inputElement; + Collection> srs; + try { + srs = bc.getServiceReferences(CmsSession.class, null); + } catch (InvalidSyntaxException e) { + throw new IllegalArgumentException("Cannot retrieve CMS sessions", e); + } + List res = new ArrayList<>(); + for (ServiceReference sr : srs) { + res.add(bc.getService(sr)); + } + return res.toArray(); + } + return null; + } + + public void dispose() { + } + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/ModulesView.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/ModulesView.java new file mode 100644 index 000000000..f0d8c2952 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/ModulesView.java @@ -0,0 +1,91 @@ +package org.argeo.cms.e4.monitoring; + +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.PostConstruct; + +import org.argeo.eclipse.ui.TreeParent; +import org.eclipse.e4.ui.di.Focus; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; + +/** The OSGi runtime from a module perspective. */ +public class ModulesView { + private final static BundleContext bc = FrameworkUtil.getBundle(ModulesView.class).getBundleContext(); + private TreeViewer viewer; + + @PostConstruct + public void createPartControl(Composite parent) { + viewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); + viewer.setContentProvider(new ModulesContentProvider()); + viewer.setLabelProvider(new ModulesLabelProvider()); + viewer.setInput(bc); + } + + @Focus + public void setFocus() { + viewer.getTree().setFocus(); + } + + private class ModulesContentProvider implements ITreeContentProvider { + private static final long serialVersionUID = 3819934804640641721L; + + public Object[] getElements(Object inputElement) { + return getChildren(inputElement); + } + + public Object[] getChildren(Object parentElement) { + if (parentElement instanceof BundleContext) { + BundleContext bundleContext = (BundleContext) parentElement; + Bundle[] bundles = bundleContext.getBundles(); + + List modules = new ArrayList(); + for (Bundle bundle : bundles) { + if (bundle.getState() == Bundle.ACTIVE) + modules.add(new BundleNode(bundle, true)); + } + return modules.toArray(); + } else if (parentElement instanceof TreeParent) { + return ((TreeParent) parentElement).getChildren(); + } else { + return null; + } + } + + public Object getParent(Object element) { + // TODO Auto-generated method stub + return null; + } + + public boolean hasChildren(Object element) { + if (element instanceof TreeParent) { + return ((TreeParent) element).hasChildren(); + } + return false; + } + + public void dispose() { + } + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + } + + private class ModulesLabelProvider extends StateLabelProvider { + private static final long serialVersionUID = 5290046145534824722L; + + @Override + public String getText(Object element) { + if (element instanceof BundleNode) + return element.toString() + " [" + ((BundleNode) element).getBundle().getBundleId() + "]"; + return element.toString(); + } + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/OsgiConfigurationsView.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/OsgiConfigurationsView.java new file mode 100644 index 000000000..759b3e955 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/OsgiConfigurationsView.java @@ -0,0 +1,163 @@ +package org.argeo.cms.e4.monitoring; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.Dictionary; +import java.util.List; + +import javax.annotation.PostConstruct; + +import org.argeo.cms.CmsException; +import org.argeo.util.LangUtils; +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.TreeViewerColumn; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Composite; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.service.cm.Configuration; +import org.osgi.service.cm.ConfigurationAdmin; + +public class OsgiConfigurationsView { + private final static BundleContext bc = FrameworkUtil.getBundle(OsgiConfigurationsView.class).getBundleContext(); + + @PostConstruct + public void createPartControl(Composite parent) { + ConfigurationAdmin configurationAdmin = bc.getService(bc.getServiceReference(ConfigurationAdmin.class)); + + TreeViewer viewer = new TreeViewer(parent); + // viewer.getTree().setHeaderVisible(true); + + TreeViewerColumn tvc = new TreeViewerColumn(viewer, SWT.NONE); + tvc.getColumn().setWidth(400); + tvc.setLabelProvider(new ColumnLabelProvider() { + private static final long serialVersionUID = 835407996597566763L; + + @Override + public String getText(Object element) { + if (element instanceof Configuration) { + return ((Configuration) element).getPid(); + } else if (element instanceof Prop) { + return ((Prop) element).key; + } + return super.getText(element); + } + + @Override + public Image getImage(Object element) { + if (element instanceof Configuration) + return OsgiExplorerImages.CONFIGURATION; + return null; + } + + }); + + tvc = new TreeViewerColumn(viewer, SWT.NONE); + tvc.getColumn().setWidth(400); + tvc.setLabelProvider(new ColumnLabelProvider() { + private static final long serialVersionUID = 6999659261190014687L; + + @Override + public String getText(Object element) { + if (element instanceof Configuration) { + // return ((Configuration) element).getFactoryPid(); + return null; + } else if (element instanceof Prop) { + return ((Prop) element).value.toString(); + } + return super.getText(element); + } + }); + + viewer.setContentProvider(new ConfigurationsContentProvider()); + viewer.setInput(configurationAdmin); + } + + static class ConfigurationsContentProvider implements ITreeContentProvider { + private static final long serialVersionUID = -4892768279440981042L; + private ConfigurationComparator configurationComparator = new ConfigurationComparator(); + + @Override + public void dispose() { + } + + @Override + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + + @Override + public Object[] getElements(Object inputElement) { + ConfigurationAdmin configurationAdmin = (ConfigurationAdmin) inputElement; + try { + Configuration[] configurations = configurationAdmin.listConfigurations(null); + Arrays.sort(configurations, configurationComparator); + return configurations; + } catch (IOException | InvalidSyntaxException e) { + throw new CmsException("Cannot list configurations", e); + } + } + + @Override + public Object[] getChildren(Object parentElement) { + if (parentElement instanceof Configuration) { + List res = new ArrayList<>(); + Configuration configuration = (Configuration) parentElement; + Dictionary props = configuration.getProperties(); + keys: for (String key : LangUtils.keys(props)) { + if (Constants.SERVICE_PID.equals(key)) + continue keys; + if (ConfigurationAdmin.SERVICE_FACTORYPID.equals(key)) + continue keys; + res.add(new Prop(configuration, key, props.get(key))); + } + return res.toArray(new Prop[res.size()]); + } + return null; + } + + @Override + public Object getParent(Object element) { + if (element instanceof Prop) + return ((Prop) element).configuration; + return null; + } + + @Override + public boolean hasChildren(Object element) { + if (element instanceof Configuration) + return true; + return false; + } + + } + + static class Prop { + final Configuration configuration; + final String key; + final Object value; + + public Prop(Configuration configuration, String key, Object value) { + this.configuration = configuration; + this.key = key; + this.value = value; + } + + } + + static class ConfigurationComparator implements Comparator { + + @Override + public int compare(Configuration o1, Configuration o2) { + return o1.getPid().compareTo(o2.getPid()); + } + + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/OsgiExplorerImages.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/OsgiExplorerImages.java new file mode 100644 index 000000000..7217fe612 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/OsgiExplorerImages.java @@ -0,0 +1,15 @@ +package org.argeo.cms.e4.monitoring; + +import org.argeo.cms.ui.theme.CmsImages; +import org.eclipse.swt.graphics.Image; + +/** Shared icons. */ +public class OsgiExplorerImages extends CmsImages { + public final static Image INSTALLED = createIcon("installed.gif"); + public final static Image RESOLVED = createIcon("resolved.gif"); + public final static Image STARTING = createIcon("starting.gif"); + public final static Image ACTIVE = createIcon("active.gif"); + public final static Image SERVICE_PUBLISHED = createIcon("service_published.gif"); + public final static Image SERVICE_REFERENCED = createIcon("service_referenced.gif"); + public final static Image CONFIGURATION = createIcon("node.gif"); +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/ServiceReferenceNode.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/ServiceReferenceNode.java new file mode 100644 index 000000000..d9c45fe11 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/ServiceReferenceNode.java @@ -0,0 +1,46 @@ +package org.argeo.cms.e4.monitoring; + +import org.argeo.eclipse.ui.TreeParent; +import org.osgi.framework.Bundle; +import org.osgi.framework.ServiceReference; + +/** A tree element representing a {@link ServiceReference} */ +@SuppressWarnings({ "rawtypes" }) +class ServiceReferenceNode extends TreeParent { + private final ServiceReference serviceReference; + private final boolean published; + + public ServiceReferenceNode(ServiceReference serviceReference, + boolean published) { + super(serviceReference.toString()); + this.serviceReference = serviceReference; + this.published = published; + + if (isPublished()) { + Bundle[] usedBundles = serviceReference.getUsingBundles(); + if (usedBundles != null) { + for (Bundle b : usedBundles) { + if (b != null) + addChild(new BundleNode(b)); + } + } + } else { + Bundle provider = serviceReference.getBundle(); + addChild(new BundleNode(provider)); + } + + for (String key : serviceReference.getPropertyKeys()) { + addChild(new TreeParent(key + "=" + + serviceReference.getProperty(key))); + } + + } + + public ServiceReference getServiceReference() { + return serviceReference; + } + + public boolean isPublished() { + return published; + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/StateLabelProvider.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/StateLabelProvider.java new file mode 100644 index 000000000..5cb5b6563 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/StateLabelProvider.java @@ -0,0 +1,82 @@ +package org.argeo.cms.e4.monitoring; + +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.swt.graphics.Image; +import org.osgi.framework.Bundle; +import org.osgi.framework.Constants; + +/** Label provider showing the sate of bundles */ +class StateLabelProvider extends ColumnLabelProvider { + private static final long serialVersionUID = -7885583135316000733L; + + @Override + public Image getImage(Object element) { + int state; + if (element instanceof Bundle) + state = ((Bundle) element).getState(); + else if (element instanceof BundleNode) + state = ((BundleNode) element).getBundle().getState(); + else if (element instanceof ServiceReferenceNode) + if (((ServiceReferenceNode) element).isPublished()) + return OsgiExplorerImages.SERVICE_PUBLISHED; + else + return OsgiExplorerImages.SERVICE_REFERENCED; + else + return null; + + switch (state) { + case Bundle.UNINSTALLED: + return OsgiExplorerImages.INSTALLED; + case Bundle.INSTALLED: + return OsgiExplorerImages.INSTALLED; + case Bundle.RESOLVED: + return OsgiExplorerImages.RESOLVED; + case Bundle.STARTING: + return OsgiExplorerImages.STARTING; + case Bundle.STOPPING: + return OsgiExplorerImages.STARTING; + case Bundle.ACTIVE: + return OsgiExplorerImages.ACTIVE; + default: + return null; + } + } + + @Override + public String getText(Object element) { + return null; + } + + @Override + public String getToolTipText(Object element) { + Bundle bundle = (Bundle) element; + Integer state = bundle.getState(); + switch (state) { + case Bundle.UNINSTALLED: + return "UNINSTALLED"; + case Bundle.INSTALLED: + return "INSTALLED"; + case Bundle.RESOLVED: + return "RESOLVED"; + case Bundle.STARTING: + String activationPolicy = bundle.getHeaders() + .get(Constants.BUNDLE_ACTIVATIONPOLICY).toString(); + + // .get("Bundle-ActivationPolicy").toString(); + // FIXME constant triggers the compilation failure + if (activationPolicy != null + && activationPolicy.equals(Constants.ACTIVATION_LAZY)) + // && activationPolicy.equals("lazy")) + // FIXME constant triggers the compilation failure + // && activationPolicy.equals(Constants.ACTIVATION_LAZY)) + return "<>"; + return "STARTING"; + case Bundle.STOPPING: + return "STOPPING"; + case Bundle.ACTIVE: + return "ACTIVE"; + default: + return null; + } + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/package-info.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/package-info.java new file mode 100644 index 000000000..873bf3118 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/package-info.java @@ -0,0 +1,2 @@ +/** Monitoring perspective. */ +package org.argeo.cms.e4.monitoring; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/package-info.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/package-info.java new file mode 100644 index 000000000..233119c0d --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/package-info.java @@ -0,0 +1,2 @@ +/** Eclipse 4 user interfaces. */ +package org.argeo.cms.e4; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/parts/EgoDashboard.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/parts/EgoDashboard.java new file mode 100644 index 000000000..5a805d1e9 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/parts/EgoDashboard.java @@ -0,0 +1,46 @@ +package org.argeo.cms.e4.parts; + +import java.security.AccessController; +import java.time.ZonedDateTime; + +import javax.annotation.PostConstruct; +import javax.security.auth.Subject; + +import org.argeo.api.cms.CmsSession; +import org.argeo.cms.auth.CurrentUser; +import org.argeo.cms.osgi.CmsOsgiUtils; +import org.argeo.cms.swt.CmsSwtUtils; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; + +/** A canonical view of the logged in user. */ +public class EgoDashboard { + private BundleContext bc = FrameworkUtil.getBundle(EgoDashboard.class).getBundleContext(); + + @PostConstruct + public void createPartControl(Composite p) { + p.setLayout(new GridLayout()); + String username = CurrentUser.getUsername(); + + CmsSwtUtils.lbl(p, "" + CurrentUser.getDisplayName() + ""); + CmsSwtUtils.txt(p, username); + CmsSwtUtils.lbl(p, "Roles:"); + roles: for (String role : CurrentUser.roles()) { + if (username.equals(role)) + continue roles; + CmsSwtUtils.txt(p, role); + } + + Subject subject = Subject.getSubject(AccessController.getContext()); + if (subject != null) { + CmsSession cmsSession = CmsOsgiUtils.getCmsSession(bc, subject); + ZonedDateTime loggedIndSince = cmsSession.getCreationTime(); + CmsSwtUtils.lbl(p, "Session:"); + CmsSwtUtils.txt(p, cmsSession.getUuid().toString()); + CmsSwtUtils.lbl(p, "Logged in since:"); + CmsSwtUtils.txt(p, loggedIndSince.toString()); + } + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/AbstractRoleEditor.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/AbstractRoleEditor.java new file mode 100644 index 000000000..137f76242 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/AbstractRoleEditor.java @@ -0,0 +1,287 @@ +package org.argeo.cms.e4.users; + +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.inject.Inject; + +import org.argeo.cms.auth.UserAdminUtils; +import org.argeo.cms.ui.eclipse.forms.AbstractFormPart; +import org.argeo.cms.ui.eclipse.forms.IManagedForm; +import org.argeo.cms.ui.eclipse.forms.ManagedForm; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.argeo.util.naming.LdapAttrs; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.e4.ui.di.Persist; +import org.eclipse.e4.ui.model.application.ui.basic.MPart; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.ScrolledComposite; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; +import org.osgi.service.useradmin.Authorization; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.User; +import org.osgi.service.useradmin.UserAdmin; +import org.osgi.service.useradmin.UserAdminEvent; + +/** Editor for a user, might be a user or a group. */ +public abstract class AbstractRoleEditor { + + // public final static String USER_EDITOR_ID = WorkbenchUiPlugin.PLUGIN_ID + + // ".userEditor"; + // public final static String GROUP_EDITOR_ID = WorkbenchUiPlugin.PLUGIN_ID + + // ".groupEditor"; + + /* DEPENDENCY INJECTION */ + @Inject + protected UserAdminWrapper userAdminWrapper; + + @Inject + private MPart mPart; + + // @Inject + // Composite parent; + + private UserAdmin userAdmin; + + // Context + private User user; + private String username; + + private NameChangeListener listener; + + private ManagedForm managedForm; + + // public void init(IEditorSite site, IEditorInput input) throws + // PartInitException { + @PostConstruct + public void init(Composite parent) { + this.userAdmin = userAdminWrapper.getUserAdmin(); + username = mPart.getPersistedState().get(LdapAttrs.uid.name()); + user = (User) userAdmin.getRole(username); + + listener = new NameChangeListener(Display.getCurrent()); + userAdminWrapper.addListener(listener); + updateEditorTitle(null); + + managedForm = new ManagedForm(parent) { + + @Override + public void staleStateChanged() { + refresh(); + } + }; + ScrolledComposite scrolled = managedForm.getForm(); + Composite body = new Composite(scrolled, SWT.NONE); + scrolled.setContent(body); + createUi(body); + managedForm.refresh(); + } + + abstract void createUi(Composite parent); + + /** + * returns the list of all authorizations for the given user or of the current + * displayed user if parameter is null + */ + protected List getFlatGroups(User aUser) { + Authorization currAuth; + if (aUser == null) + currAuth = userAdmin.getAuthorization(this.user); + else + currAuth = userAdmin.getAuthorization(aUser); + + String[] roles = currAuth.getRoles(); + + List groups = new ArrayList(); + for (String roleStr : roles) { + User currRole = (User) userAdmin.getRole(roleStr); + if (currRole != null && !groups.contains(currRole)) + groups.add(currRole); + } + return groups; + } + + protected IManagedForm getManagedForm() { + return managedForm; + } + + /** Exposes the user (or group) that is displayed by the current editor */ + protected User getDisplayedUser() { + return user; + } + + private void setDisplayedUser(User user) { + this.user = user; + } + + void updateEditorTitle(String title) { + if (title == null) { + String commonName = UserAdminUtils.getProperty(user, LdapAttrs.cn.name()); + title = "".equals(commonName) ? user.getName() : commonName; + } + setPartName(title); + } + + protected void setPartName(String name) { + mPart.setLabel(name); + } + + // protected void addPages() { + // try { + // if (user.getType() == Role.GROUP) + // addPage(new GroupMainPage(this, userAdminWrapper, repository, nodeInstance)); + // else + // addPage(new UserMainPage(this, userAdminWrapper)); + // } catch (Exception e) { + // throw new CmsException("Cannot add pages", e); + // } + // } + + @Persist + public void doSave(IProgressMonitor monitor) { + userAdminWrapper.beginTransactionIfNeeded(); + commitPages(true); + userAdminWrapper.commitOrNotifyTransactionStateChange(); + // firePropertyChange(PROP_DIRTY); + userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_REMOVED, user)); + } + + protected void commitPages(boolean b) { + managedForm.commit(b); + } + + @PreDestroy + public void dispose() { + userAdminWrapper.removeListener(listener); + managedForm.dispose(); + } + + // CONTROLERS FOR THIS EDITOR AND ITS PAGES + + class NameChangeListener extends UiUserAdminListener { + public NameChangeListener(Display display) { + super(display); + } + + @Override + public void roleChangedToUiThread(UserAdminEvent event) { + Role changedRole = event.getRole(); + if (changedRole == null || changedRole.equals(user)) { + updateEditorTitle(null); + User reloadedUser = (User) userAdminWrapper.getUserAdmin().getRole(user.getName()); + setDisplayedUser(reloadedUser); + } + } + } + + class MainInfoListener extends UiUserAdminListener { + private final AbstractFormPart part; + + public MainInfoListener(Display display, AbstractFormPart part) { + super(display); + this.part = part; + } + + @Override + public void roleChangedToUiThread(UserAdminEvent event) { + // Rollback + if (event.getRole() == null) + part.markStale(); + } + } + + class GroupChangeListener extends UiUserAdminListener { + private final AbstractFormPart part; + + public GroupChangeListener(Display display, AbstractFormPart part) { + super(display); + this.part = part; + } + + @Override + public void roleChangedToUiThread(UserAdminEvent event) { + // always mark as stale + part.markStale(); + } + } + + /** Registers a listener that will notify this part */ + class FormPartML implements ModifyListener { + private static final long serialVersionUID = 6299808129505381333L; + private AbstractFormPart formPart; + + public FormPartML(AbstractFormPart generalPart) { + this.formPart = generalPart; + } + + public void modifyText(ModifyEvent e) { + // Discard event when the control does not have the focus, typically + // to avoid all editors being marked as dirty during a Rollback + if (((Control) e.widget).isFocusControl()) + formPart.markDirty(); + } + } + + /* DEPENDENCY INJECTION */ + public void setUserAdminWrapper(UserAdminWrapper userAdminWrapper) { + this.userAdminWrapper = userAdminWrapper; + } + + /** Creates label and multiline text. */ + Text createLMT(Composite parent, String label, String value) { + Label lbl = new Label(parent, SWT.NONE); + lbl.setText(label); + lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false)); + Text text = new Text(parent, SWT.NONE); + text.setText(value); + text.setLayoutData(new GridData(SWT.LEAD, SWT.FILL, true, true)); + return text; + } + + /** Creates label and password. */ + Text createLP(Composite parent, String label, String value) { + Label lbl = new Label(parent, SWT.NONE); + lbl.setText(label); + lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false)); + Text text = new Text(parent, SWT.PASSWORD | SWT.BORDER); + text.setText(value); + text.setLayoutData(new GridData(SWT.LEAD, SWT.FILL, true, false)); + return text; + } + + /** Creates label and text. */ + Text createLT(Composite parent, String label, String value) { + Label lbl = new Label(parent, SWT.NONE); + lbl.setText(label); + lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false)); + lbl.setFont(EclipseUiUtils.getBoldFont(parent)); + Text text = new Text(parent, SWT.BORDER); + text.setText(value); + text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + // CmsUiUtils.style(text, CmsWorkbenchStyles.WORKBENCH_FORM_TEXT); + return text; + } + + Text createReadOnlyLT(Composite parent, String label, String value) { + Label lbl = new Label(parent, SWT.NONE); + lbl.setText(label); + lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false)); + lbl.setFont(EclipseUiUtils.getBoldFont(parent)); + Text text = new Text(parent, SWT.NONE); + text.setText(value); + text.setLayoutData(new GridData(SWT.LEAD, SWT.FILL, true, false)); + text.setEditable(false); + // CmsUiUtils.style(text, CmsWorkbenchStyles.WORKBENCH_FORM_TEXT); + return text; + } + +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/CmsWorkbenchStyles.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/CmsWorkbenchStyles.java new file mode 100644 index 000000000..07df312e1 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/CmsWorkbenchStyles.java @@ -0,0 +1,8 @@ +package org.argeo.cms.e4.users; + +/** Centralize the declaration of Workbench specific CSS Styles */ +interface CmsWorkbenchStyles { + + // Specific People layouting + String WORKBENCH_FORM_TEXT = "workbench_form_text"; +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupEditor.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupEditor.java new file mode 100644 index 000000000..a011c5f62 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupEditor.java @@ -0,0 +1,566 @@ +package org.argeo.cms.e4.users; + +import static org.argeo.api.cms.CmsContext.WORKGROUP; +import static org.argeo.cms.auth.UserAdminUtils.setProperty; +import static org.argeo.util.naming.LdapAttrs.businessCategory; +import static org.argeo.util.naming.LdapAttrs.description; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javax.annotation.PreDestroy; +import javax.inject.Inject; +import javax.jcr.Node; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.api.cms.CmsContext; +import org.argeo.cms.auth.UserAdminUtils; +import org.argeo.cms.e4.users.providers.CommonNameLP; +import org.argeo.cms.e4.users.providers.MailLP; +import org.argeo.cms.e4.users.providers.RoleIconLP; +import org.argeo.cms.e4.users.providers.UserFilter; +import org.argeo.cms.jcr.CmsJcrUtils; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.ui.eclipse.forms.AbstractFormPart; +import org.argeo.cms.ui.eclipse.forms.IManagedForm; +import org.argeo.eclipse.ui.ColumnDefinition; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.argeo.eclipse.ui.parts.LdifUsersTable; +import org.argeo.jcr.JcrException; +import org.argeo.jcr.JcrUtils; +import org.argeo.osgi.transaction.WorkTransaction; +import org.argeo.util.naming.LdapAttrs; +import org.eclipse.e4.ui.workbench.modeling.EPartService; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.ToolBarManager; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.ViewerDropAdapter; +import org.eclipse.swt.SWT; +import org.eclipse.swt.dnd.DND; +import org.eclipse.swt.dnd.DropTargetEvent; +import org.eclipse.swt.dnd.TextTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.dnd.TransferData; +import org.eclipse.swt.events.ModifyListener; +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.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Link; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; +import org.eclipse.swt.widgets.ToolBar; +import org.osgi.service.useradmin.Group; +import org.osgi.service.useradmin.Role; +//import org.eclipse.ui.forms.AbstractFormPart; +//import org.eclipse.ui.forms.IManagedForm; +//import org.eclipse.ui.forms.SectionPart; +//import org.eclipse.ui.forms.editor.FormEditor; +//import org.eclipse.ui.forms.editor.FormPage; +//import org.eclipse.ui.forms.widgets.FormToolkit; +//import org.eclipse.ui.forms.widgets.ScrolledForm; +//import org.eclipse.ui.forms.widgets.Section; +import org.osgi.service.useradmin.User; +import org.osgi.service.useradmin.UserAdmin; +import org.osgi.service.useradmin.UserAdminEvent; + +/** Display/edit main properties of a given group */ +public class GroupEditor extends AbstractRoleEditor { + // final static String ID = "GroupEditor.mainPage"; + + @Inject + private EPartService partService; + + // private final UserEditor editor; + @Inject + private Repository repository; + @Inject + private CmsContext nodeInstance; + // private final UserAdminWrapper userAdminWrapper; + private Session groupsSession; + + // public GroupMainPage(FormEditor editor, UserAdminWrapper userAdminWrapper, + // Repository repository, + // NodeInstance nodeInstance) { + // super(editor, ID, "Main"); + // try { + // session = repository.login(); + // } catch (RepositoryException e) { + // throw new CmsException("Cannot retrieve session of in MainGroupPage + // constructor", e); + // } + // this.editor = (UserEditor) editor; + // this.userAdminWrapper = userAdminWrapper; + // this.nodeInstance = nodeInstance; + // } + + // protected void createFormContent(final IManagedForm mf) { + // ScrolledForm form = mf.getForm(); + // Composite body = form.getBody(); + // GridLayout mainLayout = new GridLayout(); + // body.setLayout(mainLayout); + // Group group = (Group) editor.getDisplayedUser(); + // appendOverviewPart(body, group); + // appendMembersPart(body, group); + // } + + @Override + protected void createUi(Composite parent) { + try { + groupsSession = repository.login(CmsConstants.SRV_WORKSPACE); + } catch (RepositoryException e) { + throw new JcrException("Cannot retrieve session", e); + } + // ScrolledForm form = mf.getForm(); + // Composite body = form.getBody(); + // Composite body = new Composite(parent, SWT.NONE); + Composite body = parent; + GridLayout mainLayout = new GridLayout(); + body.setLayout(mainLayout); + Group group = (Group) getDisplayedUser(); + appendOverviewPart(body, group); + appendMembersPart(body, group); + } + + @PreDestroy + public void dispose() { + JcrUtils.logoutQuietly(groupsSession); + super.dispose(); + } + + /** Creates the general section */ + protected void appendOverviewPart(final Composite parent, final Group group) { + Composite body = new Composite(parent, SWT.NONE); + // GridLayout layout = new GridLayout(5, false); + GridLayout layout = new GridLayout(2, false); + body.setLayout(layout); + body.setLayoutData(CmsSwtUtils.fillWidth()); + + String cn = UserAdminUtils.getProperty(group, LdapAttrs.cn.name()); + createReadOnlyLT(body, "Name", cn); + createReadOnlyLT(body, "DN", group.getName()); + createReadOnlyLT(body, "Domain", UserAdminUtils.getDomainName(group)); + + // Description + Label descLbl = new Label(body, SWT.LEAD); + descLbl.setFont(EclipseUiUtils.getBoldFont(body)); + descLbl.setText("Description"); + descLbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, true, false, 2, 1)); + final Text descTxt = new Text(body, SWT.LEAD | SWT.MULTI | SWT.WRAP | SWT.BORDER); + GridData gd = EclipseUiUtils.fillWidth(); + gd.heightHint = 50; + gd.horizontalSpan = 2; + descTxt.setLayoutData(gd); + + // Mark as workgroup + Link markAsWorkgroupLk = new Link(body, SWT.NONE); + markAsWorkgroupLk.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 2, 1)); + + // create form part (controller) + final AbstractFormPart part = new AbstractFormPart() { + + private MainInfoListener listener; + + @Override + public void initialize(IManagedForm form) { + super.initialize(form); + listener = new MainInfoListener(parent.getDisplay(), this); + userAdminWrapper.addListener(listener); + } + + @Override + public void dispose() { + userAdminWrapper.removeListener(listener); + super.dispose(); + } + + public void commit(boolean onSave) { + // group.getProperties().put(LdapAttrs.description.name(), descTxt.getText()); + setProperty(group, description, descTxt.getText()); + super.commit(onSave); + } + + @Override + public void refresh() { + // dnTxt.setText(group.getName()); + // cnTxt.setText(UserAdminUtils.getProperty(group, LdapAttrs.cn.name())); + descTxt.setText(UserAdminUtils.getProperty(group, LdapAttrs.description.name())); + Node workgroupHome = CmsJcrUtils.getGroupHome(groupsSession, cn); + if (workgroupHome == null) + markAsWorkgroupLk.setText("Mark as workgroup"); + else + markAsWorkgroupLk.setText("Configured as workgroup"); + parent.layout(true, true); + super.refresh(); + } + }; + + markAsWorkgroupLk.addSelectionListener(new SelectionAdapter() { + private static final long serialVersionUID = -6439340898096365078L; + + @Override + public void widgetSelected(SelectionEvent e) { + + boolean confirmed = MessageDialog.openConfirm(parent.getShell(), "Mark as workgroup", + "Are you sure you want to mark " + cn + " as being a workgroup? "); + if (confirmed) { + Node workgroupHome = CmsJcrUtils.getGroupHome(groupsSession, cn); + if (workgroupHome != null) + return; // already marked as workgroup, do nothing + else { + // improve transaction management + userAdminWrapper.beginTransactionIfNeeded(); + nodeInstance.createWorkgroup(group.getName()); + setProperty(group, businessCategory, WORKGROUP); + userAdminWrapper.commitOrNotifyTransactionStateChange(); + userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, group)); + part.refresh(); + } + } + } + }); + + ModifyListener defaultListener = new FormPartML(part); + descTxt.addModifyListener(defaultListener); + getManagedForm().addPart(part); + } + + /** Filtered table with members. Has drag and drop ability */ + protected void appendMembersPart(Composite parent, Group group) { + // Section section = tk.createSection(parent, Section.TITLE_BAR); + // section.setText("Members"); + // section.setLayoutData(EclipseUiUtils.fillAll()); + + Composite body = new Composite(parent, SWT.BORDER); + body.setLayout(new GridLayout()); + // section.setClient(body); + body.setLayoutData(EclipseUiUtils.fillAll()); + + // Define the displayed columns + List columnDefs = new ArrayList(); + columnDefs.add(new ColumnDefinition(new RoleIconLP(), "", 0, 24)); + columnDefs.add(new ColumnDefinition(new CommonNameLP(), "Name", 150)); + columnDefs.add(new ColumnDefinition(new MailLP(), "Mail", 150)); + // columnDefs.add(new ColumnDefinition(new UserNameLP(), "Distinguished Name", + // 240)); + + // Create and configure the table + LdifUsersTable userViewerCmp = new MyUserTableViewer(body, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL, + userAdminWrapper.getUserAdmin()); + + userViewerCmp.setColumnDefinitions(columnDefs); + userViewerCmp.populate(true, false); + userViewerCmp.setLayoutData(EclipseUiUtils.fillAll()); + + // Controllers + TableViewer userViewer = userViewerCmp.getTableViewer(); + userViewer.addDoubleClickListener(new UserTableDefaultDClickListener(partService)); + int operations = DND.DROP_COPY | DND.DROP_MOVE; + Transfer[] tt = new Transfer[] { TextTransfer.getInstance() }; + userViewer.addDropSupport(operations, tt, + new GroupDropListener(userAdminWrapper, userViewerCmp, (Group) getDisplayedUser())); + + AbstractFormPart part = new GroupMembersPart(userViewerCmp); + getManagedForm().addPart(part); + + // remove button + // addRemoveAbility(toolBarManager, userViewerCmp.getTableViewer(), group); + Action action = new RemoveMembershipAction(userViewer, group, "Remove selected items from this group", + SecurityAdminImages.ICON_REMOVE_DESC); + + ToolBarManager toolBarManager = new ToolBarManager(SWT.FLAT); + ToolBar toolBar = toolBarManager.createControl(body); + toolBar.setLayoutData(CmsSwtUtils.fillWidth()); + + toolBarManager.add(action); + toolBarManager.update(true); + + } + + // private LdifUsersTable createMemberPart(Composite parent, Group group) { + // + // // Define the displayed columns + // List columnDefs = new ArrayList(); + // columnDefs.add(new ColumnDefinition(new RoleIconLP(), "", 0, 24)); + // columnDefs.add(new ColumnDefinition(new CommonNameLP(), "Name", 150)); + // columnDefs.add(new ColumnDefinition(new MailLP(), "Mail", 150)); + // // columnDefs.add(new ColumnDefinition(new UserNameLP(), "Distinguished + // Name", + // // 240)); + // + // // Create and configure the table + // LdifUsersTable userViewerCmp = new MyUserTableViewer(parent, SWT.MULTI | + // SWT.H_SCROLL | SWT.V_SCROLL, + // userAdminWrapper.getUserAdmin()); + // + // userViewerCmp.setColumnDefinitions(columnDefs); + // userViewerCmp.populate(true, false); + // userViewerCmp.setLayoutData(EclipseUiUtils.fillAll()); + // + // // Controllers + // TableViewer userViewer = userViewerCmp.getTableViewer(); + // userViewer.addDoubleClickListener(new + // UserTableDefaultDClickListener(partService)); + // int operations = DND.DROP_COPY | DND.DROP_MOVE; + // Transfer[] tt = new Transfer[] { TextTransfer.getInstance() }; + // userViewer.addDropSupport(operations, tt, + // new GroupDropListener(userAdminWrapper, userViewerCmp, (Group) + // getDisplayedUser())); + // + // // userViewerCmp.refresh(); + // return userViewerCmp; + // } + + // Local viewers + private class MyUserTableViewer extends LdifUsersTable { + private static final long serialVersionUID = 8467999509931900367L; + + private final UserFilter userFilter; + + public MyUserTableViewer(Composite parent, int style, UserAdmin userAdmin) { + super(parent, style, true); + userFilter = new UserFilter(); + + } + + @Override + protected List listFilteredElements(String filter) { + // reload user and set it in the editor + Group group = (Group) getDisplayedUser(); + Role[] roles = group.getMembers(); + List users = new ArrayList(); + userFilter.setSearchText(filter); + // userFilter.setShowSystemRole(true); + for (Role role : roles) + // if (role.getType() == Role.GROUP) + if (userFilter.select(null, null, role)) + users.add((User) role); + return users; + } + } + + // private void addRemoveAbility(ToolBarManager toolBarManager, TableViewer + // userViewer, Group group) { + // // Section section = sectionPart.getSection(); + // // ToolBarManager toolBarManager = new ToolBarManager(SWT.FLAT); + // // ToolBar toolbar = toolBarManager.createControl(parent); + // // ToolBar toolbar = toolBarManager.getControl(); + // // final Cursor handCursor = new Cursor(toolbar.getDisplay(), + // SWT.CURSOR_HAND); + // // toolbar.setCursor(handCursor); + // // toolbar.addDisposeListener(new DisposeListener() { + // // private static final long serialVersionUID = 3882131405820522925L; + // // + // // public void widgetDisposed(DisposeEvent e) { + // // if ((handCursor != null) && (handCursor.isDisposed() == false)) { + // // handCursor.dispose(); + // // } + // // } + // // }); + // + // Action action = new RemoveMembershipAction(userViewer, group, "Remove + // selected items from this group", + // SecurityAdminImages.ICON_REMOVE_DESC); + // toolBarManager.add(action); + // toolBarManager.update(true); + // // section.setTextClient(toolbar); + // } + + private class RemoveMembershipAction extends Action { + private static final long serialVersionUID = -1337713097184522588L; + + private final TableViewer userViewer; + private final Group group; + + RemoveMembershipAction(TableViewer userViewer, Group group, String name, ImageDescriptor img) { + super(name, img); + this.userViewer = userViewer; + this.group = group; + } + + @Override + public void run() { + ISelection selection = userViewer.getSelection(); + if (selection.isEmpty()) + return; + + @SuppressWarnings("unchecked") + Iterator it = ((IStructuredSelection) selection).iterator(); + List users = new ArrayList(); + while (it.hasNext()) { + User currUser = it.next(); + users.add(currUser); + } + + userAdminWrapper.beginTransactionIfNeeded(); + for (User user : users) { + group.removeMember(user); + } + userAdminWrapper.commitOrNotifyTransactionStateChange(); + userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, group)); + } + } + + // LOCAL CONTROLLERS + private class GroupMembersPart extends AbstractFormPart { + private final LdifUsersTable userViewer; + // private final Group group; + + private GroupChangeListener listener; + + public GroupMembersPart(LdifUsersTable userViewer) { + // super(section); + this.userViewer = userViewer; + // this.group = group; + } + + @Override + public void initialize(IManagedForm form) { + super.initialize(form); + listener = new GroupChangeListener(userViewer.getDisplay(), GroupMembersPart.this); + userAdminWrapper.addListener(listener); + } + + @Override + public void dispose() { + userAdminWrapper.removeListener(listener); + super.dispose(); + } + + @Override + public void refresh() { + userViewer.refresh(); + super.refresh(); + } + } + + /** + * Defines this table as being a potential target to add group membership + * (roles) to this group + */ + private class GroupDropListener extends ViewerDropAdapter { + private static final long serialVersionUID = 2893468717831451621L; + + private final UserAdminWrapper userAdminWrapper; + // private final LdifUsersTable myUserViewerCmp; + private final Group myGroup; + + public GroupDropListener(UserAdminWrapper userAdminWrapper, LdifUsersTable userTableViewerCmp, Group group) { + super(userTableViewerCmp.getTableViewer()); + this.userAdminWrapper = userAdminWrapper; + this.myGroup = group; + // this.myUserViewerCmp = userTableViewerCmp; + } + + @Override + public boolean validateDrop(Object target, int operation, TransferData transferType) { + // Target is always OK in a list only view + // TODO check if not a string + boolean validDrop = true; + return validDrop; + } + + @Override + public void drop(DropTargetEvent event) { + // TODO Is there an opportunity to perform the check before? + String newUserName = (String) event.data; + UserAdmin myUserAdmin = userAdminWrapper.getUserAdmin(); + Role role = myUserAdmin.getRole(newUserName); + if (role.getType() == Role.GROUP) { + Group newGroup = (Group) role; + Shell shell = getViewer().getControl().getShell(); + // Sanity checks + if (myGroup == newGroup) { // Equality + MessageDialog.openError(shell, "Forbidden addition ", "A group cannot be a member of itself."); + return; + } + + // Cycle + String myName = myGroup.getName(); + List myMemberships = getFlatGroups(myGroup); + if (myMemberships.contains(newGroup)) { + MessageDialog.openError(shell, "Forbidden addition: cycle", + "Cannot add " + newUserName + " to group " + myName + ". This would create a cycle"); + return; + } + + // Already member + List newGroupMemberships = getFlatGroups(newGroup); + if (newGroupMemberships.contains(myGroup)) { + MessageDialog.openError(shell, "Forbidden addition", + "Cannot add " + newUserName + " to group " + myName + ", this membership already exists"); + return; + } + userAdminWrapper.beginTransactionIfNeeded(); + myGroup.addMember(newGroup); + userAdminWrapper.commitOrNotifyTransactionStateChange(); + userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, myGroup)); + } else if (role.getType() == Role.USER) { + // TODO check if the group is already member of this group + WorkTransaction transaction = userAdminWrapper.beginTransactionIfNeeded(); + User user = (User) role; + myGroup.addMember(user); + if (UserAdminWrapper.COMMIT_ON_SAVE) + try { + transaction.commit(); + } catch (Exception e) { + throw new IllegalStateException( + "Cannot commit transaction " + "after user group membership update", e); + } + userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, myGroup)); + } + super.drop(event); + } + + @Override + public boolean performDrop(Object data) { + // myUserViewerCmp.refresh(); + return true; + } + } + + // LOCAL HELPERS + // private Composite addSection(FormToolkit tk, Composite parent) { + // Section section = tk.createSection(parent, SWT.NO_FOCUS); + // section.setLayoutData(EclipseUiUtils.fillWidth()); + // Composite body = tk.createComposite(section, SWT.WRAP); + // body.setLayoutData(EclipseUiUtils.fillAll()); + // section.setClient(body); + // return body; + // } + + /** Creates label and text. */ + // private Text createLT(Composite parent, String label, String value) { + // FormToolkit toolkit = getManagedForm().getToolkit(); + // Label lbl = toolkit.createLabel(parent, label); + // lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false)); + // lbl.setFont(EclipseUiUtils.getBoldFont(parent)); + // Text text = toolkit.createText(parent, value, SWT.BORDER); + // text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); + // CmsUiUtils.style(text, CmsWorkbenchStyles.WORKBENCH_FORM_TEXT); + // return text; + // } + // + // Text createReadOnlyLT(Composite parent, String label, String value) { + // FormToolkit toolkit = getManagedForm().getToolkit(); + // Label lbl = toolkit.createLabel(parent, label); + // lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false)); + // lbl.setFont(EclipseUiUtils.getBoldFont(parent)); + // Text text = toolkit.createText(parent, value, SWT.NONE); + // text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); + // text.setEditable(false); + // CmsUiUtils.style(text, CmsWorkbenchStyles.WORKBENCH_FORM_TEXT); + // return text; + // } + +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupsView.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupsView.java new file mode 100644 index 000000000..ddad34fd1 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupsView.java @@ -0,0 +1,251 @@ +package org.argeo.cms.e4.users; + +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.inject.Inject; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.CmsException; +import org.argeo.cms.auth.CurrentUser; +import org.argeo.cms.e4.users.providers.CommonNameLP; +import org.argeo.cms.e4.users.providers.DomainNameLP; +import org.argeo.cms.e4.users.providers.RoleIconLP; +import org.argeo.cms.e4.users.providers.UserDragListener; +//import org.argeo.cms.ui.workbench.WorkbenchUiPlugin; +//import org.argeo.cms.ui.workbench.internal.useradmin.UiUserAdminListener; +//import org.argeo.cms.ui.workbench.internal.useradmin.UserAdminWrapper; +//import org.argeo.cms.ui.workbench.internal.useradmin.providers.CommonNameLP; +//import org.argeo.cms.ui.workbench.internal.useradmin.providers.DomainNameLP; +//import org.argeo.cms.ui.workbench.internal.useradmin.providers.RoleIconLP; +//import org.argeo.cms.ui.workbench.internal.useradmin.providers.UserDragListener; +//import org.argeo.cms.ui.workbench.internal.useradmin.providers.UserTableDefaultDClickListener; +import org.argeo.eclipse.ui.ColumnDefinition; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.argeo.eclipse.ui.parts.LdifUsersTable; +import org.argeo.util.naming.LdapAttrs; +import org.argeo.util.naming.LdapObjs; +import org.eclipse.e4.ui.di.Focus; +import org.eclipse.e4.ui.workbench.modeling.EPartService; +import org.eclipse.e4.ui.workbench.modeling.ESelectionService; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.dnd.DND; +import org.eclipse.swt.dnd.TextTransfer; +import org.eclipse.swt.dnd.Transfer; +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.Display; +//import org.eclipse.ui.part.ViewPart; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.User; +import org.osgi.service.useradmin.UserAdminEvent; +import org.osgi.service.useradmin.UserAdminListener; + +/** List all groups with filter */ +public class GroupsView { + private final static CmsLog log = CmsLog.getLog(GroupsView.class); + // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + ".groupsView"; + + @Inject + private EPartService partService; + @Inject + private UserAdminWrapper userAdminWrapper; + + // UI Objects + private LdifUsersTable groupTableViewerCmp; + private TableViewer userViewer; + private List columnDefs = new ArrayList(); + + private UserAdminListener listener; + + @PostConstruct + public void createPartControl(Composite parent, ESelectionService selectionService) { + parent.setLayout(EclipseUiUtils.noSpaceGridLayout()); + + // boolean isAdmin = CurrentUser.isInRole(NodeConstants.ROLE_ADMIN); + + // Define the displayed columns + columnDefs.add(new ColumnDefinition(new RoleIconLP(), "", 19)); + columnDefs.add(new ColumnDefinition(new CommonNameLP(), "Name", 150)); + columnDefs.add(new ColumnDefinition(new DomainNameLP(), "Domain", 100)); + // Only show technical DN to admin + // if (isAdmin) + // columnDefs.add(new ColumnDefinition(new UserNameLP(), + // "Distinguished Name", 300)); + + // Create and configure the table + groupTableViewerCmp = new MyUserTableViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); + + groupTableViewerCmp.setColumnDefinitions(columnDefs); + // if (isAdmin) + // groupTableViewerCmp.populateWithStaticFilters(false, false); + // else + groupTableViewerCmp.populate(true, false); + + groupTableViewerCmp.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + // Links + userViewer = groupTableViewerCmp.getTableViewer(); + userViewer.addDoubleClickListener(new UserTableDefaultDClickListener(partService)); + // getViewSite().setSelectionProvider(userViewer); + userViewer.addSelectionChangedListener(new ISelectionChangedListener() { + + @Override + public void selectionChanged(SelectionChangedEvent event) { + IStructuredSelection selection = (IStructuredSelection) event.getSelection(); + selectionService.setSelection(selection.toList()); + } + }); + + // Really? + groupTableViewerCmp.refresh(); + + // Drag and drop + int operations = DND.DROP_COPY | DND.DROP_MOVE; + Transfer[] tt = new Transfer[] { TextTransfer.getInstance() }; + userViewer.addDragSupport(operations, tt, new UserDragListener(userViewer)); + + // // Register a useradmin listener + // listener = new UserAdminListener() { + // @Override + // public void roleChanged(UserAdminEvent event) { + // if (userViewer != null && !userViewer.getTable().isDisposed()) + // refresh(); + // } + // }; + // userAdminWrapper.addListener(listener); + // } + + // Register a useradmin listener + listener = new MyUiUAListener(parent.getDisplay()); + userAdminWrapper.addListener(listener); + } + + private class MyUiUAListener extends UiUserAdminListener { + public MyUiUAListener(Display display) { + super(display); + } + + @Override + public void roleChangedToUiThread(UserAdminEvent event) { + if (userViewer != null && !userViewer.getTable().isDisposed()) + refresh(); + } + } + + private class MyUserTableViewer extends LdifUsersTable { + private static final long serialVersionUID = 8467999509931900367L; + + private boolean showSystemRoles = true; + + private final String[] knownProps = { LdapAttrs.uid.name(), LdapAttrs.cn.name(), LdapAttrs.DN }; + + public MyUserTableViewer(Composite parent, int style) { + super(parent, style); + showSystemRoles = CurrentUser.isInRole(CmsConstants.ROLE_ADMIN); + } + + protected void populateStaticFilters(Composite staticFilterCmp) { + staticFilterCmp.setLayout(new GridLayout()); + final Button showSystemRoleBtn = new Button(staticFilterCmp, SWT.CHECK); + showSystemRoleBtn.setText("Show system roles"); + showSystemRoles = CurrentUser.isInRole(CmsConstants.ROLE_ADMIN); + showSystemRoleBtn.setSelection(showSystemRoles); + + showSystemRoleBtn.addSelectionListener(new SelectionAdapter() { + private static final long serialVersionUID = -7033424592697691676L; + + @Override + public void widgetSelected(SelectionEvent e) { + showSystemRoles = showSystemRoleBtn.getSelection(); + refresh(); + } + + }); + } + + @Override + protected List listFilteredElements(String filter) { + Role[] roles; + try { + StringBuilder builder = new StringBuilder(); + StringBuilder tmpBuilder = new StringBuilder(); + if (EclipseUiUtils.notEmpty(filter)) + for (String prop : knownProps) { + tmpBuilder.append("("); + tmpBuilder.append(prop); + tmpBuilder.append("=*"); + tmpBuilder.append(filter); + tmpBuilder.append("*)"); + } + if (tmpBuilder.length() > 1) { + builder.append("(&(").append(LdapAttrs.objectClass.name()).append("=") + .append(LdapObjs.groupOfNames.name()).append(")"); + // hide tokens + builder.append("(!(").append(LdapAttrs.DN).append("=*").append(CmsConstants.TOKENS_BASEDN) + .append("))"); + + if (!showSystemRoles) + builder.append("(!(").append(LdapAttrs.DN).append("=*").append(CmsConstants.ROLES_BASEDN) + .append("))"); + builder.append("(|"); + builder.append(tmpBuilder.toString()); + builder.append("))"); + } else { + if (!showSystemRoles) + builder.append("(&(").append(LdapAttrs.objectClass.name()).append("=") + .append(LdapObjs.groupOfNames.name()).append(")(!(").append(LdapAttrs.DN).append("=*") + .append(CmsConstants.ROLES_BASEDN).append("))(!(").append(LdapAttrs.DN).append("=*") + .append(CmsConstants.TOKENS_BASEDN).append(")))"); + else + builder.append("(&(").append(LdapAttrs.objectClass.name()).append("=") + .append(LdapObjs.groupOfNames.name()).append(")(!(").append(LdapAttrs.DN).append("=*") + .append(CmsConstants.TOKENS_BASEDN).append(")))"); + + } + roles = userAdminWrapper.getUserAdmin().getRoles(builder.toString()); + } catch (InvalidSyntaxException e) { + throw new CmsException("Unable to get roles with filter: " + filter, e); + } + List users = new ArrayList(); + for (Role role : roles) + if (!users.contains(role)) + users.add((User) role); + else + log.warn("Duplicated role: " + role); + + return users; + } + } + + public void refresh() { + groupTableViewerCmp.refresh(); + } + + @PreDestroy + public void dispose() { + userAdminWrapper.removeListener(listener); + } + + @Focus + public void setFocus() { + groupTableViewerCmp.setFocus(); + } + + /* DEPENDENCY INJECTION */ + public void setUserAdminWrapper(UserAdminWrapper userAdminWrapper) { + this.userAdminWrapper = userAdminWrapper; + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/SecurityAdminImages.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/SecurityAdminImages.java new file mode 100644 index 000000000..7bbe3c727 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/SecurityAdminImages.java @@ -0,0 +1,19 @@ +package org.argeo.cms.e4.users; + +import org.argeo.cms.ui.theme.CmsImages; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.swt.graphics.Image; + +/** Shared icons that must be declared programmatically . */ +public class SecurityAdminImages extends CmsImages { + private final static String PREFIX = "icons/"; + + public final static ImageDescriptor ICON_REMOVE_DESC = createDesc(PREFIX + "delete.png"); + public final static ImageDescriptor ICON_USER_DESC = createDesc(PREFIX + "person.png"); + + public final static Image ICON_USER = ICON_USER_DESC.createImage(); + public final static Image ICON_GROUP = createImg(PREFIX + "group.png"); + public final static Image ICON_WORKGROUP = createImg(PREFIX + "workgroup.png"); + public final static Image ICON_ROLE = createImg(PREFIX + "role.gif"); + +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UiAdminUtils.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UiAdminUtils.java new file mode 100644 index 000000000..f85649260 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UiAdminUtils.java @@ -0,0 +1,34 @@ +package org.argeo.cms.e4.users; + +import org.argeo.osgi.transaction.WorkTransaction; + +/** First effort to centralize back end methods used by the user admin UI */ +public class UiAdminUtils { + /* + * INTERNAL METHODS: Below methods are meant to stay here and are not part + * of a potential generic backend to manage the useradmin + */ + /** Easily notify the ActiveWindow that the transaction had a state change */ + public final static void notifyTransactionStateChange( + WorkTransaction userTransaction) { +// try { +// IWorkbenchWindow aww = PlatformUI.getWorkbench() +// .getActiveWorkbenchWindow(); +// ISourceProviderService sourceProviderService = (ISourceProviderService) aww +// .getService(ISourceProviderService.class); +// UserTransactionProvider esp = (UserTransactionProvider) sourceProviderService +// .getSourceProvider(UserTransactionProvider.TRANSACTION_STATE); +// esp.fireTransactionStateChange(); +// } catch (Exception e) { +// throw new CmsException("Unable to begin transaction", e); +// } + } + + /** + * Email addresses must match this regexp pattern ({@value #EMAIL_PATTERN}. + * Thanks to this tip. + */ + public final static String EMAIL_PATTERN = "^[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$"; +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UiUserAdminListener.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UiUserAdminListener.java new file mode 100644 index 000000000..eb64aba0e --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UiUserAdminListener.java @@ -0,0 +1,27 @@ +package org.argeo.cms.e4.users; + +import org.eclipse.swt.widgets.Display; +import org.osgi.service.useradmin.UserAdminEvent; +import org.osgi.service.useradmin.UserAdminListener; + +/** Convenience class to insure the call to refresh is done in the UI thread */ +public abstract class UiUserAdminListener implements UserAdminListener { + + private final Display display; + + public UiUserAdminListener(Display display) { + this.display = display; + } + + @Override + public void roleChanged(final UserAdminEvent event) { + display.asyncExec(new Runnable() { + @Override + public void run() { + roleChangedToUiThread(event); + } + }); + } + + public abstract void roleChangedToUiThread(UserAdminEvent event); +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserAdminWrapper.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserAdminWrapper.java new file mode 100644 index 000000000..16aa78316 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserAdminWrapper.java @@ -0,0 +1,153 @@ +package org.argeo.cms.e4.users; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.cms.CmsException; +import org.argeo.osgi.transaction.WorkTransaction; +import org.argeo.osgi.useradmin.UserAdminConf; +import org.argeo.osgi.useradmin.UserDirectory; +import org.osgi.service.useradmin.UserAdmin; +import org.osgi.service.useradmin.UserAdminEvent; +import org.osgi.service.useradmin.UserAdminListener; + +/** Centralise interaction with the UserAdmin in this bundle */ +public class UserAdminWrapper { + + private UserAdmin userAdmin; + // private ServiceReference userAdminServiceReference; +// private Set uris; + private Map> userDirectories = Collections + .synchronizedMap(new LinkedHashMap<>()); + private WorkTransaction userTransaction; + + // First effort to simplify UX while managing users and groups + public final static boolean COMMIT_ON_SAVE = true; + + // Registered listeners + List listeners = new ArrayList(); + + /** + * Starts a transaction if necessary. Should always been called together with + * {@link UserAdminWrapper#commitOrNotifyTransactionStateChange()} once the + * security model changes have been performed. + */ + public WorkTransaction beginTransactionIfNeeded() { + try { + // UserTransaction userTransaction = getUserTransaction(); + if (userTransaction.isNoTransactionStatus()) { + userTransaction.begin(); + // UiAdminUtils.notifyTransactionStateChange(userTransaction); + } + return userTransaction; + } catch (Exception e) { + throw new CmsException("Unable to begin transaction", e); + } + } + + /** + * Depending on the current application configuration, it will either commit the + * current transaction or throw a notification that the transaction state has + * changed (In the later case, it must be called from the UI thread). + */ + public void commitOrNotifyTransactionStateChange() { + try { + // UserTransaction userTransaction = getUserTransaction(); + if (userTransaction.isNoTransactionStatus()) + return; + + if (UserAdminWrapper.COMMIT_ON_SAVE) + userTransaction.commit(); + else + UiAdminUtils.notifyTransactionStateChange(userTransaction); + } catch (Exception e) { + throw new CmsException("Unable to clean transaction", e); + } + } + + // TODO implement safer mechanism + public void addListener(UserAdminListener userAdminListener) { + if (!listeners.contains(userAdminListener)) + listeners.add(userAdminListener); + } + + public void removeListener(UserAdminListener userAdminListener) { + if (listeners.contains(userAdminListener)) + listeners.remove(userAdminListener); + } + + public void notifyListeners(UserAdminEvent event) { + for (UserAdminListener listener : listeners) + listener.roleChanged(event); + } + + public Map getKnownBaseDns(boolean onlyWritable) { + Map dns = new HashMap(); + for (UserDirectory userDirectory : userDirectories.keySet()) { + Boolean readOnly = userDirectory.isReadOnly(); + String baseDn = userDirectory.getBaseDn().toString(); + + if (onlyWritable && readOnly) + continue; + if (baseDn.equalsIgnoreCase(CmsConstants.ROLES_BASEDN)) + continue; + if (baseDn.equalsIgnoreCase(CmsConstants.TOKENS_BASEDN)) + continue; + dns.put(baseDn, UserAdminConf.propertiesAsUri(userDirectories.get(userDirectory)).toString()); + + } +// for (String uri : uris) { +// if (!uri.startsWith("/")) +// continue; +// Dictionary props = UserAdminConf.uriAsProperties(uri); +// String readOnly = UserAdminConf.readOnly.getValue(props); +// String baseDn = UserAdminConf.baseDn.getValue(props); +// +// if (onlyWritable && "true".equals(readOnly)) +// continue; +// if (baseDn.equalsIgnoreCase(NodeConstants.ROLES_BASEDN)) +// continue; +// if (baseDn.equalsIgnoreCase(NodeConstants.TOKENS_BASEDN)) +// continue; +// dns.put(baseDn, uri); +// } + return dns; + } + + public UserAdmin getUserAdmin() { + return userAdmin; + } + + public WorkTransaction getUserTransaction() { + return userTransaction; + } + + /* DEPENDENCY INJECTION */ + public void setUserAdmin(UserAdmin userAdmin, Map properties) { + this.userAdmin = userAdmin; +// this.uris = Collections.unmodifiableSortedSet(new TreeSet<>(properties.keySet())); + } + + public void setUserTransaction(WorkTransaction userTransaction) { + this.userTransaction = userTransaction; + } + + public void addUserDirectory(UserDirectory userDirectory, Map properties) { + userDirectories.put(userDirectory, new Hashtable<>(properties)); + } + + public void removeUserDirectory(UserDirectory userDirectory, Map properties) { + userDirectories.remove(userDirectory); + } + + // public void setUserAdminServiceReference( + // ServiceReference userAdminServiceReference) { + // this.userAdminServiceReference = userAdminServiceReference; + // } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserBatchUpdateWizard.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserBatchUpdateWizard.java new file mode 100644 index 000000000..a38d171ef --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserBatchUpdateWizard.java @@ -0,0 +1,606 @@ +package org.argeo.cms.e4.users; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.CmsException; +import org.argeo.cms.auth.CurrentUser; +import org.argeo.cms.auth.UserAdminUtils; +import org.argeo.cms.e4.users.providers.CommonNameLP; +import org.argeo.cms.e4.users.providers.DomainNameLP; +import org.argeo.cms.e4.users.providers.MailLP; +import org.argeo.cms.e4.users.providers.UserNameLP; +import org.argeo.eclipse.ui.ColumnDefinition; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.argeo.eclipse.ui.parts.LdifUsersTable; +import org.argeo.osgi.transaction.WorkTransaction; +import org.argeo.util.naming.LdapAttrs; +import org.argeo.util.naming.LdapObjs; +import org.eclipse.jface.dialogs.IPageChangeProvider; +import org.eclipse.jface.dialogs.IPageChangedListener; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.dialogs.PageChangedEvent; +import org.eclipse.jface.wizard.IWizardContainer; +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +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.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Text; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.User; +import org.osgi.service.useradmin.UserAdminEvent; + +/** Wizard to update users */ +public class UserBatchUpdateWizard extends Wizard { + + private final static CmsLog log = CmsLog.getLog(UserBatchUpdateWizard.class); + private UserAdminWrapper userAdminWrapper; + + // pages + private ChooseCommandWizardPage chooseCommandPage; + private ChooseUsersWizardPage userListPage; + private ValidateAndLaunchWizardPage validatePage; + + // Various implemented commands keys + private final static String CMD_UPDATE_PASSWORD = "resetPassword"; + private final static String CMD_UPDATE_EMAIL = "resetEmail"; + private final static String CMD_GROUP_MEMBERSHIP = "groupMembership"; + + private final Map commands = new HashMap() { + private static final long serialVersionUID = 1L; + { + put("Reset password(s)", CMD_UPDATE_PASSWORD); + put("Reset email(s)", CMD_UPDATE_EMAIL); + // TODO implement role / group management + // put("Add/Remove from group", CMD_GROUP_MEMBERSHIP); + } + }; + + public UserBatchUpdateWizard(UserAdminWrapper userAdminWrapper) { + this.userAdminWrapper = userAdminWrapper; + } + + @Override + public void addPages() { + chooseCommandPage = new ChooseCommandWizardPage(); + addPage(chooseCommandPage); + userListPage = new ChooseUsersWizardPage(); + addPage(userListPage); + validatePage = new ValidateAndLaunchWizardPage(); + addPage(validatePage); + } + + @Override + public boolean performFinish() { + if (!canFinish()) + return false; + WorkTransaction ut = userAdminWrapper.getUserTransaction(); + if (!ut.isNoTransactionStatus() && !MessageDialog.openConfirm(getShell(), "Existing Transaction", + "A user transaction is already existing, " + "are you sure you want to proceed ?")) + return false; + + // We cannot use jobs, user modifications are still meant to be done in + // the UIThread + // UpdateJob job = null; + // if (job != null) + // job.schedule(); + + if (CMD_UPDATE_PASSWORD.equals(chooseCommandPage.getCommand())) { + char[] newValue = chooseCommandPage.getPwdValue(); + if (newValue == null) + throw new CmsException("Password cannot be null or an empty string"); + ResetPassword job = new ResetPassword(userAdminWrapper, userListPage.getSelectedUsers(), newValue); + job.doUpdate(); + } else if (CMD_UPDATE_EMAIL.equals(chooseCommandPage.getCommand())) { + String newValue = chooseCommandPage.getEmailValue(); + if (newValue == null) + throw new CmsException("Password cannot be null or an empty string"); + ResetEmail job = new ResetEmail(userAdminWrapper, userListPage.getSelectedUsers(), newValue); + job.doUpdate(); + } + return true; + } + + public boolean canFinish() { + if (this.getContainer().getCurrentPage() == validatePage) + return true; + return false; + } + + private class ResetPassword { + private char[] newPwd; + private UserAdminWrapper userAdminWrapper; + private List usersToUpdate; + + public ResetPassword(UserAdminWrapper userAdminWrapper, List usersToUpdate, char[] newPwd) { + this.newPwd = newPwd; + this.usersToUpdate = usersToUpdate; + this.userAdminWrapper = userAdminWrapper; + } + + @SuppressWarnings("unchecked") + protected void doUpdate() { + userAdminWrapper.beginTransactionIfNeeded(); + try { + for (User user : usersToUpdate) { + // the char array is emptied after being used. + user.getCredentials().put(null, newPwd.clone()); + } + userAdminWrapper.commitOrNotifyTransactionStateChange(); + } catch (Exception e) { + throw new CmsException("Cannot perform batch update on users", e); + } finally { + WorkTransaction ut = userAdminWrapper.getUserTransaction(); + if (!ut.isNoTransactionStatus()) + ut.rollback(); + } + } + } + + private class ResetEmail { + private String newEmail; + private UserAdminWrapper userAdminWrapper; + private List usersToUpdate; + + public ResetEmail(UserAdminWrapper userAdminWrapper, List usersToUpdate, String newEmail) { + this.newEmail = newEmail; + this.usersToUpdate = usersToUpdate; + this.userAdminWrapper = userAdminWrapper; + } + + @SuppressWarnings("unchecked") + protected void doUpdate() { + userAdminWrapper.beginTransactionIfNeeded(); + try { + for (User user : usersToUpdate) { + // the char array is emptied after being used. + user.getProperties().put(LdapAttrs.mail.name(), newEmail); + } + + userAdminWrapper.commitOrNotifyTransactionStateChange(); + if (!usersToUpdate.isEmpty()) + userAdminWrapper.notifyListeners( + new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, usersToUpdate.get(0))); + } catch (Exception e) { + throw new CmsException("Cannot perform batch update on users", e); + } finally { + WorkTransaction ut = userAdminWrapper.getUserTransaction(); + if (!ut.isNoTransactionStatus()) + ut.rollback(); + } + } + } + + // @SuppressWarnings("unused") + // private class AddToGroup extends UpdateJob { + // private String groupID; + // private Session session; + // + // public AddToGroup(Session session, List nodesToUpdate, + // String groupID) { + // super(session, nodesToUpdate); + // this.session = session; + // this.groupID = groupID; + // } + // + // protected void doUpdate(Node node) { + // log.info("Add/Remove to group actions are not yet implemented"); + // // TODO implement this + // // try { + // // throw new CmsException("Not yet implemented"); + // // } catch (RepositoryException re) { + // // throw new CmsException( + // // "Unable to update boolean value for node " + node, re); + // // } + // } + // } + + // /** + // * Base privileged job that will be run asynchronously to perform the + // batch + // * update + // */ + // private abstract class UpdateJob extends PrivilegedJob { + // + // private final UserAdminWrapper userAdminWrapper; + // private final List usersToUpdate; + // + // protected abstract void doUpdate(User user); + // + // public UpdateJob(UserAdminWrapper userAdminWrapper, + // List usersToUpdate) { + // super("Perform update"); + // this.usersToUpdate = usersToUpdate; + // this.userAdminWrapper = userAdminWrapper; + // } + // + // @Override + // protected IStatus doRun(IProgressMonitor progressMonitor) { + // try { + // JcrMonitor monitor = new EclipseJcrMonitor(progressMonitor); + // int total = usersToUpdate.size(); + // monitor.beginTask("Performing change", total); + // userAdminWrapper.beginTransactionIfNeeded(); + // for (User user : usersToUpdate) { + // doUpdate(user); + // monitor.worked(1); + // } + // userAdminWrapper.getUserTransaction().commit(); + // } catch (Exception e) { + // throw new CmsException( + // "Cannot perform batch update on users", e); + // } finally { + // UserTransaction ut = userAdminWrapper.getUserTransaction(); + // try { + // if (ut.getStatus() != javax.transaction.Status.STATUS_NO_TRANSACTION) + // ut.rollback(); + // } catch (IllegalStateException | SecurityException + // | SystemException e) { + // log.error("Unable to rollback session in 'finally', " + // + "the system might be in a dirty state"); + // e.printStackTrace(); + // } + // } + // return Status.OK_STATUS; + // } + // } + + // PAGES + /** + * Displays a combo box that enables user to choose which action to perform + */ + private class ChooseCommandWizardPage extends WizardPage { + private static final long serialVersionUID = -8069434295293996633L; + private Combo chooseCommandCmb; + private Button trueChk; + private Text valueTxt; + private Text pwdTxt; + private Text pwd2Txt; + + public ChooseCommandWizardPage() { + super("Choose a command to run."); + setTitle("Choose a command to run."); + } + + @Override + public void createControl(Composite parent) { + GridLayout gl = new GridLayout(); + Composite container = new Composite(parent, SWT.NO_FOCUS); + container.setLayout(gl); + + chooseCommandCmb = new Combo(container, SWT.READ_ONLY); + chooseCommandCmb.setLayoutData(EclipseUiUtils.fillWidth()); + String[] values = commands.keySet().toArray(new String[0]); + chooseCommandCmb.setItems(values); + + final Composite bottomPart = new Composite(container, SWT.NO_FOCUS); + bottomPart.setLayoutData(EclipseUiUtils.fillAll()); + bottomPart.setLayout(EclipseUiUtils.noSpaceGridLayout()); + + chooseCommandCmb.addSelectionListener(new SelectionAdapter() { + private static final long serialVersionUID = 1L; + + @Override + public void widgetSelected(SelectionEvent e) { + if (getCommand().equals(CMD_UPDATE_PASSWORD)) + populatePasswordCmp(bottomPart); + else if (getCommand().equals(CMD_UPDATE_EMAIL)) + populateEmailCmp(bottomPart); + else if (getCommand().equals(CMD_GROUP_MEMBERSHIP)) + populateGroupCmp(bottomPart); + else + populateBooleanFlagCmp(bottomPart); + checkPageComplete(); + bottomPart.layout(true, true); + } + }); + setControl(container); + } + + private void populateBooleanFlagCmp(Composite parent) { + EclipseUiUtils.clear(parent); + trueChk = new Button(parent, SWT.CHECK); + trueChk.setText("Do it. (It will to the contrary if unchecked)"); + trueChk.setSelection(true); + trueChk.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false)); + } + + private void populatePasswordCmp(Composite parent) { + EclipseUiUtils.clear(parent); + Composite body = new Composite(parent, SWT.NO_FOCUS); + + ModifyListener ml = new ModifyListener() { + private static final long serialVersionUID = -1558726363536729634L; + + @Override + public void modifyText(ModifyEvent event) { + checkPageComplete(); + } + }; + + body.setLayout(new GridLayout(2, false)); + body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + pwdTxt = EclipseUiUtils.createGridLP(body, "New password", ml); + pwd2Txt = EclipseUiUtils.createGridLP(body, "Repeat password", ml); + } + + private void populateEmailCmp(Composite parent) { + EclipseUiUtils.clear(parent); + Composite body = new Composite(parent, SWT.NO_FOCUS); + + ModifyListener ml = new ModifyListener() { + private static final long serialVersionUID = 2147704227294268317L; + + @Override + public void modifyText(ModifyEvent event) { + checkPageComplete(); + } + }; + + body.setLayout(new GridLayout(2, false)); + body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + valueTxt = EclipseUiUtils.createGridLT(body, "New e-mail", ml); + } + + private void checkPageComplete() { + String errorMsg = null; + if (chooseCommandCmb.getSelectionIndex() < 0) + errorMsg = "Please select an action"; + else if (CMD_UPDATE_EMAIL.equals(getCommand())) { + if (!valueTxt.getText().matches(UiAdminUtils.EMAIL_PATTERN)) + errorMsg = "Not a valid e-mail address"; + } else if (CMD_UPDATE_PASSWORD.equals(getCommand())) { + if (EclipseUiUtils.isEmpty(pwdTxt.getText()) || pwdTxt.getText().length() < 4) + errorMsg = "Please enter a password that is at least 4 character long"; + else if (!pwdTxt.getText().equals(pwd2Txt.getText())) + errorMsg = "Passwords are different"; + } + if (EclipseUiUtils.notEmpty(errorMsg)) { + setMessage(errorMsg, WizardPage.ERROR); + setPageComplete(false); + } else { + setMessage("Page complete, you can proceed to user choice", WizardPage.INFORMATION); + setPageComplete(true); + } + + getContainer().updateButtons(); + } + + private void populateGroupCmp(Composite parent) { + EclipseUiUtils.clear(parent); + trueChk = new Button(parent, SWT.CHECK); + trueChk.setText("Add to group. (It will remove user(s) from the " + "corresponding group if unchecked)"); + trueChk.setSelection(true); + trueChk.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false)); + } + + protected String getCommand() { + return commands.get(chooseCommandCmb.getItem(chooseCommandCmb.getSelectionIndex())); + } + + protected String getCommandLbl() { + return chooseCommandCmb.getItem(chooseCommandCmb.getSelectionIndex()); + } + + @SuppressWarnings("unused") + protected boolean getBoleanValue() { + // FIXME this is not consistent and will lead to errors. + if ("argeo:enabled".equals(getCommand())) + return trueChk.getSelection(); + else + return !trueChk.getSelection(); + } + + @SuppressWarnings("unused") + protected String getStringValue() { + String value = null; + if (valueTxt != null) { + value = valueTxt.getText(); + if ("".equals(value.trim())) + value = null; + } + return value; + } + + protected char[] getPwdValue() { + // We do not directly reset the password text fields: There is no + // need to over secure this process: setting a pwd to multi users + // at the same time is anyhow a bad practice and should be used only + // in test environment or for temporary access + if (pwdTxt == null || pwdTxt.isDisposed()) + return null; + else + return pwdTxt.getText().toCharArray(); + } + + protected String getEmailValue() { + // We do not directly reset the password text fields: There is no + // need to over secure this process: setting a pwd to multi users + // at the same time is anyhow a bad practice and should be used only + // in test environment or for temporary access + if (valueTxt == null || valueTxt.isDisposed()) + return null; + else + return valueTxt.getText(); + } + } + + /** + * Displays a list of users with a check box to be able to choose some of them + */ + private class ChooseUsersWizardPage extends WizardPage implements IPageChangedListener { + private static final long serialVersionUID = 7651807402211214274L; + private ChooseUserTableViewer userTableCmp; + + public ChooseUsersWizardPage() { + super("Choose Users"); + setTitle("Select users who will be impacted"); + } + + @Override + public void createControl(Composite parent) { + Composite pageCmp = new Composite(parent, SWT.NONE); + pageCmp.setLayout(EclipseUiUtils.noSpaceGridLayout()); + + // Define the displayed columns + List columnDefs = new ArrayList(); + columnDefs.add(new ColumnDefinition(new CommonNameLP(), "Common Name", 150)); + columnDefs.add(new ColumnDefinition(new MailLP(), "E-mail", 150)); + columnDefs.add(new ColumnDefinition(new DomainNameLP(), "Domain", 200)); + + // Only show technical DN to admin + if (CurrentUser.isInRole(CmsConstants.ROLE_ADMIN)) + columnDefs.add(new ColumnDefinition(new UserNameLP(), "Distinguished Name", 300)); + + userTableCmp = new ChooseUserTableViewer(pageCmp, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); + userTableCmp.setLayoutData(EclipseUiUtils.fillAll()); + userTableCmp.setColumnDefinitions(columnDefs); + userTableCmp.populate(true, true); + userTableCmp.refresh(); + + setControl(pageCmp); + + // Add listener to update message when shown + final IWizardContainer wContainer = this.getContainer(); + if (wContainer instanceof IPageChangeProvider) { + ((IPageChangeProvider) wContainer).addPageChangedListener(this); + } + + } + + @Override + public void pageChanged(PageChangedEvent event) { + if (event.getSelectedPage() == this) { + String msg = "Chosen batch action: " + chooseCommandPage.getCommandLbl(); + ((WizardPage) event.getSelectedPage()).setMessage(msg); + } + } + + protected List getSelectedUsers() { + return userTableCmp.getSelectedUsers(); + } + + private class ChooseUserTableViewer extends LdifUsersTable { + private static final long serialVersionUID = 5080437561015853124L; + private final String[] knownProps = { LdapAttrs.uid.name(), LdapAttrs.DN, LdapAttrs.cn.name(), + LdapAttrs.givenName.name(), LdapAttrs.sn.name(), LdapAttrs.mail.name() }; + + public ChooseUserTableViewer(Composite parent, int style) { + super(parent, style); + } + + @Override + protected List listFilteredElements(String filter) { + Role[] roles; + + try { + StringBuilder builder = new StringBuilder(); + + StringBuilder tmpBuilder = new StringBuilder(); + if (EclipseUiUtils.notEmpty(filter)) + for (String prop : knownProps) { + tmpBuilder.append("("); + tmpBuilder.append(prop); + tmpBuilder.append("=*"); + tmpBuilder.append(filter); + tmpBuilder.append("*)"); + } + if (tmpBuilder.length() > 1) { + builder.append("(&(").append(LdapAttrs.objectClass.name()).append("=") + .append(LdapObjs.inetOrgPerson.name()).append(")(|"); + builder.append(tmpBuilder.toString()); + builder.append("))"); + } else + builder.append("(").append(LdapAttrs.objectClass.name()).append("=") + .append(LdapObjs.inetOrgPerson.name()).append(")"); + roles = userAdminWrapper.getUserAdmin().getRoles(builder.toString()); + } catch (InvalidSyntaxException e) { + throw new CmsException("Unable to get roles with filter: " + filter, e); + } + List users = new ArrayList(); + for (Role role : roles) + // Prevent current logged in user to perform batch on + // himself + if (!UserAdminUtils.isCurrentUser((User) role)) + users.add((User) role); + return users; + } + } + } + + /** Summary of input data before launching the process */ + private class ValidateAndLaunchWizardPage extends WizardPage implements IPageChangedListener { + private static final long serialVersionUID = 7098918351451743853L; + private ChosenUsersTableViewer userTableCmp; + + public ValidateAndLaunchWizardPage() { + super("Validate and launch"); + setTitle("Validate and launch"); + } + + @Override + public void createControl(Composite parent) { + Composite pageCmp = new Composite(parent, SWT.NO_FOCUS); + pageCmp.setLayout(EclipseUiUtils.noSpaceGridLayout()); + + List columnDefs = new ArrayList(); + columnDefs.add(new ColumnDefinition(new CommonNameLP(), "Common Name", 150)); + columnDefs.add(new ColumnDefinition(new MailLP(), "E-mail", 150)); + columnDefs.add(new ColumnDefinition(new DomainNameLP(), "Domain", 200)); + // Only show technical DN to admin + if (CurrentUser.isInRole(CmsConstants.ROLE_ADMIN)) + columnDefs.add(new ColumnDefinition(new UserNameLP(), "Distinguished Name", 300)); + userTableCmp = new ChosenUsersTableViewer(pageCmp, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); + userTableCmp.setLayoutData(EclipseUiUtils.fillAll()); + userTableCmp.setColumnDefinitions(columnDefs); + userTableCmp.populate(false, false); + userTableCmp.refresh(); + setControl(pageCmp); + // Add listener to update message when shown + final IWizardContainer wContainer = this.getContainer(); + if (wContainer instanceof IPageChangeProvider) { + ((IPageChangeProvider) wContainer).addPageChangedListener(this); + } + } + + @Override + public void pageChanged(PageChangedEvent event) { + if (event.getSelectedPage() == this) { + @SuppressWarnings({ "unchecked", "rawtypes" }) + Object[] values = ((ArrayList) userListPage.getSelectedUsers()) + .toArray(new Object[userListPage.getSelectedUsers().size()]); + userTableCmp.getTableViewer().setInput(values); + String msg = "Following batch action: [" + chooseCommandPage.getCommandLbl() + + "] will be perfomed on the users listed below.\n"; + // + "Are you sure you want to proceed?"; + setMessage(msg); + } + } + + private class ChosenUsersTableViewer extends LdifUsersTable { + private static final long serialVersionUID = 7814764735794270541L; + + public ChosenUsersTableViewer(Composite parent, int style) { + super(parent, style); + } + + @Override + protected List listFilteredElements(String filter) { + return userListPage.getSelectedUsers(); + } + } + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserEditor.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserEditor.java new file mode 100644 index 000000000..66f442082 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserEditor.java @@ -0,0 +1,535 @@ +package org.argeo.cms.e4.users; + +import static org.argeo.cms.auth.UserAdminUtils.getProperty; +import static org.argeo.util.naming.LdapAttrs.cn; +import static org.argeo.util.naming.LdapAttrs.givenName; +import static org.argeo.util.naming.LdapAttrs.mail; +import static org.argeo.util.naming.LdapAttrs.sn; +import static org.argeo.util.naming.LdapAttrs.uid; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javax.inject.Inject; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.cms.auth.CurrentUser; +import org.argeo.cms.auth.UserAdminUtils; +import org.argeo.cms.e4.users.providers.CommonNameLP; +import org.argeo.cms.e4.users.providers.DomainNameLP; +import org.argeo.cms.e4.users.providers.RoleIconLP; +import org.argeo.cms.e4.users.providers.UserFilter; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.ui.eclipse.forms.AbstractFormPart; +//import org.argeo.cms.ui.eclipse.forms.FormToolkit; +import org.argeo.cms.ui.eclipse.forms.IManagedForm; +import org.argeo.eclipse.ui.ColumnDefinition; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.argeo.eclipse.ui.parts.LdifUsersTable; +import org.argeo.util.naming.LdapAttrs; +import org.eclipse.e4.ui.workbench.modeling.EPartService; +import org.eclipse.jface.action.Action; +import org.eclipse.jface.action.ToolBarManager; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.dialogs.TrayDialog; +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerDropAdapter; +import org.eclipse.swt.SWT; +import org.eclipse.swt.dnd.DND; +import org.eclipse.swt.dnd.DropTargetEvent; +import org.eclipse.swt.dnd.TextTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.dnd.TransferData; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +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.Display; +import org.eclipse.swt.widgets.Link; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; +import org.eclipse.swt.widgets.ToolBar; +import org.osgi.service.useradmin.Group; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.User; +import org.osgi.service.useradmin.UserAdmin; +import org.osgi.service.useradmin.UserAdminEvent; + +/** Display/edit the properties of a given user */ +public class UserEditor extends AbstractRoleEditor { + // final static String ID = "UserEditor.mainPage"; + + @Inject + private EPartService partService; + + // private final UserEditor editor; + // private UserAdminWrapper userAdminWrapper; + + // Local configuration + // private final int PRE_TITLE_INDENT = 10; + + // public UserMainPage(FormEditor editor, UserAdminWrapper userAdminWrapper) { + // super(editor, ID, "Main"); + // this.editor = (UserEditor) editor; + // this.userAdminWrapper = userAdminWrapper; + // } + + // protected void createFormContent(final IManagedForm mf) { + // ScrolledForm form = mf.getForm(); + // Composite body = form.getBody(); + // GridLayout mainLayout = new GridLayout(); + // // mainLayout.marginRight = 10; + // body.setLayout(mainLayout); + // User user = editor.getDisplayedUser(); + // appendOverviewPart(body, user); + // // Remove to ability to force the password for his own user. The user + // // must then use the change pwd feature + // appendMemberOfPart(body, user); + // } + + @Override + protected void createUi(Composite body) { + // Composite body = new Composite(parent, SWT.BORDER); + GridLayout mainLayout = new GridLayout(); + // mainLayout.marginRight = 10; + body.setLayout(mainLayout); + // body.getParent().setLayout(new GridLayout()); + // body.setLayoutData(CmsUiUtils.fillAll()); + User user = getDisplayedUser(); + appendOverviewPart(body, user); + // Remove to ability to force the password for his own user. The user + // must then use the change pwd feature + appendMemberOfPart(body, user); + } + + /** Creates the general section */ + private void appendOverviewPart(final Composite parent, final User user) { + // FormToolkit tk = getManagedForm().getToolkit(); + + // Section section = tk.createSection(parent, SWT.NO_FOCUS); + // GridData gd = EclipseUiUtils.fillWidth(); + // // gd.verticalAlignment = PRE_TITLE_INDENT; + // section.setLayoutData(gd); + Composite body = new Composite(parent, SWT.NONE); + body.setLayoutData(EclipseUiUtils.fillWidth()); + // section.setClient(body); + // body.setLayout(new GridLayout(6, false)); + body.setLayout(new GridLayout(2, false)); + + Text commonName = createReadOnlyLT(body, "Name", getProperty(user, cn)); + Text distinguishedName = createReadOnlyLT(body, "Login", getProperty(user, uid)); + Text firstName = createLT(body, "First name", getProperty(user, givenName)); + Text lastName = createLT(body, "Last name", getProperty(user, sn)); + Text email = createLT(body, "Email", getProperty(user, mail)); + + Link resetPwdLk = new Link(body, SWT.NONE); + if (!UserAdminUtils.isCurrentUser(user)) { + resetPwdLk.setText("Reset password"); + } + resetPwdLk.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1)); + + // create form part (controller) + AbstractFormPart part = new AbstractFormPart() { + private MainInfoListener listener; + + @Override + public void initialize(IManagedForm form) { + super.initialize(form); + listener = new MainInfoListener(parent.getDisplay(), this); + userAdminWrapper.addListener(listener); + } + + @Override + public void dispose() { + userAdminWrapper.removeListener(listener); + super.dispose(); + } + + @SuppressWarnings("unchecked") + public void commit(boolean onSave) { + // TODO Sanity checks (mail validity...) + user.getProperties().put(LdapAttrs.givenName.name(), firstName.getText()); + user.getProperties().put(LdapAttrs.sn.name(), lastName.getText()); + user.getProperties().put(LdapAttrs.cn.name(), commonName.getText()); + user.getProperties().put(LdapAttrs.mail.name(), email.getText()); + super.commit(onSave); + } + + @Override + public void refresh() { + distinguishedName.setText(UserAdminUtils.getProperty(user, LdapAttrs.uid.name())); + commonName.setText(UserAdminUtils.getProperty(user, LdapAttrs.cn.name())); + firstName.setText(UserAdminUtils.getProperty(user, LdapAttrs.givenName.name())); + lastName.setText(UserAdminUtils.getProperty(user, LdapAttrs.sn.name())); + email.setText(UserAdminUtils.getProperty(user, LdapAttrs.mail.name())); + refreshFormTitle(user); + super.refresh(); + } + }; + + // Improve this: automatically generate CN when first or last name + // changes + ModifyListener cnML = new ModifyListener() { + private static final long serialVersionUID = 4298649222869835486L; + + @Override + public void modifyText(ModifyEvent event) { + String first = firstName.getText(); + String last = lastName.getText(); + String cn = first.trim() + " " + last.trim() + " "; + cn = cn.trim(); + commonName.setText(cn); + // getManagedForm().getForm().setText(cn); + updateEditorTitle(cn); + } + }; + firstName.addModifyListener(cnML); + lastName.addModifyListener(cnML); + + ModifyListener defaultListener = new FormPartML(part); + firstName.addModifyListener(defaultListener); + lastName.addModifyListener(defaultListener); + email.addModifyListener(defaultListener); + + if (!UserAdminUtils.isCurrentUser(user)) + resetPwdLk.addSelectionListener(new SelectionAdapter() { + private static final long serialVersionUID = 5881800534589073787L; + + @Override + public void widgetSelected(SelectionEvent e) { + new ChangePasswordDialog(user, "Reset password").open(); + } + }); + + getManagedForm().addPart(part); + } + + private class ChangePasswordDialog extends TrayDialog { + private static final long serialVersionUID = 2843538207460082349L; + + private User user; + private Text password1; + private Text password2; + private String title; + // private FormToolkit tk; + + public ChangePasswordDialog(User user, String title) { + super(Display.getDefault().getActiveShell()); + // this.tk = tk; + this.user = user; + this.title = title; + } + + protected Control createDialogArea(Composite parent) { + Composite dialogarea = (Composite) super.createDialogArea(parent); + dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + Composite body = new Composite(dialogarea, SWT.NO_FOCUS); + body.setLayoutData(EclipseUiUtils.fillAll()); + GridLayout layout = new GridLayout(2, false); + body.setLayout(layout); + + password1 = createLP(body, "New password", ""); + password2 = createLP(body, "Repeat password", ""); + parent.pack(); + return body; + } + + @SuppressWarnings("unchecked") + @Override + protected void okPressed() { + String msg = null; + + if (password1.getText().equals("")) + msg = "Password cannot be empty"; + else if (password1.getText().equals(password2.getText())) { + char[] newPassword = password1.getText().toCharArray(); + // userAdminWrapper.beginTransactionIfNeeded(); + userAdminWrapper.beginTransactionIfNeeded(); + user.getCredentials().put(null, newPassword); + userAdminWrapper.commitOrNotifyTransactionStateChange(); + super.okPressed(); + } else { + msg = "Passwords are not equals"; + } + + if (EclipseUiUtils.notEmpty(msg)) + MessageDialog.openError(getParentShell(), "Cannot reset pasword", msg); + } + + protected void configureShell(Shell shell) { + super.configureShell(shell); + shell.setText(title); + } + } + + private LdifUsersTable appendMemberOfPart(final Composite parent, User user) { + // Section section = addSection(tk, parent, "Roles"); + // Composite body = (Composite) section.getClient(); + // Composite body= parent; + Composite body = new Composite(parent, SWT.BORDER); + body.setLayout(new GridLayout()); + body.setLayoutData(CmsSwtUtils.fillAll()); + + // boolean isAdmin = CurrentUser.isInRole(NodeConstants.ROLE_ADMIN); + + // Displayed columns + List columnDefs = new ArrayList(); + columnDefs.add(new ColumnDefinition(new RoleIconLP(), "", 0, 24)); + columnDefs.add(new ColumnDefinition(new CommonNameLP(), "Name", 150)); + columnDefs.add(new ColumnDefinition(new DomainNameLP(), "Domain", 100)); + // Only show technical DN to administrators + // if (isAdmin) + // columnDefs.add(new ColumnDefinition(new UserNameLP(), "Distinguished Name", + // 300)); + + // Create and configure the table + final LdifUsersTable userViewerCmp = new MyUserTableViewer(body, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL, user); + + userViewerCmp.setColumnDefinitions(columnDefs); + // if (isAdmin) + // userViewerCmp.populateWithStaticFilters(false, false); + // else + userViewerCmp.populate(true, false); + GridData gd = EclipseUiUtils.fillAll(); + gd.heightHint = 500; + userViewerCmp.setLayoutData(gd); + + // Controllers + TableViewer userViewer = userViewerCmp.getTableViewer(); + userViewer.addDoubleClickListener(new UserTableDefaultDClickListener(partService)); + int operations = DND.DROP_COPY | DND.DROP_MOVE; + Transfer[] tt = new Transfer[] { TextTransfer.getInstance() }; + GroupDropListener dropL = new GroupDropListener(userAdminWrapper, userViewer, user); + userViewer.addDropSupport(operations, tt, dropL); + + AbstractFormPart part = new AbstractFormPart() { + + private GroupChangeListener listener; + + @Override + public void initialize(IManagedForm form) { + super.initialize(form); + listener = new GroupChangeListener(parent.getDisplay(), this); + userAdminWrapper.addListener(listener); + } + + public void commit(boolean onSave) { + super.commit(onSave); + } + + @Override + public void dispose() { + userAdminWrapper.removeListener(listener); + super.dispose(); + } + + @Override + public void refresh() { + userViewerCmp.refresh(); + super.refresh(); + } + }; + getManagedForm().addPart(part); + // addRemoveAbitily(body, userViewer, user); + // userViewerCmp.refresh(); + String tooltip = "Remove " + UserAdminUtils.getUserLocalId(user.getName()) + " from the below selected groups"; + Action action = new RemoveMembershipAction(userViewer, user, tooltip, SecurityAdminImages.ICON_REMOVE_DESC); + ToolBarManager toolBarManager = new ToolBarManager(SWT.FLAT); + ToolBar toolBar = toolBarManager.createControl(body); + toolBar.setLayoutData(CmsSwtUtils.fillWidth()); + toolBarManager.add(action); + toolBarManager.update(true); + return userViewerCmp; + } + + private class MyUserTableViewer extends LdifUsersTable { + private static final long serialVersionUID = 2653790051461237329L; + + private Button showSystemRoleBtn; + + private final User user; + private final UserFilter userFilter; + + public MyUserTableViewer(Composite parent, int style, User user) { + super(parent, style, true); + this.user = user; + userFilter = new UserFilter(); + } + + protected void populateStaticFilters(Composite staticFilterCmp) { + staticFilterCmp.setLayout(new GridLayout()); + showSystemRoleBtn = new Button(staticFilterCmp, SWT.CHECK); + showSystemRoleBtn.setText("Show system roles"); + boolean showSysRole = CurrentUser.isInRole(CmsConstants.ROLE_ADMIN); + showSystemRoleBtn.setSelection(showSysRole); + userFilter.setShowSystemRole(showSysRole); + showSystemRoleBtn.addSelectionListener(new SelectionAdapter() { + private static final long serialVersionUID = -7033424592697691676L; + + @Override + public void widgetSelected(SelectionEvent e) { + userFilter.setShowSystemRole(showSystemRoleBtn.getSelection()); + refresh(); + } + }); + } + + @Override + protected List listFilteredElements(String filter) { + List users = (List) getFlatGroups(null); + List filteredUsers = new ArrayList(); + if (users.contains(user)) + users.remove(user); + userFilter.setSearchText(filter); + for (User user : users) + if (userFilter.select(null, null, user)) + filteredUsers.add(user); + return filteredUsers; + } + } + + // private void addRemoveAbility(Composite parent, TableViewer userViewer, User + // user) { + // // Section section = sectionPart.getSection(); + // ToolBarManager toolBarManager = new ToolBarManager(SWT.FLAT); + // ToolBar toolbar = toolBarManager.createControl(parent); + // final Cursor handCursor = new Cursor(Display.getCurrent(), SWT.CURSOR_HAND); + // toolbar.setCursor(handCursor); + // toolbar.addDisposeListener(new DisposeListener() { + // private static final long serialVersionUID = 3882131405820522925L; + // + // public void widgetDisposed(DisposeEvent e) { + // if ((handCursor != null) && (handCursor.isDisposed() == false)) { + // handCursor.dispose(); + // } + // } + // }); + // + // String tooltip = "Remove " + UserAdminUtils.getUserLocalId(user.getName()) + + // " from the below selected groups"; + // Action action = new RemoveMembershipAction(userViewer, user, tooltip, + // SecurityAdminImages.ICON_REMOVE_DESC); + // toolBarManager.add(action); + // toolBarManager.update(true); + // // section.setTextClient(toolbar); + // } + + private class RemoveMembershipAction extends Action { + private static final long serialVersionUID = -1337713097184522588L; + + private final TableViewer userViewer; + private final User user; + + RemoveMembershipAction(TableViewer userViewer, User user, String name, ImageDescriptor img) { + super(name, img); + this.userViewer = userViewer; + this.user = user; + } + + @Override + public void run() { + ISelection selection = userViewer.getSelection(); + if (selection.isEmpty()) + return; + + @SuppressWarnings("unchecked") + Iterator it = ((IStructuredSelection) selection).iterator(); + List groups = new ArrayList(); + while (it.hasNext()) { + Group currGroup = it.next(); + groups.add(currGroup); + } + + userAdminWrapper.beginTransactionIfNeeded(); + for (Group group : groups) { + group.removeMember(user); + } + userAdminWrapper.commitOrNotifyTransactionStateChange(); + for (Group group : groups) { + userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, group)); + } + } + } + + /** + * Defines the table as being a potential target to add group memberships + * (roles) to this user + */ + private class GroupDropListener extends ViewerDropAdapter { + private static final long serialVersionUID = 2893468717831451621L; + + private final UserAdminWrapper myUserAdminWrapper; + private final User myUser; + + public GroupDropListener(UserAdminWrapper userAdminWrapper, Viewer userViewer, User user) { + super(userViewer); + this.myUserAdminWrapper = userAdminWrapper; + this.myUser = user; + } + + @Override + public boolean validateDrop(Object target, int operation, TransferData transferType) { + // Target is always OK in a list only view + // TODO check if not a string + boolean validDrop = true; + return validDrop; + } + + @Override + public void drop(DropTargetEvent event) { + String name = (String) event.data; + UserAdmin myUserAdmin = myUserAdminWrapper.getUserAdmin(); + Role role = myUserAdmin.getRole(name); + // TODO this check should be done before. + if (role.getType() == Role.GROUP) { + // TODO check if the user is already member of this group + + myUserAdminWrapper.beginTransactionIfNeeded(); + Group group = (Group) role; + group.addMember(myUser); + userAdminWrapper.commitOrNotifyTransactionStateChange(); + myUserAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, group)); + } + super.drop(event); + } + + @Override + public boolean performDrop(Object data) { + // userTableViewerCmp.refresh(); + return true; + } + } + + // LOCAL HELPERS + private void refreshFormTitle(User group) { + // getManagedForm().getForm().setText(UserAdminUtils.getProperty(group, + // LdapAttrs.cn.name())); + } + + /** Appends a section with a title */ + // private Section addSection(FormToolkit tk, Composite parent, String title) { + // Section section = tk.createSection(parent, Section.TITLE_BAR); + // GridData gd = EclipseUiUtils.fillWidth(); + // gd.verticalAlignment = PRE_TITLE_INDENT; + // section.setLayoutData(gd); + // section.setText(title); + // // section.getMenu().setVisible(true); + // + // Composite body = tk.createComposite(section, SWT.WRAP); + // body.setLayoutData(EclipseUiUtils.fillAll()); + // section.setClient(body); + // + // return section; + // } + +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserTableDefaultDClickListener.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserTableDefaultDClickListener.java new file mode 100644 index 000000000..c6d024ebc --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserTableDefaultDClickListener.java @@ -0,0 +1,39 @@ +package org.argeo.cms.e4.users; + +import org.argeo.cms.e4.CmsE4Utils; +import org.argeo.util.naming.LdapAttrs; +import org.eclipse.e4.ui.workbench.modeling.EPartService; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IDoubleClickListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.osgi.service.useradmin.Group; +import org.osgi.service.useradmin.User; + +/** + * Default double click listener for the various user tables, will open the + * clicked item in the editor + */ +public class UserTableDefaultDClickListener implements IDoubleClickListener { + private final EPartService partService; + + public UserTableDefaultDClickListener(EPartService partService) { + this.partService = partService; + } + + public void doubleClick(DoubleClickEvent evt) { + if (evt.getSelection().isEmpty()) + return; + Object obj = ((IStructuredSelection) evt.getSelection()).getFirstElement(); + User user = (User) obj; + + String editorId = getEditorId(user); + CmsE4Utils.openEditor(partService, editorId, LdapAttrs.uid.name(), user.getName()); + } + + protected String getEditorId(User user) { + if (user instanceof Group) + return "org.argeo.cms.e4.partdescriptor.groupEditor"; + else + return "org.argeo.cms.e4.partdescriptor.userEditor"; + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UsersView.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UsersView.java new file mode 100644 index 000000000..877a925c6 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UsersView.java @@ -0,0 +1,182 @@ +package org.argeo.cms.e4.users; + +import java.util.ArrayList; +import java.util.List; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.inject.Inject; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.cms.CmsException; +import org.argeo.cms.auth.CurrentUser; +import org.argeo.cms.e4.users.providers.CommonNameLP; +import org.argeo.cms.e4.users.providers.DomainNameLP; +import org.argeo.cms.e4.users.providers.MailLP; +import org.argeo.cms.e4.users.providers.UserDragListener; +import org.argeo.cms.e4.users.providers.UserNameLP; +import org.argeo.eclipse.ui.ColumnDefinition; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.argeo.eclipse.ui.parts.LdifUsersTable; +import org.argeo.util.naming.LdapAttrs; +import org.argeo.util.naming.LdapObjs; +import org.eclipse.e4.ui.di.Focus; +import org.eclipse.e4.ui.workbench.modeling.EPartService; +import org.eclipse.e4.ui.workbench.modeling.ESelectionService; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.dnd.DND; +import org.eclipse.swt.dnd.TextTransfer; +import org.eclipse.swt.dnd.Transfer; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.User; +import org.osgi.service.useradmin.UserAdminEvent; +import org.osgi.service.useradmin.UserAdminListener; + +/** List all users with filter - based on Ldif userAdmin */ +public class UsersView { + // private final static Log log = LogFactory.getLog(UsersView.class); + + // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + ".usersView"; + + @Inject + private UserAdminWrapper userAdminWrapper; + @Inject + private EPartService partService; + + // UI Objects + private LdifUsersTable userTableViewerCmp; + private TableViewer userViewer; + private List columnDefs = new ArrayList(); + + private UserAdminListener listener; + + @PostConstruct + public void createPartControl(Composite parent, ESelectionService selectionService) { + + parent.setLayout(EclipseUiUtils.noSpaceGridLayout()); + // Define the displayed columns + columnDefs.add(new ColumnDefinition(new CommonNameLP(), "Common Name", 150)); + columnDefs.add(new ColumnDefinition(new MailLP(), "E-mail", 150)); + columnDefs.add(new ColumnDefinition(new DomainNameLP(), "Domain", 200)); + // Only show technical DN to admin + if (CurrentUser.isInRole(CmsConstants.ROLE_ADMIN)) + columnDefs.add(new ColumnDefinition(new UserNameLP(), "Distinguished Name", 300)); + + // Create and configure the table + userTableViewerCmp = new MyUserTableViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); + userTableViewerCmp.setLayoutData(EclipseUiUtils.fillAll()); + userTableViewerCmp.setColumnDefinitions(columnDefs); + userTableViewerCmp.populate(true, false); + + // Links + userViewer = userTableViewerCmp.getTableViewer(); + userViewer.addDoubleClickListener(new UserTableDefaultDClickListener(partService)); + userViewer.addSelectionChangedListener(new ISelectionChangedListener() { + + @Override + public void selectionChanged(SelectionChangedEvent event) { + IStructuredSelection selection = (IStructuredSelection) event.getSelection(); + selectionService.setSelection(selection.toList()); + } + }); + // getViewSite().setSelectionProvider(userViewer); + + // Really? + userTableViewerCmp.refresh(); + + // Drag and drop + int operations = DND.DROP_COPY | DND.DROP_MOVE; + Transfer[] tt = new Transfer[] { TextTransfer.getInstance() }; + userViewer.addDragSupport(operations, tt, new UserDragListener(userViewer)); + + // Register a useradmin listener + listener = new MyUiUAListener(parent.getDisplay()); + userAdminWrapper.addListener(listener); + } + + private class MyUiUAListener extends UiUserAdminListener { + public MyUiUAListener(Display display) { + super(display); + } + + @Override + public void roleChangedToUiThread(UserAdminEvent event) { + if (userViewer != null && !userViewer.getTable().isDisposed()) + refresh(); + } + } + + private class MyUserTableViewer extends LdifUsersTable { + private static final long serialVersionUID = 8467999509931900367L; + + private final String[] knownProps = { LdapAttrs.DN, LdapAttrs.uid.name(), LdapAttrs.cn.name(), + LdapAttrs.givenName.name(), LdapAttrs.sn.name(), LdapAttrs.mail.name() }; + + public MyUserTableViewer(Composite parent, int style) { + super(parent, style); + } + + @Override + protected List listFilteredElements(String filter) { + Role[] roles; + + try { + StringBuilder builder = new StringBuilder(); + + StringBuilder tmpBuilder = new StringBuilder(); + if (EclipseUiUtils.notEmpty(filter)) + for (String prop : knownProps) { + tmpBuilder.append("("); + tmpBuilder.append(prop); + tmpBuilder.append("=*"); + tmpBuilder.append(filter); + tmpBuilder.append("*)"); + } + if (tmpBuilder.length() > 1) { + builder.append("(&(").append(LdapAttrs.objectClass.name()).append("=") + .append(LdapObjs.inetOrgPerson.name()).append(")(|"); + builder.append(tmpBuilder.toString()); + builder.append("))"); + } else + builder.append("(").append(LdapAttrs.objectClass.name()).append("=") + .append(LdapObjs.inetOrgPerson.name()).append(")"); + roles = userAdminWrapper.getUserAdmin().getRoles(builder.toString()); + } catch (InvalidSyntaxException e) { + throw new CmsException("Unable to get roles with filter: " + filter, e); + } + List users = new ArrayList(); + for (Role role : roles) + // if (role.getType() == Role.USER && role.getType() != + // Role.GROUP) + users.add((User) role); + return users; + } + } + + public void refresh() { + userTableViewerCmp.refresh(); + } + + // Override generic view methods + @PreDestroy + public void dispose() { + userAdminWrapper.removeListener(listener); + } + + @Focus + public void setFocus() { + userTableViewerCmp.setFocus(); + } + + /* DEPENDENCY INJECTION */ + public void setUserAdminWrapper(UserAdminWrapper userAdminWrapper) { + this.userAdminWrapper = userAdminWrapper; + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteGroups.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteGroups.java new file mode 100644 index 000000000..742bc3f5f --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteGroups.java @@ -0,0 +1,95 @@ +package org.argeo.cms.e4.users.handlers; + +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Named; + +import org.argeo.cms.auth.UserAdminUtils; +import org.argeo.cms.e4.users.GroupsView; +import org.argeo.cms.e4.users.UserAdminWrapper; +import org.eclipse.e4.core.di.annotations.CanExecute; +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.e4.ui.model.application.ui.basic.MPart; +import org.eclipse.e4.ui.services.IServiceConstants; +import org.eclipse.e4.ui.workbench.modeling.ESelectionService; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.swt.widgets.Display; +import org.osgi.service.useradmin.Group; +import org.osgi.service.useradmin.UserAdmin; +import org.osgi.service.useradmin.UserAdminEvent; + +/** Delete the selected groups */ +public class DeleteGroups { + // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + + // ".deleteGroups"; + + /* DEPENDENCY INJECTION */ + @Inject + private UserAdminWrapper userAdminWrapper; + + @Inject + ESelectionService selectionService; + + @SuppressWarnings("unchecked") + @Execute + public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part, ESelectionService selectionService) { + // ISelection selection = null;// HandlerUtil.getCurrentSelection(event); + // if (selection.isEmpty()) + // return null; + // + // List groups = new ArrayList(); + // Iterator it = ((IStructuredSelection) selection).iterator(); + + List selection = (List) selectionService.getSelection(); + if (selection == null) + return; + + StringBuilder builder = new StringBuilder(); + for (Group group : selection) { + Group currGroup = group; + String groupName = UserAdminUtils.getUserLocalId(currGroup.getName()); + // TODO add checks + builder.append(groupName).append("; "); + // groups.add(currGroup); + } + + if (!MessageDialog.openQuestion(Display.getCurrent().getActiveShell(), "Delete Groups", "Are you sure that you " + + "want to delete these groups?\n" + builder.substring(0, builder.length() - 2))) + return; + + userAdminWrapper.beginTransactionIfNeeded(); + UserAdmin userAdmin = userAdminWrapper.getUserAdmin(); + // IWorkbenchPage iwp = + // HandlerUtil.getActiveWorkbenchWindow(event).getActivePage(); + for (Group group : selection) { + String groupName = group.getName(); + // TODO find a way to close the editor cleanly if opened. Cannot be + // done through the UserAdminListeners, it causes a + // java.util.ConcurrentModificationException because disposing the + // editor unregisters and disposes the listener + // IEditorPart part = iwp.findEditor(new UserEditorInput(groupName)); + // if (part != null) + // iwp.closeEditor(part, false); + userAdmin.removeRole(groupName); + } + userAdminWrapper.commitOrNotifyTransactionStateChange(); + + // Update the view + for (Group group : selection) { + userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_REMOVED, group)); + } + + // return null; + } + + @CanExecute + public boolean canExecute(@Named(IServiceConstants.ACTIVE_PART) MPart part, ESelectionService selectionService) { + return part.getObject() instanceof GroupsView && selectionService.getSelection() != null; + } + + /* DEPENDENCY INJECTION */ + // public void setUserAdminWrapper(UserAdminWrapper userAdminWrapper) { + // this.userAdminWrapper = userAdminWrapper; + // } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteUsers.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteUsers.java new file mode 100644 index 000000000..d1afd2210 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteUsers.java @@ -0,0 +1,88 @@ +package org.argeo.cms.e4.users.handlers; + +import java.util.List; + +import javax.inject.Inject; +import javax.inject.Named; + +import org.argeo.cms.auth.UserAdminUtils; +import org.argeo.cms.e4.users.UserAdminWrapper; +import org.argeo.cms.e4.users.UsersView; +import org.eclipse.e4.core.di.annotations.CanExecute; +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.e4.ui.model.application.ui.basic.MPart; +import org.eclipse.e4.ui.services.IServiceConstants; +import org.eclipse.e4.ui.workbench.modeling.ESelectionService; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.swt.widgets.Display; +import org.osgi.service.useradmin.User; +import org.osgi.service.useradmin.UserAdmin; +import org.osgi.service.useradmin.UserAdminEvent; + +/** Delete the selected users */ +public class DeleteUsers { + // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + ".deleteUsers"; + + /* DEPENDENCY INJECTION */ + @Inject + private UserAdminWrapper userAdminWrapper; + + @SuppressWarnings("unchecked") + @Execute + public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part, ESelectionService selectionService) { + // ISelection selection = null;// HandlerUtil.getCurrentSelection(event); + // if (selection.isEmpty()) + // return null; + List selection = (List) selectionService.getSelection(); + if (selection == null) + return; + +// Iterator it = ((IStructuredSelection) selection).iterator(); +// List users = new ArrayList(); + StringBuilder builder = new StringBuilder(); + + for(User user:selection) { + User currUser = user; +// User currUser = it.next(); + String userName = UserAdminUtils.getUserLocalId(currUser.getName()); + if (UserAdminUtils.isCurrentUser(currUser)) { + MessageDialog.openError(Display.getCurrent().getActiveShell(), "Deletion forbidden", + "You cannot delete your own user this way."); + return; + } + builder.append(userName).append("; "); +// users.add(currUser); + } + + if (!MessageDialog.openQuestion(Display.getCurrent().getActiveShell(), "Delete Users", + "Are you sure that you want to delete these users?\n" + builder.substring(0, builder.length() - 2))) + return; + + userAdminWrapper.beginTransactionIfNeeded(); + UserAdmin userAdmin = userAdminWrapper.getUserAdmin(); + // IWorkbenchPage iwp = + // HandlerUtil.getActiveWorkbenchWindow(event).getActivePage(); + + for (User user : selection) { + String userName = user.getName(); + // TODO find a way to close the editor cleanly if opened. Cannot be + // done through the UserAdminListeners, it causes a + // java.util.ConcurrentModificationException because disposing the + // editor unregisters and disposes the listener + // IEditorPart part = iwp.findEditor(new UserEditorInput(userName)); + // if (part != null) + // iwp.closeEditor(part, false); + userAdmin.removeRole(userName); + } + userAdminWrapper.commitOrNotifyTransactionStateChange(); + + for (User user : selection) { + userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_REMOVED, user)); + } + } + + @CanExecute + public boolean canExecute(@Named(IServiceConstants.ACTIVE_PART) MPart part, ESelectionService selectionService) { + return part.getObject() instanceof UsersView && selectionService.getSelection() != null; + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewGroup.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewGroup.java new file mode 100644 index 000000000..d2ffa791b --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewGroup.java @@ -0,0 +1,212 @@ +package org.argeo.cms.e4.users.handlers; + +import java.util.Dictionary; +import java.util.Map; + +import javax.inject.Inject; + +import org.argeo.cms.CmsException; +import org.argeo.cms.e4.users.UserAdminWrapper; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.argeo.eclipse.ui.dialogs.ErrorFeedback; +import org.argeo.osgi.useradmin.UserAdminConf; +import org.argeo.util.naming.LdapAttrs; +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.jface.wizard.WizardDialog; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; +import org.osgi.service.useradmin.Group; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.UserAdminEvent; + +/** Create a new group */ +public class NewGroup { + // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + ".newGroup"; + + /* DEPENDENCY INJECTION */ + @Inject + private UserAdminWrapper userAdminWrapper; + + @Execute + public Object execute() { + NewGroupWizard newGroupWizard = new NewGroupWizard(); + newGroupWizard.setWindowTitle("Group creation"); + WizardDialog dialog = new WizardDialog(Display.getCurrent().getActiveShell(), newGroupWizard); + dialog.open(); + return null; + } + + private class NewGroupWizard extends Wizard { + + // Pages + private MainGroupInfoWizardPage mainGroupInfo; + + // UI fields + private Text dNameTxt, commonNameTxt, descriptionTxt; + private Combo baseDnCmb; + + public NewGroupWizard() { + } + + @Override + public void addPages() { + mainGroupInfo = new MainGroupInfoWizardPage(); + addPage(mainGroupInfo); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public boolean performFinish() { + if (!canFinish()) + return false; + String commonName = commonNameTxt.getText(); + try { + userAdminWrapper.beginTransactionIfNeeded(); + String dn = getDn(commonName); + Group group = (Group) userAdminWrapper.getUserAdmin().createRole(dn, Role.GROUP); + Dictionary props = group.getProperties(); + String descStr = descriptionTxt.getText(); + if (EclipseUiUtils.notEmpty(descStr)) + props.put(LdapAttrs.description.name(), descStr); + userAdminWrapper.commitOrNotifyTransactionStateChange(); + userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CREATED, group)); + return true; + } catch (Exception e) { + ErrorFeedback.show("Cannot create new group " + commonName, e); + return false; + } + } + + private class MainGroupInfoWizardPage extends WizardPage implements FocusListener { + private static final long serialVersionUID = -3150193365151601807L; + + public MainGroupInfoWizardPage() { + super("Main"); + setTitle("General information"); + setMessage("Please choose a domain, provide a common name " + "and a free description"); + } + + @Override + public void createControl(Composite parent) { + Composite bodyCmp = new Composite(parent, SWT.NONE); + setControl(bodyCmp); + bodyCmp.setLayout(new GridLayout(2, false)); + + dNameTxt = EclipseUiUtils.createGridLT(bodyCmp, "Distinguished name"); + dNameTxt.setEnabled(false); + + baseDnCmb = createGridLC(bodyCmp, "Base DN"); + // Initialise before adding the listener to avoid NPE + initialiseDnCmb(baseDnCmb); + baseDnCmb.addFocusListener(this); + + commonNameTxt = EclipseUiUtils.createGridLT(bodyCmp, "Common name"); + commonNameTxt.addFocusListener(this); + + Label descLbl = new Label(bodyCmp, SWT.LEAD); + descLbl.setText("Description"); + descLbl.setLayoutData(new GridData(SWT.RIGHT, SWT.TOP, false, false)); + descriptionTxt = new Text(bodyCmp, SWT.LEAD | SWT.MULTI | SWT.WRAP | SWT.BORDER); + descriptionTxt.setLayoutData(EclipseUiUtils.fillAll()); + descriptionTxt.addFocusListener(this); + + // Initialize buttons + setPageComplete(false); + getContainer().updateButtons(); + } + + @Override + public void focusLost(FocusEvent event) { + String name = commonNameTxt.getText(); + if (EclipseUiUtils.isEmpty(name)) + dNameTxt.setText(""); + else + dNameTxt.setText(getDn(name)); + + String message = checkComplete(); + if (message != null) { + setMessage(message, WizardPage.ERROR); + setPageComplete(false); + } else { + setMessage("Complete", WizardPage.INFORMATION); + setPageComplete(true); + } + getContainer().updateButtons(); + } + + @Override + public void focusGained(FocusEvent event) { + } + + /** @return the error message or null if complete */ + protected String checkComplete() { + String name = commonNameTxt.getText(); + + if (name.trim().equals("")) + return "Common name must not be empty"; + Role role = userAdminWrapper.getUserAdmin().getRole(getDn(name)); + if (role != null) + return "Group " + name + " already exists"; + return null; + } + + @Override + public void setVisible(boolean visible) { + super.setVisible(visible); + if (visible) + if (baseDnCmb.getSelectionIndex() == -1) + baseDnCmb.setFocus(); + else + commonNameTxt.setFocus(); + } + } + + private Map getDns() { + return userAdminWrapper.getKnownBaseDns(true); + } + + private String getDn(String cn) { + Map dns = getDns(); + String bdn = baseDnCmb.getText(); + if (EclipseUiUtils.notEmpty(bdn)) { + Dictionary props = UserAdminConf.uriAsProperties(dns.get(bdn)); + String dn = LdapAttrs.cn.name() + "=" + cn + "," + UserAdminConf.groupBase.getValue(props) + "," + bdn; + return dn; + } + return null; + } + + private void initialiseDnCmb(Combo combo) { + Map dns = userAdminWrapper.getKnownBaseDns(true); + if (dns.isEmpty()) + throw new CmsException("No writable base dn found. Cannot create group"); + combo.setItems(dns.keySet().toArray(new String[0])); + if (dns.size() == 1) + combo.select(0); + } + } + + private Combo createGridLC(Composite parent, String label) { + Label lbl = new Label(parent, SWT.LEAD); + lbl.setText(label); + lbl.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false)); + Combo combo = new Combo(parent, SWT.LEAD | SWT.BORDER | SWT.READ_ONLY); + combo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); + return combo; + } + + /* DEPENDENCY INJECTION */ + public void setUserAdminWrapper(UserAdminWrapper userAdminWrapper) { + this.userAdminWrapper = userAdminWrapper; + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewUser.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewUser.java new file mode 100644 index 000000000..07d82c749 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewUser.java @@ -0,0 +1,287 @@ +package org.argeo.cms.e4.users.handlers; + +import java.util.Dictionary; +import java.util.List; +import java.util.Map; + +import javax.inject.Inject; +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; +import javax.naming.ldap.Rdn; + +import org.argeo.cms.CmsException; +import org.argeo.cms.auth.UserAdminUtils; +import org.argeo.cms.e4.users.UiAdminUtils; +import org.argeo.cms.e4.users.UserAdminWrapper; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.argeo.eclipse.ui.dialogs.ErrorFeedback; +import org.argeo.osgi.useradmin.UserAdminConf; +import org.argeo.util.naming.LdapAttrs; +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.jface.wizard.Wizard; +import org.eclipse.jface.wizard.WizardDialog; +import org.eclipse.jface.wizard.WizardPage; +import org.eclipse.swt.SWT; +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.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.User; +import org.osgi.service.useradmin.UserAdminEvent; + +/** Open a wizard that enables creation of a new user. */ +public class NewUser { + // private final static Log log = LogFactory.getLog(NewUser.class); + // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + ".newUser"; + + /* DEPENDENCY INJECTION */ + @Inject + private UserAdminWrapper userAdminWrapper; + + @Execute + public Object execute() { + NewUserWizard newUserWizard = new NewUserWizard(); + newUserWizard.setWindowTitle("User creation"); + WizardDialog dialog = new WizardDialog(Display.getCurrent().getActiveShell(), newUserWizard); + dialog.open(); + return null; + } + + private class NewUserWizard extends Wizard { + + // pages + private MainUserInfoWizardPage mainUserInfo; + + // End user fields + private Text dNameTxt, usernameTxt, firstNameTxt, lastNameTxt, primaryMailTxt, pwd1Txt, pwd2Txt; + private Combo baseDnCmb; + + public NewUserWizard() { + + } + + @Override + public void addPages() { + mainUserInfo = new MainUserInfoWizardPage(); + addPage(mainUserInfo); + String message = "Default wizard that also eases user creation tests:\n " + + "Mail and last name are automatically " + + "generated form the uid. Password are defauted to 'demo'."; + mainUserInfo.setMessage(message, WizardPage.WARNING); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public boolean performFinish() { + if (!canFinish()) + return false; + String username = mainUserInfo.getUsername(); + userAdminWrapper.beginTransactionIfNeeded(); + try { + User user = (User) userAdminWrapper.getUserAdmin().createRole(getDn(username), Role.USER); + + Dictionary props = user.getProperties(); + + String lastNameStr = lastNameTxt.getText(); + if (EclipseUiUtils.notEmpty(lastNameStr)) + props.put(LdapAttrs.sn.name(), lastNameStr); + + String firstNameStr = firstNameTxt.getText(); + if (EclipseUiUtils.notEmpty(firstNameStr)) + props.put(LdapAttrs.givenName.name(), firstNameStr); + + String cn = UserAdminUtils.buildDefaultCn(firstNameStr, lastNameStr); + if (EclipseUiUtils.notEmpty(cn)) + props.put(LdapAttrs.cn.name(), cn); + + String mailStr = primaryMailTxt.getText(); + if (EclipseUiUtils.notEmpty(mailStr)) + props.put(LdapAttrs.mail.name(), mailStr); + + char[] password = mainUserInfo.getPassword(); + user.getCredentials().put(null, password); + userAdminWrapper.commitOrNotifyTransactionStateChange(); + userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CREATED, user)); + return true; + } catch (Exception e) { + ErrorFeedback.show("Cannot create new user " + username, e); + return false; + } + } + + private class MainUserInfoWizardPage extends WizardPage implements ModifyListener { + private static final long serialVersionUID = -3150193365151601807L; + + public MainUserInfoWizardPage() { + super("Main"); + setTitle("Required Information"); + } + + @Override + public void createControl(Composite parent) { + Composite composite = new Composite(parent, SWT.NONE); + composite.setLayout(new GridLayout(2, false)); + dNameTxt = EclipseUiUtils.createGridLT(composite, "Distinguished name", this); + dNameTxt.setEnabled(false); + + baseDnCmb = createGridLC(composite, "Base DN"); + initialiseDnCmb(baseDnCmb); + baseDnCmb.addModifyListener(this); + baseDnCmb.addModifyListener(new ModifyListener() { + private static final long serialVersionUID = -1435351236582736843L; + + @Override + public void modifyText(ModifyEvent event) { + String name = usernameTxt.getText(); + dNameTxt.setText(getDn(name)); + } + }); + + usernameTxt = EclipseUiUtils.createGridLT(composite, "Local ID", this); + usernameTxt.addModifyListener(new ModifyListener() { + private static final long serialVersionUID = -1435351236582736843L; + + @Override + public void modifyText(ModifyEvent event) { + String name = usernameTxt.getText(); + if (name.trim().equals("")) { + dNameTxt.setText(""); + lastNameTxt.setText(""); + primaryMailTxt.setText(""); + pwd1Txt.setText(""); + pwd2Txt.setText(""); + } else { + dNameTxt.setText(getDn(name)); + lastNameTxt.setText(name.toUpperCase()); + primaryMailTxt.setText(getMail(name)); + pwd1Txt.setText("demo"); + pwd2Txt.setText("demo"); + } + } + }); + + primaryMailTxt = EclipseUiUtils.createGridLT(composite, "Email", this); + firstNameTxt = EclipseUiUtils.createGridLT(composite, "First name", this); + lastNameTxt = EclipseUiUtils.createGridLT(composite, "Last name", this); + pwd1Txt = EclipseUiUtils.createGridLP(composite, "Password", this); + pwd2Txt = EclipseUiUtils.createGridLP(composite, "Repeat password", this); + setControl(composite); + + // Initialize buttons + setPageComplete(false); + getContainer().updateButtons(); + } + + @Override + public void modifyText(ModifyEvent event) { + String message = checkComplete(); + if (message != null) { + setMessage(message, WizardPage.ERROR); + setPageComplete(false); + } else { + setMessage("Complete", WizardPage.INFORMATION); + setPageComplete(true); + } + getContainer().updateButtons(); + } + + /** @return error message or null if complete */ + protected String checkComplete() { + String name = usernameTxt.getText(); + + if (name.trim().equals("")) + return "User name must not be empty"; + Role role = userAdminWrapper.getUserAdmin().getRole(getDn(name)); + if (role != null) + return "User " + name + " already exists"; + if (!primaryMailTxt.getText().matches(UiAdminUtils.EMAIL_PATTERN)) + return "Not a valid email address"; + if (lastNameTxt.getText().trim().equals("")) + return "Specify a last name"; + if (pwd1Txt.getText().trim().equals("")) + return "Specify a password"; + if (pwd2Txt.getText().trim().equals("")) + return "Repeat the password"; + if (!pwd2Txt.getText().equals(pwd1Txt.getText())) + return "Passwords are different"; + return null; + } + + @Override + public void setVisible(boolean visible) { + super.setVisible(visible); + if (visible) + if (baseDnCmb.getSelectionIndex() == -1) + baseDnCmb.setFocus(); + else + usernameTxt.setFocus(); + } + + public String getUsername() { + return usernameTxt.getText(); + } + + public char[] getPassword() { + return pwd1Txt.getTextChars(); + } + + } + + private Map getDns() { + return userAdminWrapper.getKnownBaseDns(true); + } + + private String getDn(String uid) { + Map dns = getDns(); + String bdn = baseDnCmb.getText(); + if (EclipseUiUtils.notEmpty(bdn)) { + Dictionary props = UserAdminConf.uriAsProperties(dns.get(bdn)); + String dn = LdapAttrs.uid.name() + "=" + uid + "," + UserAdminConf.userBase.getValue(props) + "," + bdn; + return dn; + } + return null; + } + + private void initialiseDnCmb(Combo combo) { + Map dns = userAdminWrapper.getKnownBaseDns(true); + if (dns.isEmpty()) + throw new CmsException("No writable base dn found. Cannot create user"); + combo.setItems(dns.keySet().toArray(new String[0])); + if (dns.size() == 1) + combo.select(0); + } + + private String getMail(String username) { + if (baseDnCmb.getSelectionIndex() == -1) + return null; + String baseDn = baseDnCmb.getText(); + try { + LdapName name = new LdapName(baseDn); + List rdns = name.getRdns(); + return username + "@" + (String) rdns.get(1).getValue() + '.' + (String) rdns.get(0).getValue(); + } catch (InvalidNameException e) { + throw new CmsException("Unable to generate mail for " + username + " with base dn " + baseDn, e); + } + } + } + + private Combo createGridLC(Composite parent, String label) { + Label lbl = new Label(parent, SWT.LEAD); + lbl.setText(label); + lbl.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false)); + Combo combo = new Combo(parent, SWT.LEAD | SWT.BORDER | SWT.READ_ONLY); + combo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); + return combo; + } + + /* DEPENDENCY INJECTION */ + public void setUserAdminWrapper(UserAdminWrapper userAdminWrapper) { + this.userAdminWrapper = userAdminWrapper; + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/package-info.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/package-info.java new file mode 100644 index 000000000..cf3db1d16 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/package-info.java @@ -0,0 +1,2 @@ +/** Users management handlers. */ +package org.argeo.cms.e4.users.handlers; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/package-info.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/package-info.java new file mode 100644 index 000000000..c6f14b0cf --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/package-info.java @@ -0,0 +1,2 @@ +/** Users management perspective. */ +package org.argeo.cms.e4.users; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/CommonNameLP.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/CommonNameLP.java new file mode 100644 index 000000000..2d8db67d7 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/CommonNameLP.java @@ -0,0 +1,21 @@ +package org.argeo.cms.e4.users.providers; + +import org.argeo.cms.auth.UserAdminUtils; +import org.argeo.util.naming.LdapAttrs; +import org.osgi.service.useradmin.User; + +/** Simply declare a label provider that returns the common name of a user */ +public class CommonNameLP extends UserAdminAbstractLP { + private static final long serialVersionUID = 5256703081044911941L; + + @Override + public String getText(User user) { + return UserAdminUtils.getProperty(user, LdapAttrs.cn.name()); + } + + @Override + public String getToolTipText(Object element) { + return UserAdminUtils.getProperty((User) element, LdapAttrs.DN); + } + +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/DomainNameLP.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/DomainNameLP.java new file mode 100644 index 000000000..e23729da8 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/DomainNameLP.java @@ -0,0 +1,14 @@ +package org.argeo.cms.e4.users.providers; + +import org.argeo.cms.auth.UserAdminUtils; +import org.osgi.service.useradmin.User; + +/** The human friendly domain name for the corresponding user. */ +public class DomainNameLP extends UserAdminAbstractLP { + private static final long serialVersionUID = 5256703081044911941L; + + @Override + public String getText(User user) { + return UserAdminUtils.getDomainName(user); + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/MailLP.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/MailLP.java new file mode 100644 index 000000000..52d3b858f --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/MailLP.java @@ -0,0 +1,15 @@ +package org.argeo.cms.e4.users.providers; + +import org.argeo.cms.auth.UserAdminUtils; +import org.argeo.util.naming.LdapAttrs; +import org.osgi.service.useradmin.User; + +/** Simply declare a label provider that returns the Primary Mail of a user */ +public class MailLP extends UserAdminAbstractLP { + private static final long serialVersionUID = 8329764452141982707L; + + @Override + public String getText(User user) { + return UserAdminUtils.getProperty(user, LdapAttrs.mail.name()); + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/RoleIconLP.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/RoleIconLP.java new file mode 100644 index 000000000..8c94093e4 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/RoleIconLP.java @@ -0,0 +1,35 @@ +package org.argeo.cms.e4.users.providers; + +import org.argeo.api.cms.CmsContext; +import org.argeo.api.cms.CmsConstants; +import org.argeo.cms.auth.UserAdminUtils; +import org.argeo.cms.e4.users.SecurityAdminImages; +import org.argeo.util.naming.LdapAttrs; +import org.eclipse.swt.graphics.Image; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.User; + +/** Provide a bundle specific image depending on the current user type */ +public class RoleIconLP extends UserAdminAbstractLP { + private static final long serialVersionUID = 6550449442061090388L; + + @Override + public String getText(User user) { + return ""; + } + + @Override + public Image getImage(Object element) { + User user = (User) element; + String dn = user.getName(); + if (dn.endsWith(CmsConstants.ROLES_BASEDN)) + return SecurityAdminImages.ICON_ROLE; + else if (user.getType() == Role.GROUP) { + String businessCategory = UserAdminUtils.getProperty(user, LdapAttrs.businessCategory); + if (businessCategory != null && businessCategory.equals(CmsContext.WORKGROUP)) + return SecurityAdminImages.ICON_WORKGROUP; + return SecurityAdminImages.ICON_GROUP; + } else + return SecurityAdminImages.ICON_USER; + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserAdminAbstractLP.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserAdminAbstractLP.java new file mode 100644 index 000000000..e33b1531d --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserAdminAbstractLP.java @@ -0,0 +1,66 @@ +package org.argeo.cms.e4.users.providers; + +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; + +import org.argeo.cms.CmsException; +import org.argeo.cms.auth.UserAdminUtils; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.widgets.Display; +import org.osgi.service.useradmin.User; + +/** + * Utility class that add font modifications to a column label provider + * depending on the given user properties + */ +public abstract class UserAdminAbstractLP extends ColumnLabelProvider { + private static final long serialVersionUID = 137336765024922368L; + + // private Font italic; + private Font bold; + + @Override + public Font getFont(Object element) { + // Self as bold + try { + LdapName selfUserName = UserAdminUtils.getCurrentUserLdapName(); + String userName = ((User) element).getName(); + LdapName userLdapName = new LdapName(userName); + if (userLdapName.equals(selfUserName)) { + if (bold == null) + bold = JFaceResources.getFontRegistry() + .defaultFontDescriptor().setStyle(SWT.BOLD) + .createFont(Display.getCurrent()); + return bold; + } + } catch (InvalidNameException e) { + throw new CmsException("cannot parse dn for " + element, e); + } + + // Disabled as Italic + // Node userProfile = (Node) elem; + // if (!userProfile.getProperty(ARGEO_ENABLED).getBoolean()) + // return italic; + + return null; + // return super.getFont(element); + } + + @Override + public String getText(Object element) { + User user = (User) element; + return getText(user); + } + + public void setDisplay(Display display) { + // italic = JFaceResources.getFontRegistry().defaultFontDescriptor() + // .setStyle(SWT.ITALIC).createFont(display); + bold = JFaceResources.getFontRegistry().defaultFontDescriptor() + .setStyle(SWT.BOLD).createFont(Display.getCurrent()); + } + + public abstract String getText(User user); +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserDragListener.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserDragListener.java new file mode 100644 index 000000000..56a26244b --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserDragListener.java @@ -0,0 +1,40 @@ +package org.argeo.cms.e4.users.providers; + +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.dnd.DragSourceEvent; +import org.eclipse.swt.dnd.DragSourceListener; +import org.osgi.service.useradmin.User; + +/** Default drag listener to modify group and users via the UI */ +public class UserDragListener implements DragSourceListener { + private static final long serialVersionUID = -2074337775033781454L; + private final Viewer viewer; + + public UserDragListener(Viewer viewer) { + this.viewer = viewer; + } + + public void dragStart(DragSourceEvent event) { + // TODO implement finer checks + IStructuredSelection selection = (IStructuredSelection) viewer + .getSelection(); + if (selection.isEmpty() || selection.size() > 1) + event.doit = false; + else + event.doit = true; + } + + public void dragSetData(DragSourceEvent event) { + // TODO Support multiple selection + Object obj = ((IStructuredSelection) viewer.getSelection()) + .getFirstElement(); + if (obj != null) { + User user = (User) obj; + event.data = user.getName(); + } + } + + public void dragFinished(DragSourceEvent event) { + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserFilter.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserFilter.java new file mode 100644 index 000000000..154b04725 --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserFilter.java @@ -0,0 +1,58 @@ +package org.argeo.cms.e4.users.providers; + +import static org.argeo.eclipse.ui.EclipseUiUtils.notEmpty; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.cms.auth.UserAdminUtils; +import org.argeo.util.naming.LdapAttrs; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerFilter; +import org.osgi.service.useradmin.User; + +/** + * Filter user list using JFace mechanism on the client (yet on the server) side + * rather than having the UserAdmin to process the search + */ +public class UserFilter extends ViewerFilter { + private static final long serialVersionUID = 5082509381672880568L; + + private String searchString; + private boolean showSystemRole = true; + + private final String[] knownProps = { LdapAttrs.DN, LdapAttrs.cn.name(), LdapAttrs.givenName.name(), + LdapAttrs.sn.name(), LdapAttrs.uid.name(), LdapAttrs.description.name(), LdapAttrs.mail.name() }; + + public void setSearchText(String s) { + // ensure that the value can be used for matching + if (notEmpty(s)) + searchString = ".*" + s.toLowerCase() + ".*"; + else + searchString = ".*"; + } + + public void setShowSystemRole(boolean showSystemRole) { + this.showSystemRole = showSystemRole; + } + + @Override + public boolean select(Viewer viewer, Object parentElement, Object element) { + User user = (User) element; + if (!showSystemRole && user.getName().matches(".*(" + CmsConstants.ROLES_BASEDN + ")")) + // UserAdminUtils.getProperty(user, LdifName.dn.name()) + // .toLowerCase().endsWith(AuthConstants.ROLES_BASEDN)) + return false; + + if (searchString == null || searchString.length() == 0) + return true; + + if (user.getName().matches(searchString)) + return true; + + for (String key : knownProps) { + String currVal = UserAdminUtils.getProperty(user, key); + if (notEmpty(currVal) && currVal.toLowerCase().matches(searchString)) + return true; + } + return false; + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserNameLP.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserNameLP.java new file mode 100644 index 000000000..3cd00eb2b --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserNameLP.java @@ -0,0 +1,13 @@ +package org.argeo.cms.e4.users.providers; + +import org.osgi.service.useradmin.User; + +/** Simply declare a label provider that returns the username of a user */ +public class UserNameLP extends UserAdminAbstractLP { + private static final long serialVersionUID = 6550449442061090388L; + + @Override + public String getText(User user) { + return user.getName(); + } +} diff --git a/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/package-info.java b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/package-info.java new file mode 100644 index 000000000..33bef8dee --- /dev/null +++ b/eclipse/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/package-info.java @@ -0,0 +1,2 @@ +/** Users management content providers. */ +package org.argeo.cms.e4.users.providers; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.servlet/.classpath b/eclipse/org.argeo.cms.servlet/.classpath new file mode 100644 index 000000000..e801ebfb4 --- /dev/null +++ b/eclipse/org.argeo.cms.servlet/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/eclipse/org.argeo.cms.servlet/.project b/eclipse/org.argeo.cms.servlet/.project new file mode 100644 index 000000000..d39f97472 --- /dev/null +++ b/eclipse/org.argeo.cms.servlet/.project @@ -0,0 +1,33 @@ + + + org.argeo.cms.servlet + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + org.eclipse.pde.ds.core.builder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/eclipse/org.argeo.cms.servlet/OSGI-INF/jettyServiceFactory.xml b/eclipse/org.argeo.cms.servlet/OSGI-INF/jettyServiceFactory.xml new file mode 100644 index 000000000..c007351aa --- /dev/null +++ b/eclipse/org.argeo.cms.servlet/OSGI-INF/jettyServiceFactory.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/eclipse/org.argeo.cms.servlet/OSGI-INF/pkgServlet.xml b/eclipse/org.argeo.cms.servlet/OSGI-INF/pkgServlet.xml new file mode 100644 index 000000000..00fcaff99 --- /dev/null +++ b/eclipse/org.argeo.cms.servlet/OSGI-INF/pkgServlet.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/eclipse/org.argeo.cms.servlet/OSGI-INF/pkgServletContext.xml b/eclipse/org.argeo.cms.servlet/OSGI-INF/pkgServletContext.xml new file mode 100644 index 000000000..7540a2cdb --- /dev/null +++ b/eclipse/org.argeo.cms.servlet/OSGI-INF/pkgServletContext.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/eclipse/org.argeo.cms.servlet/bnd.bnd b/eclipse/org.argeo.cms.servlet/bnd.bnd new file mode 100644 index 000000000..b539a49bb --- /dev/null +++ b/eclipse/org.argeo.cms.servlet/bnd.bnd @@ -0,0 +1,11 @@ +Import-Package:\ +org.osgi.service.http;version=0.0.0,\ +org.osgi.service.http.whiteboard;version=0.0.0,\ +org.osgi.framework.namespace;version=0.0.0,\ +org.argeo.cms.osgi,\ +* + +Service-Component:\ +OSGI-INF/jettyServiceFactory.xml,\ +OSGI-INF/pkgServletContext.xml,\ +OSGI-INF/pkgServlet.xml diff --git a/eclipse/org.argeo.cms.servlet/build.properties b/eclipse/org.argeo.cms.servlet/build.properties new file mode 100644 index 000000000..ee94f53be --- /dev/null +++ b/eclipse/org.argeo.cms.servlet/build.properties @@ -0,0 +1,5 @@ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + OSGI-INF/jettyServiceFactory.xml +source.. = src/ diff --git a/eclipse/org.argeo.cms.servlet/pom.xml b/eclipse/org.argeo.cms.servlet/pom.xml new file mode 100644 index 000000000..2cd38a1fd --- /dev/null +++ b/eclipse/org.argeo.cms.servlet/pom.xml @@ -0,0 +1,23 @@ + + + 4.0.0 + + org.argeo.commons + 2.3-SNAPSHOT + eclipse + .. + + org.argeo.cms.servlet + jar + CMS Servlet + CMS components depending on the Servlet APIs + + + org.argeo.commons + org.argeo.cms + 2.3-SNAPSHOT + + + \ No newline at end of file diff --git a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/CmsServletContext.java b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/CmsServletContext.java new file mode 100644 index 000000000..1ae6286ac --- /dev/null +++ b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/CmsServletContext.java @@ -0,0 +1,98 @@ +package org.argeo.cms.servlet; + +import java.io.IOException; +import java.net.URL; +import java.security.PrivilegedAction; +import java.util.Map; + +import javax.security.auth.Subject; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.argeo.api.cms.CmsAuth; +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.auth.RemoteAuthCallbackHandler; +import org.argeo.cms.auth.RemoteAuthUtils; +import org.argeo.cms.servlet.internal.HttpUtils; +import org.osgi.framework.Bundle; +import org.osgi.framework.FrameworkUtil; +import org.osgi.service.http.context.ServletContextHelper; + +/** + * Default servlet context degrading to anonymous if the the session is not + * pre-authenticated. + */ +public class CmsServletContext extends ServletContextHelper { + private final static CmsLog log = CmsLog.getLog(CmsServletContext.class); + // use CMS bundle for resources + private Bundle bundle = FrameworkUtil.getBundle(getClass()); + + public void init(Map properties) { + + } + + public void destroy() { + + } + + @Override + public boolean handleSecurity(HttpServletRequest request, HttpServletResponse response) throws IOException { + if (log.isTraceEnabled()) + HttpUtils.logRequestHeaders(log, request); + LoginContext lc; + try { + lc = CmsAuth.USER.newLoginContext( + new RemoteAuthCallbackHandler(new ServletHttpRequest(request), new ServletHttpResponse(response))); + lc.login(); + } catch (LoginException e) { + lc = processUnauthorized(request, response); + if (log.isTraceEnabled()) + HttpUtils.logResponseHeaders(log, response); + if (lc == null) + return false; + } + + Subject subject = lc.getSubject(); + // log.debug("SERVLET CONTEXT: "+subject); + Subject.doAs(subject, new PrivilegedAction() { + + @Override + public Void run() { + // TODO also set login context in order to log out ? + RemoteAuthUtils.configureRequestSecurity(new ServletHttpRequest(request)); + return null; + } + + }); + return true; + } + + @Override + public void finishSecurity(HttpServletRequest request, HttpServletResponse response) { + RemoteAuthUtils.clearRequestSecurity(new ServletHttpRequest(request)); + } + + protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) { + // anonymous + try { + LoginContext lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS, + new RemoteAuthCallbackHandler(new ServletHttpRequest(request), new ServletHttpResponse(response))); + lc.login(); + return lc; + } catch (LoginException e1) { + if (log.isDebugEnabled()) + log.error("Cannot log in as anonymous", e1); + return null; + } + } + + @Override + public URL getResource(String name) { + // TODO make it more robust and versatile + // if used directly it can only load from within this bundle + return bundle.getResource(name); + } + +} diff --git a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java new file mode 100644 index 000000000..3bea0b4de --- /dev/null +++ b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java @@ -0,0 +1,39 @@ +package org.argeo.cms.servlet; + +import javax.security.auth.login.LoginContext; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.argeo.cms.auth.SpnegoLoginModule; +import org.argeo.cms.servlet.internal.HttpUtils; + +/** Servlet context forcing authentication. */ +public class PrivateWwwAuthServletContext extends CmsServletContext { + // TODO make it configurable + private final String httpAuthRealm = "Argeo"; + private final boolean forceBasic = false; + + @Override + protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) { + askForWwwAuth(request, response); + return null; + } + + protected void askForWwwAuth(HttpServletRequest request, HttpServletResponse response) { + // response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "basic + // realm=\"" + httpAuthRealm + "\""); + if (SpnegoLoginModule.hasAcceptorCredentials() && !forceBasic)// SPNEGO + response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "Negotiate"); + else + response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "Basic realm=\"" + httpAuthRealm + "\""); + + // response.setDateHeader("Date", System.currentTimeMillis()); + // response.setDateHeader("Expires", System.currentTimeMillis() + (24 * + // 60 * 60 * 1000)); + // response.setHeader("Accept-Ranges", "bytes"); + // response.setHeader("Connection", "Keep-Alive"); + // response.setHeader("Keep-Alive", "timeout=5, max=97"); + // response.setContentType("text/html; charset=UTF-8"); + response.setStatus(401); + } +} diff --git a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpRequest.java b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpRequest.java new file mode 100644 index 000000000..95912e407 --- /dev/null +++ b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpRequest.java @@ -0,0 +1,63 @@ +package org.argeo.cms.servlet; + +import java.util.Locale; +import java.util.Objects; + +import javax.servlet.http.HttpServletRequest; + +import org.argeo.cms.auth.RemoteAuthRequest; +import org.argeo.cms.auth.RemoteAuthSession; + +public class ServletHttpRequest implements RemoteAuthRequest { + private final HttpServletRequest request; + + public ServletHttpRequest(HttpServletRequest request) { + Objects.requireNonNull(request); + this.request = request; + } + + @Override + public RemoteAuthSession getSession() { + return new ServletHttpSession(request.getSession(false)); + } + + @Override + public RemoteAuthSession createSession() { + return new ServletHttpSession(request.getSession(true)); + } + + @Override + public Locale getLocale() { + return request.getLocale(); + } + + @Override + public Object getAttribute(String key) { + return request.getAttribute(key); + } + + @Override + public void setAttribute(String key, Object object) { + request.setAttribute(key, object); + } + + @Override + public String getHeader(String key) { + return request.getHeader(key); + } + + @Override + public String getRemoteAddr() { + return request.getRemoteAddr(); + } + + @Override + public int getLocalPort() { + return request.getLocalPort(); + } + + @Override + public int getRemotePort() { + return request.getRemotePort(); + } +} diff --git a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpResponse.java b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpResponse.java new file mode 100644 index 000000000..de47365ca --- /dev/null +++ b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpResponse.java @@ -0,0 +1,22 @@ +package org.argeo.cms.servlet; + +import java.util.Objects; + +import javax.servlet.http.HttpServletResponse; + +import org.argeo.cms.auth.RemoteAuthResponse; + +public class ServletHttpResponse implements RemoteAuthResponse { + private final HttpServletResponse response; + + public ServletHttpResponse(HttpServletResponse response) { + Objects.requireNonNull(response); + this.response = response; + } + + @Override + public void setHeader(String keys, String value) { + response.setHeader(keys, value); + } + +} diff --git a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpSession.java b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpSession.java new file mode 100644 index 000000000..8d087daa7 --- /dev/null +++ b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpSession.java @@ -0,0 +1,28 @@ +package org.argeo.cms.servlet; + +import org.argeo.cms.auth.RemoteAuthSession; + +public class ServletHttpSession implements RemoteAuthSession { + private javax.servlet.http.HttpSession session; + + public ServletHttpSession(javax.servlet.http.HttpSession session) { + super(); + this.session = session; + } + + @Override + public boolean isValid() { + try {// test http session + session.getCreationTime(); + return true; + } catch (IllegalStateException ise) { + return false; + } + } + + @Override + public String getId() { + return session.getId(); + } + +} diff --git a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/HttpUtils.java b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/HttpUtils.java new file mode 100644 index 000000000..70f2cc6b0 --- /dev/null +++ b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/HttpUtils.java @@ -0,0 +1,70 @@ +package org.argeo.cms.servlet.internal; + +import java.util.Enumeration; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.argeo.api.cms.CmsLog; + +public class HttpUtils { + public final static String HEADER_AUTHORIZATION = "Authorization"; + public final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate"; + + static boolean isBrowser(String userAgent) { + return userAgent.contains("webkit") || userAgent.contains("gecko") || userAgent.contains("firefox") + || userAgent.contains("msie") || userAgent.contains("chrome") || userAgent.contains("chromium") + || userAgent.contains("opera") || userAgent.contains("browser"); + } + + public static void logResponseHeaders(CmsLog log, HttpServletResponse response) { + if (!log.isDebugEnabled()) + return; + for (String headerName : response.getHeaderNames()) { + Object headerValue = response.getHeader(headerName); + log.debug(headerName + ": " + headerValue); + } + } + + public static void logRequestHeaders(CmsLog log, HttpServletRequest request) { + if (!log.isDebugEnabled()) + return; + for (Enumeration headerNames = request.getHeaderNames(); headerNames.hasMoreElements();) { + String headerName = headerNames.nextElement(); + Object headerValue = request.getHeader(headerName); + log.debug(headerName + ": " + headerValue); + } + log.debug(request.getRequestURI() + "\n"); + } + + public static void logRequest(CmsLog log, HttpServletRequest request) { + log.debug("contextPath=" + request.getContextPath()); + log.debug("servletPath=" + request.getServletPath()); + log.debug("requestURI=" + request.getRequestURI()); + log.debug("queryString=" + request.getQueryString()); + StringBuilder buf = new StringBuilder(); + // headers + Enumeration en = request.getHeaderNames(); + while (en.hasMoreElements()) { + String header = en.nextElement(); + Enumeration values = request.getHeaders(header); + while (values.hasMoreElements()) + buf.append(" " + header + ": " + values.nextElement()); + buf.append('\n'); + } + + // attributed + Enumeration an = request.getAttributeNames(); + while (an.hasMoreElements()) { + String attr = an.nextElement(); + Object value = request.getAttribute(attr); + buf.append(" " + attr + ": " + value); + buf.append('\n'); + } + log.debug("\n" + buf); + } + + private HttpUtils() { + + } +} diff --git a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/PkgServlet.java b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/PkgServlet.java new file mode 100644 index 000000000..c762b67ec --- /dev/null +++ b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/PkgServlet.java @@ -0,0 +1,133 @@ +package org.argeo.cms.servlet.internal; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.Collection; +import java.util.SortedMap; +import java.util.TreeMap; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.io.IOUtils; +import org.argeo.cms.osgi.PublishNamespace; +import org.argeo.osgi.util.FilterRequirement; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.Version; +import org.osgi.framework.VersionRange; +import org.osgi.framework.namespace.PackageNamespace; +import org.osgi.framework.wiring.BundleCapability; +import org.osgi.framework.wiring.BundleWiring; +import org.osgi.framework.wiring.FrameworkWiring; +import org.osgi.resource.Requirement; + +public class PkgServlet extends HttpServlet { + private static final long serialVersionUID = 7660824185145214324L; + + private BundleContext bundleContext = FrameworkUtil.getBundle(PkgServlet.class).getBundleContext(); + + @Override + protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { + String pathInfo = req.getPathInfo(); + + String pkg, versionStr, file; + String[] parts = pathInfo.split("/"); + // first is always empty + if (parts.length == 4) { + pkg = parts[1]; + versionStr = parts[2]; + file = parts[3]; + } else if (parts.length == 3) { + pkg = parts[1]; + versionStr = null; + file = parts[2]; + } else { + throw new IllegalArgumentException("Unsupported path length " + pathInfo); + } + + FrameworkWiring frameworkWiring = bundleContext.getBundle(0).adapt(FrameworkWiring.class); + String filter; + if (versionStr == null) { + filter = "(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")"; + } else { + if (versionStr.startsWith("[") || versionStr.startsWith("(")) {// range + VersionRange versionRange = new VersionRange(versionStr); + filter = "(&(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")" + + versionRange.toFilterString(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE) + ")"; + + } else { + Version version = new Version(versionStr); + filter = "(&(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")(" + + PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE + "=" + version + "))"; + } + } + Requirement requirement = new FilterRequirement(PackageNamespace.PACKAGE_NAMESPACE, filter); + Collection packages = frameworkWiring.findProviders(requirement); + if (packages.isEmpty()) { + resp.sendError(404); + return; + } + + // TODO verify that it works with multiple versions + SortedMap sorted = new TreeMap<>(); + for (BundleCapability capability : packages) { + sorted.put(capability.getRevision().getVersion(), capability); + } + + Bundle bundle = sorted.get(sorted.firstKey()).getRevision().getBundle(); + String entryPath = '/' + pkg.replace('.', '/') + '/' + file; + URL internalURL = bundle.getResource(entryPath); + if (internalURL == null) { + resp.sendError(404); + return; + } + + // Resource found, we now check whether it can be published + boolean publish = false; + BundleWiring bundleWiring = bundle.adapt(BundleWiring.class); + capabilities: for (BundleCapability bundleCapability : bundleWiring + .getCapabilities(PublishNamespace.CMS_PUBLISH_NAMESPACE)) { + Object publishedPkg = bundleCapability.getAttributes().get(PublishNamespace.PKG); + if (publishedPkg != null) { + if (publishedPkg.equals("*") || publishedPkg.equals(pkg)) { + Object publishedFile = bundleCapability.getAttributes().get(PublishNamespace.FILE); + if (publishedFile == null) { + publish = true; + break capabilities; + } else { + String[] publishedFiles = publishedFile.toString().split(","); + for (String pattern : publishedFiles) { + if (pattern.startsWith("*.")) { + String ext = pattern.substring(1); + if (file.endsWith(ext)) { + publish = true; + break capabilities; + } + } else { + if (publishedFile.equals(file)) { + publish = true; + break capabilities; + } + } + } + } + } + } + } + + if (!publish) { + resp.sendError(404); + return; + } + + try (InputStream in = internalURL.openStream()) { + IOUtils.copy(in, resp.getOutputStream()); + } + } + +} diff --git a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/RobotServlet.java b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/RobotServlet.java new file mode 100644 index 000000000..288ee268c --- /dev/null +++ b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/RobotServlet.java @@ -0,0 +1,24 @@ +package org.argeo.cms.servlet.internal; + +import java.io.IOException; +import java.io.PrintWriter; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +public class RobotServlet extends HttpServlet { + private static final long serialVersionUID = 7935661175336419089L; + + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + PrintWriter writer = response.getWriter(); + writer.append("User-agent: *\n"); + writer.append("Disallow:\n"); + response.setHeader("Content-Type", "text/plain"); + writer.flush(); + } + +} diff --git a/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/jetty/JettyServiceFactory.java b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/jetty/JettyServiceFactory.java new file mode 100644 index 000000000..05de32c48 --- /dev/null +++ b/eclipse/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/jetty/JettyServiceFactory.java @@ -0,0 +1,79 @@ +package org.argeo.cms.servlet.internal.jetty; + +import java.util.Dictionary; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.api.cms.CmsLog; +import org.eclipse.equinox.http.jetty.JettyConfigurator; +import org.osgi.framework.FrameworkUtil; +import org.osgi.service.cm.ConfigurationException; +import org.osgi.service.cm.ManagedServiceFactory; + +public class JettyServiceFactory implements ManagedServiceFactory { + private final CmsLog log = CmsLog.getLog(JettyServiceFactory.class); + + public void start() { + + } + + @Override + public String getName() { + return "Jetty Service Factory"; + } + + @Override + public void updated(String pid, Dictionary properties) throws ConfigurationException { + // Explicitly configures Jetty so that the default server is not started by the + // activator of the Equinox Jetty bundle. + +// if (!webServerConfig.isEmpty()) { +// webServerConfig.put("customizer.class", KernelConstants.CMS_JETTY_CUSTOMIZER_CLASS); +// +// // TODO centralise with Jetty extender +// Object webSocketEnabled = webServerConfig.get(InternalHttpConstants.WEBSOCKET_ENABLED); +// if (webSocketEnabled != null && webSocketEnabled.toString().equals("true")) { +// bc.registerService(ServerEndpointConfig.Configurator.class, new CmsWebSocketConfigurator(), null); +// webServerConfig.put(InternalHttpConstants.WEBSOCKET_ENABLED, "true"); +// } +// } + + int tryCount = 60; + try { + tryGettyJetty: while (tryCount > 0) { + try { + // FIXME deal with multiple ids + JettyConfigurator.startServer(CmsConstants.DEFAULT, properties); + // Explicitly starts Jetty OSGi HTTP bundle, so that it gets triggered if OSGi + // configuration is not cleaned + FrameworkUtil.getBundle(JettyConfigurator.class).start(); + break tryGettyJetty; + } catch (IllegalStateException e) { + // Jetty may not be ready + try { + Thread.sleep(1000); + } catch (Exception e1) { + // silent + } + tryCount--; + } + } + } catch (Exception e) { + log.error("Cannot start default Jetty server with config " + properties, e); + } + + } + + @Override + public void deleted(String pid) { + } + + public void stop() { + try { + JettyConfigurator.stopServer(CmsConstants.DEFAULT); + } catch (Exception e) { + log.error("Cannot stop default Jetty server.", e); + } + + } + +} diff --git a/eclipse/org.argeo.cms.swt/.classpath b/eclipse/org.argeo.cms.swt/.classpath new file mode 100644 index 000000000..e03d341b1 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/.classpath @@ -0,0 +1,9 @@ + + + + + + + diff --git a/eclipse/org.argeo.cms.swt/.project b/eclipse/org.argeo.cms.swt/.project new file mode 100644 index 000000000..082112e6d --- /dev/null +++ b/eclipse/org.argeo.cms.swt/.project @@ -0,0 +1,28 @@ + + + org.argeo.cms.swt + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/eclipse/org.argeo.cms.swt/META-INF/.gitignore b/eclipse/org.argeo.cms.swt/META-INF/.gitignore new file mode 100644 index 000000000..4854a41b9 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/META-INF/.gitignore @@ -0,0 +1 @@ +/MANIFEST.MF diff --git a/eclipse/org.argeo.cms.swt/bnd.bnd b/eclipse/org.argeo.cms.swt/bnd.bnd new file mode 100644 index 000000000..9d4eae6c3 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/bnd.bnd @@ -0,0 +1,7 @@ +Import-Package: org.eclipse.swt,\ + org.eclipse.jface.window,\ + org.eclipse.core.commands.common,\ + * + +Bundle-ActivationPolicy: lazy + \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/build.properties b/eclipse/org.argeo.cms.swt/build.properties new file mode 100644 index 000000000..0e0438744 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/build.properties @@ -0,0 +1,5 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + . + \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/icons/actions/add.png b/eclipse/org.argeo.cms.swt/icons/actions/add.png new file mode 100644 index 000000000..5c06bf082 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/actions/add.png differ diff --git a/eclipse/org.argeo.cms.swt/icons/actions/close-all.png b/eclipse/org.argeo.cms.swt/icons/actions/close-all.png new file mode 100644 index 000000000..81bfc950b Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/actions/close-all.png differ diff --git a/eclipse/org.argeo.cms.swt/icons/actions/delete.png b/eclipse/org.argeo.cms.swt/icons/actions/delete.png new file mode 100644 index 000000000..9712723d7 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/actions/delete.png differ diff --git a/eclipse/org.argeo.cms.swt/icons/actions/edit.png b/eclipse/org.argeo.cms.swt/icons/actions/edit.png new file mode 100644 index 000000000..ad3db9f42 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/actions/edit.png differ diff --git a/eclipse/org.argeo.cms.swt/icons/actions/save-all.png b/eclipse/org.argeo.cms.swt/icons/actions/save-all.png new file mode 100644 index 000000000..f48ed320b Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/actions/save-all.png differ diff --git a/eclipse/org.argeo.cms.swt/icons/actions/save.png b/eclipse/org.argeo.cms.swt/icons/actions/save.png new file mode 100644 index 000000000..1c58ada49 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/actions/save.png differ diff --git a/eclipse/org.argeo.cms.swt/icons/active.gif b/eclipse/org.argeo.cms.swt/icons/active.gif new file mode 100644 index 000000000..7d24707ee Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/active.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/add.gif b/eclipse/org.argeo.cms.swt/icons/add.gif new file mode 100644 index 000000000..252d7ebcb Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/add.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/add.png b/eclipse/org.argeo.cms.swt/icons/add.png new file mode 100644 index 000000000..c7edfecaa Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/add.png differ diff --git a/eclipse/org.argeo.cms.swt/icons/addFolder.gif b/eclipse/org.argeo.cms.swt/icons/addFolder.gif new file mode 100644 index 000000000..d3f43d977 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/addFolder.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/addPrivileges.gif b/eclipse/org.argeo.cms.swt/icons/addPrivileges.gif new file mode 100644 index 000000000..a6b251fc8 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/addPrivileges.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/addRepo.gif b/eclipse/org.argeo.cms.swt/icons/addRepo.gif new file mode 100644 index 000000000..26d81c065 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/addRepo.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/addWorkspace.png b/eclipse/org.argeo.cms.swt/icons/addWorkspace.png new file mode 100644 index 000000000..bbee7755f Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/addWorkspace.png differ diff --git a/eclipse/org.argeo.cms.swt/icons/adminLog.gif b/eclipse/org.argeo.cms.swt/icons/adminLog.gif new file mode 100644 index 000000000..6ef3bca66 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/adminLog.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/batch.gif b/eclipse/org.argeo.cms.swt/icons/batch.gif new file mode 100644 index 000000000..b8ca14a8b Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/batch.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/begin.gif b/eclipse/org.argeo.cms.swt/icons/begin.gif new file mode 100755 index 000000000..feb8e94a7 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/begin.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/binary.png b/eclipse/org.argeo.cms.swt/icons/binary.png new file mode 100644 index 000000000..fdf4f82be Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/binary.png differ diff --git a/eclipse/org.argeo.cms.swt/icons/browser.gif b/eclipse/org.argeo.cms.swt/icons/browser.gif new file mode 100644 index 000000000..6c7320c69 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/browser.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/bundles.gif b/eclipse/org.argeo.cms.swt/icons/bundles.gif new file mode 100644 index 000000000..e9a6bd966 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/bundles.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/changePassword.gif b/eclipse/org.argeo.cms.swt/icons/changePassword.gif new file mode 100644 index 000000000..274a850e4 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/changePassword.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/clear.gif b/eclipse/org.argeo.cms.swt/icons/clear.gif new file mode 100644 index 000000000..6bc10f9d0 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/clear.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/close-all.png b/eclipse/org.argeo.cms.swt/icons/close-all.png new file mode 100644 index 000000000..85d4d429b Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/close-all.png differ diff --git a/eclipse/org.argeo.cms.swt/icons/commit.gif b/eclipse/org.argeo.cms.swt/icons/commit.gif new file mode 100755 index 000000000..876f3eb16 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/commit.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/delete.png b/eclipse/org.argeo.cms.swt/icons/delete.png new file mode 100644 index 000000000..676a39dcf Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/delete.png differ diff --git a/eclipse/org.argeo.cms.swt/icons/dumpNode.gif b/eclipse/org.argeo.cms.swt/icons/dumpNode.gif new file mode 100644 index 000000000..14eb1be09 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/dumpNode.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/file.gif b/eclipse/org.argeo.cms.swt/icons/file.gif new file mode 100644 index 000000000..ef3028807 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/file.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/folder.gif b/eclipse/org.argeo.cms.swt/icons/folder.gif new file mode 100644 index 000000000..42e027c93 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/folder.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/getSize.gif b/eclipse/org.argeo.cms.swt/icons/getSize.gif new file mode 100644 index 000000000..b05bf3e3d Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/getSize.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/group.png b/eclipse/org.argeo.cms.swt/icons/group.png new file mode 100644 index 000000000..cc6683aff Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/group.png differ diff --git a/eclipse/org.argeo.cms.swt/icons/home.gif b/eclipse/org.argeo.cms.swt/icons/home.gif new file mode 100644 index 000000000..fd0c66950 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/home.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/home.png b/eclipse/org.argeo.cms.swt/icons/home.png new file mode 100644 index 000000000..5eb096790 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/home.png differ diff --git a/eclipse/org.argeo.cms.swt/icons/import_fs.png b/eclipse/org.argeo.cms.swt/icons/import_fs.png new file mode 100644 index 000000000..d7c890c81 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/import_fs.png differ diff --git a/eclipse/org.argeo.cms.swt/icons/installed.gif b/eclipse/org.argeo.cms.swt/icons/installed.gif new file mode 100644 index 000000000..298871653 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/installed.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/log.gif b/eclipse/org.argeo.cms.swt/icons/log.gif new file mode 100644 index 000000000..e3ecc5535 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/log.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/logout.png b/eclipse/org.argeo.cms.swt/icons/logout.png new file mode 100644 index 000000000..f2952fa5b Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/logout.png differ diff --git a/eclipse/org.argeo.cms.swt/icons/maintenance.gif b/eclipse/org.argeo.cms.swt/icons/maintenance.gif new file mode 100644 index 000000000..e5690ecb1 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/maintenance.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/node.gif b/eclipse/org.argeo.cms.swt/icons/node.gif new file mode 100644 index 000000000..364c0e70b Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/node.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/nodes.gif b/eclipse/org.argeo.cms.swt/icons/nodes.gif new file mode 100644 index 000000000..bba3dbc69 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/nodes.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/osgi_explorer.gif b/eclipse/org.argeo.cms.swt/icons/osgi_explorer.gif new file mode 100644 index 000000000..e9a6bd966 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/osgi_explorer.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/password.gif b/eclipse/org.argeo.cms.swt/icons/password.gif new file mode 100644 index 000000000..a6b251fc8 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/password.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/person-logged-in.png b/eclipse/org.argeo.cms.swt/icons/person-logged-in.png new file mode 100644 index 000000000..87acc1435 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/person-logged-in.png differ diff --git a/eclipse/org.argeo.cms.swt/icons/person.png b/eclipse/org.argeo.cms.swt/icons/person.png new file mode 100644 index 000000000..7d979a531 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/person.png differ diff --git a/eclipse/org.argeo.cms.swt/icons/query.png b/eclipse/org.argeo.cms.swt/icons/query.png new file mode 100644 index 000000000..54c089de1 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/query.png differ diff --git a/eclipse/org.argeo.cms.swt/icons/refresh.png b/eclipse/org.argeo.cms.swt/icons/refresh.png new file mode 100644 index 000000000..71b3481c9 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/refresh.png differ diff --git a/eclipse/org.argeo.cms.swt/icons/remote_connected.gif b/eclipse/org.argeo.cms.swt/icons/remote_connected.gif new file mode 100644 index 000000000..1492b4efa Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/remote_connected.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/remote_disconnected.gif b/eclipse/org.argeo.cms.swt/icons/remote_disconnected.gif new file mode 100644 index 000000000..6c54da9ad Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/remote_disconnected.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/remove.gif b/eclipse/org.argeo.cms.swt/icons/remove.gif new file mode 100644 index 000000000..0ae6decd0 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/remove.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/removePrivileges.gif b/eclipse/org.argeo.cms.swt/icons/removePrivileges.gif new file mode 100644 index 000000000..aa78fd2fa Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/removePrivileges.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/rename.gif b/eclipse/org.argeo.cms.swt/icons/rename.gif new file mode 100644 index 000000000..8048405a7 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/rename.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/repositories.gif b/eclipse/org.argeo.cms.swt/icons/repositories.gif new file mode 100644 index 000000000..c13bea1ca Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/repositories.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/repository_connected.gif b/eclipse/org.argeo.cms.swt/icons/repository_connected.gif new file mode 100644 index 000000000..a15fa5538 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/repository_connected.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/repository_disconnected.gif b/eclipse/org.argeo.cms.swt/icons/repository_disconnected.gif new file mode 100644 index 000000000..4576dc563 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/repository_disconnected.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/resolved.gif b/eclipse/org.argeo.cms.swt/icons/resolved.gif new file mode 100644 index 000000000..f4a1ea150 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/resolved.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/role.gif b/eclipse/org.argeo.cms.swt/icons/role.gif new file mode 100644 index 000000000..274a850e4 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/role.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/rollback.gif b/eclipse/org.argeo.cms.swt/icons/rollback.gif new file mode 100755 index 000000000..c75399599 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/rollback.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/save-all.png b/eclipse/org.argeo.cms.swt/icons/save-all.png new file mode 100644 index 000000000..b68a29b2c Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/save-all.png differ diff --git a/eclipse/org.argeo.cms.swt/icons/save.gif b/eclipse/org.argeo.cms.swt/icons/save.gif new file mode 100644 index 000000000..654ad7b42 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/save.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/save.png b/eclipse/org.argeo.cms.swt/icons/save.png new file mode 100644 index 000000000..f27ef2d26 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/save.png differ diff --git a/eclipse/org.argeo.cms.swt/icons/save_security.png b/eclipse/org.argeo.cms.swt/icons/save_security.png new file mode 100644 index 000000000..ca41dc92b Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/save_security.png differ diff --git a/eclipse/org.argeo.cms.swt/icons/save_security_disabled.png b/eclipse/org.argeo.cms.swt/icons/save_security_disabled.png new file mode 100644 index 000000000..fb7d08d9a Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/save_security_disabled.png differ diff --git a/eclipse/org.argeo.cms.swt/icons/security.gif b/eclipse/org.argeo.cms.swt/icons/security.gif new file mode 100644 index 000000000..57fb95edc Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/security.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/service_published.gif b/eclipse/org.argeo.cms.swt/icons/service_published.gif new file mode 100644 index 000000000..17f771aff Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/service_published.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/service_referenced.gif b/eclipse/org.argeo.cms.swt/icons/service_referenced.gif new file mode 100644 index 000000000..c24a95fba Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/service_referenced.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/sort.gif b/eclipse/org.argeo.cms.swt/icons/sort.gif new file mode 100644 index 000000000..23c5d0b11 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/sort.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/starting.gif b/eclipse/org.argeo.cms.swt/icons/starting.gif new file mode 100644 index 000000000..563743d39 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/starting.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/sync.gif b/eclipse/org.argeo.cms.swt/icons/sync.gif new file mode 100644 index 000000000..b4fa052de Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/sync.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/user.gif b/eclipse/org.argeo.cms.swt/icons/user.gif new file mode 100644 index 000000000..90a00147b Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/user.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/users.gif b/eclipse/org.argeo.cms.swt/icons/users.gif new file mode 100644 index 000000000..2de7edd64 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/users.gif differ diff --git a/eclipse/org.argeo.cms.swt/icons/workgroup.png b/eclipse/org.argeo.cms.swt/icons/workgroup.png new file mode 100644 index 000000000..7fef996df Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/workgroup.png differ diff --git a/eclipse/org.argeo.cms.swt/icons/workgroup.xcf b/eclipse/org.argeo.cms.swt/icons/workgroup.xcf new file mode 100644 index 000000000..f517c827c Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/workgroup.xcf differ diff --git a/eclipse/org.argeo.cms.swt/icons/workspace_connected.png b/eclipse/org.argeo.cms.swt/icons/workspace_connected.png new file mode 100644 index 000000000..0430baaf5 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/workspace_connected.png differ diff --git a/eclipse/org.argeo.cms.swt/icons/workspace_disconnected.png b/eclipse/org.argeo.cms.swt/icons/workspace_disconnected.png new file mode 100644 index 000000000..fddcb8c4e Binary files /dev/null and b/eclipse/org.argeo.cms.swt/icons/workspace_disconnected.png differ diff --git a/eclipse/org.argeo.cms.swt/pom.xml b/eclipse/org.argeo.cms.swt/pom.xml new file mode 100644 index 000000000..a8b43aa58 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/pom.xml @@ -0,0 +1,53 @@ + + + 4.0.0 + + org.argeo.commons + 2.3-SNAPSHOT + eclipse + .. + + org.argeo.cms.swt + CMS SWT + + + + + + + + org.argeo.commons + org.argeo.cms + 2.3-SNAPSHOT + + + org.argeo.commons + org.argeo.cms.servlet + 2.3-SNAPSHOT + + + + + org.argeo.commons.rap + org.argeo.swt.specific.rap + 2.3-SNAPSHOT + provided + + + + org.argeo.tp.rap.e4 + org.eclipse.rap.rwt + provided + + + org.argeo.tp.rap.e4 + org.eclipse.core.commands + provided + + + org.argeo.tp.rap.e4 + org.eclipse.rap.jface + provided + + + \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsIcon.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsIcon.java new file mode 100644 index 000000000..4ff89f27a --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsIcon.java @@ -0,0 +1,25 @@ +package org.argeo.cms.swt; + +import org.argeo.api.cms.CmsTheme; +import org.eclipse.swt.graphics.Image; + +/** Can be applied to {@link Enum}s in order to generated {@link Image}s. */ +public interface CmsIcon { + String name(); + + default Image getSmallIcon(CmsTheme theme) { + return ((CmsSwtTheme) theme).getIcon(name(), getSmallIconSize()); + } + + default Image getBigIcon(CmsTheme theme) { + return ((CmsSwtTheme) theme).getIcon(name(), getBigIconSize()); + } + + default Integer getSmallIconSize() { + return 16; + } + + default Integer getBigIconSize() { + return 32; + } +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsStyles.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsStyles.java new file mode 100644 index 000000000..9eba6f6ec --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsStyles.java @@ -0,0 +1,32 @@ +package org.argeo.cms.swt; + +/** Styles references in the CSS. */ +@Deprecated +public interface CmsStyles { + // General + public final static String CMS_SHELL = "cms_shell"; + public final static String CMS_MENU_LINK = "cms_menu_link"; + + // Header + public final static String CMS_HEADER = "cms_header"; + public final static String CMS_HEADER_LEAD = "cms_header-lead"; + public final static String CMS_HEADER_CENTER = "cms_header-center"; + public final static String CMS_HEADER_END = "cms_header-end"; + + public final static String CMS_LEAD = "cms_lead"; + public final static String CMS_END = "cms_end"; + public final static String CMS_FOOTER = "cms_footer"; + + public final static String CMS_USER_MENU = "cms_user_menu"; + public final static String CMS_USER_MENU_LINK = "cms_user_menu-link"; + public final static String CMS_USER_MENU_ITEM = "cms_user_menu-item"; + public final static String CMS_LOGIN_DIALOG = "cms_login_dialog"; + public final static String CMS_LOGIN_DIALOG_USERNAME = "cms_login_dialog-username"; + public final static String CMS_LOGIN_DIALOG_PASSWORD = "cms_login_dialog-password"; + + // Body + public final static String CMS_SCROLLED_AREA = "cms_scrolled_area"; + public final static String CMS_BODY = "cms_body"; + public final static String CMS_STATIC_TEXT = "cms_static-text"; + public final static String CMS_LINK = "cms_link"; +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtTheme.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtTheme.java new file mode 100644 index 000000000..b5f7c0e4d --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtTheme.java @@ -0,0 +1,21 @@ +package org.argeo.cms.swt; + +import org.argeo.api.cms.CmsTheme; +import org.eclipse.swt.graphics.Image; + +/** SWT specific {@link CmsTheme}. */ +public interface CmsSwtTheme extends CmsTheme { + /** The image registered at this path, or null if not found. */ + Image getImage(String path); + + /** + * And icon with this file name (without the extension), with a best effort to + * find the appropriate size, or null if not found. + * + * @param name An icon file name without path and extension. + * @param preferredSize the preferred size, if null, + * {@link #getDefaultIconSize()} will be tried. + */ + Image getIcon(String name, Integer preferredSize); + +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtUtils.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtUtils.java new file mode 100644 index 000000000..a94d70706 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtUtils.java @@ -0,0 +1,251 @@ +package org.argeo.cms.swt; + +import java.util.HashMap; +import java.util.Map; + +import org.argeo.api.cms.CmsStyle; +import org.argeo.api.cms.CmsTheme; +import org.argeo.api.cms.CmsView; +import org.argeo.eclipse.ui.specific.EclipseUiSpecificUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.layout.FormAttachment; +import org.eclipse.swt.layout.FormData; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.layout.RowData; +import org.eclipse.swt.layout.RowLayout; +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.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; +import org.eclipse.swt.widgets.Widget; + +/** SWT utilities. */ +public class CmsSwtUtils { + + /** Singleton. */ + private CmsSwtUtils() { + } + + public static CmsTheme getCmsTheme(Composite parent) { + CmsTheme theme = (CmsTheme) parent.getData(CmsTheme.class.getName()); + if (theme == null) { + // find parent shell + Shell topShell = parent.getShell(); + while (topShell.getParent() != null) + topShell = (Shell) topShell.getParent(); + theme = (CmsTheme) topShell.getData(CmsTheme.class.getName()); + parent.setData(CmsTheme.class.getName(), theme); + } + return theme; + } + + public static void registerCmsTheme(Shell shell, CmsTheme theme) { + // find parent shell + Shell topShell = shell; + while (topShell.getParent() != null) + topShell = (Shell) topShell.getParent(); + // check if already set + if (topShell.getData(CmsTheme.class.getName()) != null) { + CmsTheme registeredTheme = (CmsTheme) topShell.getData(CmsTheme.class.getName()); + throw new IllegalArgumentException( + "Theme " + registeredTheme.getThemeId() + " already registered in this shell"); + } + topShell.setData(CmsTheme.class.getName(), theme); + } + + public static CmsView getCmsView(Control parent) { + // find parent shell + Shell topShell = parent.getShell(); + while (topShell.getParent() != null) + topShell = (Shell) topShell.getParent(); + return (CmsView) topShell.getData(CmsView.class.getName()); + } + + public static void registerCmsView(Shell shell, CmsView view) { + // find parent shell + Shell topShell = shell; + while (topShell.getParent() != null) + topShell = (Shell) topShell.getParent(); + // check if already set + if (topShell.getData(CmsView.class.getName()) != null) { + CmsView registeredView = (CmsView) topShell.getData(CmsView.class.getName()); + throw new IllegalArgumentException("Cms view " + registeredView + " already registered in this shell"); + } + shell.setData(CmsView.class.getName(), view); + } + + /** Sends an event via {@link CmsView#sendEvent(String, Map)}. */ + public static void sendEventOnSelect(Control control, String topic, Map properties) { + SelectionListener listener = (Selected) (e) -> { + getCmsView(control.getParent()).sendEvent(topic, properties); + }; + if (control instanceof Button) { + ((Button) control).addSelectionListener(listener); + } else + throw new UnsupportedOperationException("Control type " + control.getClass() + " is not supported."); + } + + /** + * Convenience method to sends an event via + * {@link CmsView#sendEvent(String, Map)}. + */ + public static void sendEventOnSelect(Control control, String topic, String key, Object value) { + Map properties = new HashMap<>(); + properties.put(key, value); + sendEventOnSelect(control, topic, properties); + } + + /* + * GRID LAYOUT + */ + public static GridLayout noSpaceGridLayout() { + return noSpaceGridLayout(new GridLayout()); + } + + public static GridLayout noSpaceGridLayout(int columns) { + return noSpaceGridLayout(new GridLayout(columns, false)); + } + + /** @return the same layout, with spaces removed. */ + public static GridLayout noSpaceGridLayout(GridLayout layout) { + layout.horizontalSpacing = 0; + layout.verticalSpacing = 0; + layout.marginWidth = 0; + layout.marginHeight = 0; + return layout; + } + + public static GridData fillAll() { + return new GridData(SWT.FILL, SWT.FILL, true, true); + } + + public static GridData fillWidth() { + return grabWidth(SWT.FILL, SWT.FILL); + } + + public static GridData grabWidth(int horizontalAlignment, int verticalAlignment) { + return new GridData(horizontalAlignment, horizontalAlignment, true, false); + } + + public static GridData fillHeight() { + return grabHeight(SWT.FILL, SWT.FILL); + } + + public static GridData grabHeight(int horizontalAlignment, int verticalAlignment) { + return new GridData(horizontalAlignment, horizontalAlignment, false, true); + } + + /* + * ROW LAYOUT + */ + /** @return the same layout, with margins removed. */ + public static RowLayout noMarginsRowLayout(RowLayout rowLayout) { + rowLayout.marginTop = 0; + rowLayout.marginBottom = 0; + rowLayout.marginLeft = 0; + rowLayout.marginRight = 0; + return rowLayout; + } + + public static RowLayout noMarginsRowLayout(int type) { + return noMarginsRowLayout(new RowLayout(type)); + } + + public static RowData rowData16px() { + return new RowData(16, 16); + } + + /* + * FORM LAYOUT + */ + public static FormData coverAll() { + FormData fdLabel = new FormData(); + fdLabel.top = new FormAttachment(0, 0); + fdLabel.left = new FormAttachment(0, 0); + fdLabel.right = new FormAttachment(100, 0); + fdLabel.bottom = new FormAttachment(100, 0); + return fdLabel; + } + + /* + * STYLING + */ + + /** Style widget */ + public static T style(T widget, String style) { + if (style == null) + return widget;// does nothing + EclipseUiSpecificUtils.setStyleData(widget, style); + if (widget instanceof Control) { + CmsView cmsView = getCmsView((Control) widget); + if (cmsView != null) + cmsView.applyStyles(widget); + } + return widget; + } + + /** Style widget */ + public static T style(T widget, CmsStyle style) { + return style(widget, style.style()); + } + + /** Enable markups on widget */ + public static T markup(T widget) { + EclipseUiSpecificUtils.setMarkupData(widget); + return widget; + } + + /** Disable markup validation. */ + public static T disableMarkupValidation(T widget) { + EclipseUiSpecificUtils.setMarkupValidationDisabledData(widget); + return widget; + } + + /** + * Apply markup and set text on {@link Label}, {@link Button}, {@link Text}. + * + * @param widget the widget to style and to use in order to display text + * @param txt the object to display via its toString() method. + * This argument should not be null, but if it is null and + * assertions are disabled "" is displayed instead; if + * assertions are enabled the call will fail. + * + * @see markup + */ + public static T text(T widget, Object txt) { + assert txt != null; + String str = txt != null ? txt.toString() : ""; + markup(widget); + if (widget instanceof Label) + ((Label) widget).setText(str); + else if (widget instanceof Button) + ((Button) widget).setText(str); + else if (widget instanceof Text) + ((Text) widget).setText(str); + else + throw new IllegalArgumentException("Unsupported widget type " + widget.getClass()); + return widget; + } + + /** A {@link Label} with markup activated. */ + public static Label lbl(Composite parent, Object txt) { + return text(new Label(parent, SWT.NONE), txt); + } + + /** A read-only {@link Text} whose content can be copy/pasted. */ + public static Text txt(Composite parent, Object txt) { + return text(new Text(parent, SWT.NONE), txt); + } + + /** Dispose all children of a Composite */ + public static void clear(Composite composite) { + if (composite.isDisposed()) + return; + for (Control child : composite.getChildren()) + child.dispose(); + } +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDoubleClick.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDoubleClick.java new file mode 100644 index 000000000..b818b06d9 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDoubleClick.java @@ -0,0 +1,26 @@ +package org.argeo.cms.swt; + +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; + +/** + * {@link MouseListener#mouseDoubleClick(MouseEvent)} as a functional interface + * in order to use as a short lambda expression in UI code. + * {@link MouseListener#mouseDownouseEvent)} and + * {@link MouseListener#mouseUp(MouseEvent)} do nothing by default. + */ +@FunctionalInterface +public interface MouseDoubleClick extends MouseListener { + @Override + void mouseDoubleClick(MouseEvent e); + + @Override + default void mouseDown(MouseEvent e) { + // does nothing + } + + @Override + default void mouseUp(MouseEvent e) { + // does nothing + } +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDown.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDown.java new file mode 100644 index 000000000..baecb0072 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDown.java @@ -0,0 +1,26 @@ +package org.argeo.cms.swt; + +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; + +/** + * {@link MouseListener#mouseDown(MouseEvent)} as a functional interface in + * order to use as a short lambda expression in UI code. + * {@link MouseListener#mouseDoubleClick(MouseEvent)} and + * {@link MouseListener#mouseUp(MouseEvent)} do nothing by default. + */ +@FunctionalInterface +public interface MouseDown extends MouseListener { + @Override + void mouseDown(MouseEvent e); + + @Override + default void mouseDoubleClick(MouseEvent e) { + // does nothing + } + + @Override + default void mouseUp(MouseEvent e) { + // does nothing + } +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/Selected.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/Selected.java new file mode 100644 index 000000000..03fbad01e --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/Selected.java @@ -0,0 +1,21 @@ +package org.argeo.cms.swt; + +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; + +/** + * {@link SelectionListener} as a functional interface in order to use as a + * short lambda expression in UI code. + * {@link SelectionListener#widgetDefaultSelected(SelectionEvent)} does nothing + * by default. + */ +@FunctionalInterface +public interface Selected extends SelectionListener { + @Override + public void widgetSelected(SelectionEvent e); + + default public void widgetDefaultSelected(SelectionEvent e) { + // does nothing + } + +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/SimpleSwtUxContext.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/SimpleSwtUxContext.java new file mode 100644 index 000000000..9c55e8b10 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/SimpleSwtUxContext.java @@ -0,0 +1,50 @@ +package org.argeo.cms.swt; + +import org.argeo.api.cms.UxContext; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Display; + +public class SimpleSwtUxContext implements UxContext { + private Point size; + private Point small = new Point(400, 400); + + public SimpleSwtUxContext() { + this(Display.getCurrent().getBounds()); + } + + public SimpleSwtUxContext(Rectangle rect) { + this.size = new Point(rect.width, rect.height); + } + + public SimpleSwtUxContext(Point size) { + this.size = size; + } + + @Override + public boolean isPortrait() { + return size.x >= size.y; + } + + @Override + public boolean isLandscape() { + return size.x < size.y; + } + + @Override + public boolean isSquare() { + return size.x == size.y; + } + + @Override + public boolean isSmall() { + return size.x <= small.x || size.y <= small.y; + } + + @Override + public boolean isMasterData() { + // TODO make it configurable + return true; + } + +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java new file mode 100644 index 000000000..9c8680c4d --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java @@ -0,0 +1,336 @@ +package org.argeo.cms.swt.auth; + +import static org.argeo.cms.CmsMsg.password; +import static org.argeo.cms.CmsMsg.username; + +import java.io.IOException; +import java.util.List; +import java.util.Locale; + +import javax.security.auth.Subject; +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.LanguageCallback; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.UnsupportedCallbackException; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; + +import org.argeo.api.cms.CmsAuth; +import org.argeo.api.cms.CmsContext; +import org.argeo.api.cms.CmsLog; +import org.argeo.api.cms.CmsView; +import org.argeo.cms.CmsMsg; +import org.argeo.cms.LocaleUtils; +import org.argeo.cms.auth.RemoteAuthCallback; +import org.argeo.cms.servlet.ServletHttpRequest; +import org.argeo.cms.servlet.ServletHttpResponse; +import org.argeo.cms.swt.CmsStyles; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.eclipse.ui.specific.UiContext; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.events.TraverseEvent; +import org.eclipse.swt.events.TraverseListener; +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.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +public class CmsLogin implements CmsStyles, CallbackHandler { + private final static CmsLog log = CmsLog.getLog(CmsLogin.class); + + private Composite parent; + private Text usernameT, passwordT; + private Composite credentialsBlock; + private final SelectionListener loginSelectionListener; + + private final Locale defaultLocale; + private LocaleChoice localeChoice = null; + + private final CmsView cmsView; + + // optional subject to be set explicitly + private Subject subject = null; + + public CmsLogin(CmsView cmsView) { + this.cmsView = cmsView; + CmsContext nodeState = null;// = Activator.getNodeState(); + // FIXME reactivate locales + if (nodeState != null) { + defaultLocale = nodeState.getDefaultLocale(); + List locales = nodeState.getLocales(); + if (locales != null) + localeChoice = new LocaleChoice(locales, defaultLocale); + } else { + defaultLocale = Locale.getDefault(); + } + loginSelectionListener = new SelectionListener() { + private static final long serialVersionUID = -8832133363830973578L; + + @Override + public void widgetSelected(SelectionEvent e) { + login(); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + } + }; + } + + protected boolean isAnonymous() { + return cmsView.isAnonymous(); + } + + public final void createUi(Composite parent) { + this.parent = parent; + createContents(parent); + } + + protected void createContents(Composite parent) { + defaultCreateContents(parent); + } + + public final void defaultCreateContents(Composite parent) { + parent.setLayout(CmsSwtUtils.noSpaceGridLayout()); + Composite credentialsBlock = createCredentialsBlock(parent); + if (parent instanceof Shell) { + credentialsBlock.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true)); + } + } + + public final Composite createCredentialsBlock(Composite parent) { + if (isAnonymous()) { + return anonymousUi(parent); + } else { + return userUi(parent); + } + } + + public Composite getCredentialsBlock() { + return credentialsBlock; + } + + protected Composite userUi(Composite parent) { + Locale locale = localeChoice == null ? this.defaultLocale : localeChoice.getSelectedLocale(); + credentialsBlock = new Composite(parent, SWT.NONE); + credentialsBlock.setLayout(new GridLayout()); + // credentialsBlock.setLayoutData(CmsUiUtils.fillAll()); + + specificUserUi(credentialsBlock); + + Label l = new Label(credentialsBlock, SWT.NONE); + CmsSwtUtils.style(l, CMS_USER_MENU_ITEM); + l.setText(CmsMsg.logout.lead(locale)); + GridData lData = CmsSwtUtils.fillWidth(); + lData.widthHint = 120; + l.setLayoutData(lData); + + l.addMouseListener(new MouseAdapter() { + private static final long serialVersionUID = 6444395812777413116L; + + public void mouseDown(MouseEvent e) { + logout(); + } + }); + return credentialsBlock; + } + + /** To be overridden */ + protected void specificUserUi(Composite parent) { + + } + + protected Composite anonymousUi(Composite parent) { + Locale locale = localeChoice == null ? this.defaultLocale : localeChoice.getSelectedLocale(); + // We need a composite for the traversal + credentialsBlock = new Composite(parent, SWT.NONE); + credentialsBlock.setLayout(new GridLayout()); + // credentialsBlock.setLayoutData(CmsUiUtils.fillAll()); + CmsSwtUtils.style(credentialsBlock, CMS_LOGIN_DIALOG); + + Integer textWidth = 120; + if (parent instanceof Shell) + CmsSwtUtils.style(parent, CMS_USER_MENU); + // new Label(this, SWT.NONE).setText(CmsMsg.username.lead()); + usernameT = new Text(credentialsBlock, SWT.BORDER); + usernameT.setMessage(username.lead(locale)); + CmsSwtUtils.style(usernameT, CMS_LOGIN_DIALOG_USERNAME); + GridData gd = CmsSwtUtils.fillWidth(); + gd.widthHint = textWidth; + usernameT.setLayoutData(gd); + + // new Label(this, SWT.NONE).setText(CmsMsg.password.lead()); + passwordT = new Text(credentialsBlock, SWT.BORDER | SWT.PASSWORD); + passwordT.setMessage(password.lead(locale)); + CmsSwtUtils.style(passwordT, CMS_LOGIN_DIALOG_PASSWORD); + gd = CmsSwtUtils.fillWidth(); + gd.widthHint = textWidth; + passwordT.setLayoutData(gd); + + TraverseListener tl = new TraverseListener() { + private static final long serialVersionUID = -1158892811534971856L; + + public void keyTraversed(TraverseEvent e) { + if (e.detail == SWT.TRAVERSE_RETURN) + login(); + } + }; + credentialsBlock.addTraverseListener(tl); + usernameT.addTraverseListener(tl); + passwordT.addTraverseListener(tl); + parent.setTabList(new Control[] { credentialsBlock }); + credentialsBlock.setTabList(new Control[] { usernameT, passwordT }); + + // Button + Button loginButton = new Button(credentialsBlock, SWT.PUSH); + loginButton.setText(CmsMsg.login.lead(locale)); + loginButton.setLayoutData(CmsSwtUtils.fillWidth()); + loginButton.addSelectionListener(loginSelectionListener); + + extendsCredentialsBlock(credentialsBlock, locale, loginSelectionListener); + if (localeChoice != null) + createLocalesBlock(credentialsBlock); + return credentialsBlock; + } + + /** + * To be overridden in order to provide custom login button and other links. + */ + protected void extendsCredentialsBlock(Composite credentialsBlock, Locale selectedLocale, + SelectionListener loginSelectionListener) { + + } + + protected void updateLocale(Locale selectedLocale) { + // save already entered values + String usernameStr = usernameT.getText(); + char[] pwd = passwordT.getTextChars(); + + for (Control child : parent.getChildren()) + child.dispose(); + createContents(parent); + if (parent.getParent() != null) + parent.getParent().layout(true, true); + else + parent.layout(); + usernameT.setText(usernameStr); + passwordT.setTextChars(pwd); + } + + protected Composite createLocalesBlock(final Composite parent) { + Composite c = new Composite(parent, SWT.NONE); + CmsSwtUtils.style(c, CMS_USER_MENU_ITEM); + c.setLayout(CmsSwtUtils.noSpaceGridLayout()); + c.setLayoutData(CmsSwtUtils.fillAll()); + + SelectionListener selectionListener = new SelectionAdapter() { + private static final long serialVersionUID = 4891637813567806762L; + + public void widgetSelected(SelectionEvent event) { + Button button = (Button) event.widget; + if (button.getSelection()) { + localeChoice.setSelectedIndex((Integer) event.widget.getData()); + updateLocale(localeChoice.getSelectedLocale()); + } + }; + }; + + List locales = localeChoice.getLocales(); + for (Integer i = 0; i < locales.size(); i++) { + Locale locale = locales.get(i); + Button button = new Button(c, SWT.RADIO); + CmsSwtUtils.style(button, CMS_USER_MENU_ITEM); + button.setData(i); + button.setText(LocaleUtils.toLead(locale.getDisplayName(locale), locale) + " (" + locale + ")"); + // button.addListener(SWT.Selection, listener); + button.addSelectionListener(selectionListener); + if (i == localeChoice.getSelectedIndex()) + button.setSelection(true); + } + return c; + } + + protected boolean login() { + // TODO use CmsVie in order to retrieve subject? + // Subject subject = cmsView.getLoginContext().getSubject(); + // LoginContext loginContext = cmsView.getLoginContext(); + try { + // + // LOGIN + // + // loginContext.logout(); + LoginContext loginContext; + if (subject == null) + loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, this); + else + loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, subject, this); + loginContext.login(); + cmsView.authChange(loginContext); + return true; + } catch (LoginException e) { + if (log.isTraceEnabled()) + log.warn("Login failed: " + e.getMessage(), e); + else + log.warn("Login failed: " + e.getMessage()); + + try { + Thread.sleep(3000); + } catch (InterruptedException e2) { + // silent + } + // ErrorFeedback.show("Login failed", e); + return false; + } + // catch (LoginException e) { + // log.error("Cannot login", e); + // return false; + // } + } + + protected void logout() { + cmsView.logout(); + cmsView.navigateTo("~"); + } + + @Override + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + for (Callback callback : callbacks) { + if (callback instanceof NameCallback && usernameT != null) + ((NameCallback) callback).setName(usernameT.getText()); + else if (callback instanceof PasswordCallback && passwordT != null) + ((PasswordCallback) callback).setPassword(passwordT.getTextChars()); + else if (callback instanceof RemoteAuthCallback) { + ((RemoteAuthCallback) callback).setRequest(new ServletHttpRequest(UiContext.getHttpRequest())); + ((RemoteAuthCallback) callback).setResponse(new ServletHttpResponse(UiContext.getHttpResponse())); + } else if (callback instanceof LanguageCallback) { + Locale toUse = null; + if (localeChoice != null) + toUse = localeChoice.getSelectedLocale(); + else if (defaultLocale != null) + toUse = defaultLocale; + + if (toUse != null) { + ((LanguageCallback) callback).setLocale(toUse); + UiContext.setLocale(toUse); + } + + } + } + } + + public void setSubject(Subject subject) { + this.subject = subject; + } + +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLoginShell.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLoginShell.java new file mode 100644 index 000000000..f6a35f136 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLoginShell.java @@ -0,0 +1,72 @@ +package org.argeo.cms.swt.auth; + +import org.argeo.api.cms.CmsView; +import org.argeo.cms.swt.CmsSwtUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; + +/** The site-related user menu */ +public class CmsLoginShell extends CmsLogin { + private final Shell shell; + + public CmsLoginShell(CmsView cmsView) { + super(cmsView); + shell = createShell(); +// createUi(shell); + } + + /** To be overridden. */ + protected Shell createShell() { + Shell shell = new Shell(Display.getCurrent(), SWT.NO_TRIM); + shell.setMaximized(true); + return shell; + } + + /** To be overridden. */ + public void open() { + CmsSwtUtils.style(shell, CMS_USER_MENU); + shell.open(); + } + + @Override + protected boolean login() { + boolean success = false; + try { + success = super.login(); + return success; + } finally { + if (success) + closeShell(); + else { + for (Control child : shell.getChildren()) + child.dispose(); + createUi(shell); + shell.layout(); + // TODO error message + } + } + } + + @Override + protected void logout() { + closeShell(); + super.logout(); + } + + protected void closeShell() { + if (!shell.isDisposed()) { + shell.close(); + shell.dispose(); + } + } + + public Shell getShell() { + return shell; + } + + public void createUi(){ + createUi(shell); + } +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CompositeCallbackHandler.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CompositeCallbackHandler.java new file mode 100644 index 000000000..495007cb2 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CompositeCallbackHandler.java @@ -0,0 +1,273 @@ +package org.argeo.cms.swt.auth; + +import java.io.IOException; +import java.util.Arrays; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.NameCallback; +import javax.security.auth.callback.PasswordCallback; +import javax.security.auth.callback.TextOutputCallback; +import javax.security.auth.callback.UnsupportedCallbackException; + +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.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +/** + * A composite that can populate itself based on {@link Callback}s. It can be + * used directly as a {@link CallbackHandler} or be used by one by calling the + * {@link #createCallbackHandlers(Callback[])}. Supported standard + * {@link Callback}s are:
+ *
    + *
  • {@link PasswordCallback}
  • + *
  • {@link NameCallback}
  • + *
  • {@link TextOutputCallback}
  • + *
+ * Supported Argeo {@link Callback}s are:
+ *
    + *
  • {@link LocaleChoice}
  • + *
+ */ +public class CompositeCallbackHandler extends Composite implements CallbackHandler { + private static final long serialVersionUID = -928223893722723777L; + + private boolean wasUsedAlready = false; + private boolean isSubmitted = false; + private boolean isCanceled = false; + + public CompositeCallbackHandler(Composite parent, int style) { + super(parent, style); + } + + @Override + public synchronized void handle(final Callback[] callbacks) throws IOException, UnsupportedCallbackException { + // reset + if (wasUsedAlready && !isSubmitted() && !isCanceled()) { + cancel(); + for (Control control : getChildren()) + control.dispose(); + isSubmitted = false; + isCanceled = false; + } + + for (Callback callback : callbacks) + checkCallbackSupported(callback); + // create controls synchronously in the UI thread + getDisplay().syncExec(new Runnable() { + + @Override + public void run() { + createCallbackHandlers(callbacks); + } + }); + + if (!wasUsedAlready) + wasUsedAlready = true; + + // while (!isSubmitted() && !isCanceled()) { + // try { + // wait(1000l); + // } catch (InterruptedException e) { + // // silent + // } + // } + + // cleanCallbacksAfterCancel(callbacks); + } + + public void checkCallbackSupported(Callback callback) throws UnsupportedCallbackException { + if (callback instanceof TextOutputCallback || callback instanceof NameCallback + || callback instanceof PasswordCallback || callback instanceof LocaleChoice) { + return; + } else { + throw new UnsupportedCallbackException(callback); + } + } + + /** + * Set writable callbacks to null if the handle is canceled (check is done + * by the method) + */ + public void cleanCallbacksAfterCancel(Callback[] callbacks) { + if (isCanceled()) { + for (Callback callback : callbacks) { + if (callback instanceof NameCallback) { + ((NameCallback) callback).setName(null); + } else if (callback instanceof PasswordCallback) { + PasswordCallback pCallback = (PasswordCallback) callback; + char[] arr = pCallback.getPassword(); + if (arr != null) { + Arrays.fill(arr, '*'); + pCallback.setPassword(null); + } + } + } + } + } + + public void createCallbackHandlers(Callback[] callbacks) { + Composite composite = this; + for (int i = 0; i < callbacks.length; i++) { + Callback callback = callbacks[i]; + if (callback instanceof TextOutputCallback) { + createLabelTextoutputHandler(composite, (TextOutputCallback) callback); + } else if (callback instanceof NameCallback) { + createNameHandler(composite, (NameCallback) callback); + } else if (callback instanceof PasswordCallback) { + createPasswordHandler(composite, (PasswordCallback) callback); + } else if (callback instanceof LocaleChoice) { + createLocaleHandler(composite, (LocaleChoice) callback); + } + } + } + + protected Text createNameHandler(Composite composite, final NameCallback callback) { + Label label = new Label(composite, SWT.NONE); + label.setText(callback.getPrompt()); + final Text text = new Text(composite, SWT.SINGLE | SWT.LEAD | SWT.BORDER); + if (callback.getDefaultName() != null) { + // set default value, if provided + text.setText(callback.getDefaultName()); + callback.setName(callback.getDefaultName()); + } + text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + text.addModifyListener(new ModifyListener() { + private static final long serialVersionUID = 7300032545287292973L; + + public void modifyText(ModifyEvent event) { + callback.setName(text.getText()); + } + }); + text.addSelectionListener(new SelectionListener() { + private static final long serialVersionUID = 1820530045857665111L; + + @Override + public void widgetSelected(SelectionEvent e) { + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + submit(); + } + }); + + text.addKeyListener(new KeyListener() { + private static final long serialVersionUID = -8698107785092095713L; + + @Override + public void keyReleased(KeyEvent e) { + } + + @Override + public void keyPressed(KeyEvent e) { + } + }); + return text; + } + + protected Text createPasswordHandler(Composite composite, final PasswordCallback callback) { + Label label = new Label(composite, SWT.NONE); + label.setText(callback.getPrompt()); + final Text passwordText = new Text(composite, SWT.SINGLE | SWT.LEAD | SWT.PASSWORD | SWT.BORDER); + passwordText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + passwordText.addModifyListener(new ModifyListener() { + private static final long serialVersionUID = -7099363995047686732L; + + public void modifyText(ModifyEvent event) { + callback.setPassword(passwordText.getTextChars()); + } + }); + passwordText.addSelectionListener(new SelectionListener() { + private static final long serialVersionUID = 1820530045857665111L; + + @Override + public void widgetSelected(SelectionEvent e) { + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + submit(); + } + }); + return passwordText; + } + + protected Combo createLocaleHandler(Composite composite, final LocaleChoice callback) { + String[] labels = callback.getSupportedLocalesLabels(); + if (labels.length == 0) + return null; + Label label = new Label(composite, SWT.NONE); + label.setText("Language"); + + final Combo combo = new Combo(composite, SWT.READ_ONLY); + combo.setItems(labels); + combo.select(callback.getDefaultIndex()); + combo.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + combo.addSelectionListener(new SelectionListener() { + private static final long serialVersionUID = 38678989091946277L; + + @Override + public void widgetSelected(SelectionEvent e) { + callback.setSelectedIndex(combo.getSelectionIndex()); + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + } + }); + return combo; + } + + protected Label createLabelTextoutputHandler(Composite composite, final TextOutputCallback callback) { + Label label = new Label(composite, SWT.NONE); + label.setText(callback.getMessage()); + GridData data = new GridData(SWT.FILL, SWT.FILL, true, true); + data.horizontalSpan = 2; + label.setLayoutData(data); + return label; + // TODO: find a way to pass this information + // int messageType = callback.getMessageType(); + // int dialogMessageType = IMessageProvider.NONE; + // switch (messageType) { + // case TextOutputCallback.INFORMATION: + // dialogMessageType = IMessageProvider.INFORMATION; + // break; + // case TextOutputCallback.WARNING: + // dialogMessageType = IMessageProvider.WARNING; + // break; + // case TextOutputCallback.ERROR: + // dialogMessageType = IMessageProvider.ERROR; + // break; + // } + // setMessage(callback.getMessage(), dialogMessageType); + } + + synchronized boolean isSubmitted() { + return isSubmitted; + } + + synchronized boolean isCanceled() { + return isCanceled; + } + + protected synchronized void submit() { + isSubmitted = true; + notifyAll(); + } + + protected synchronized void cancel() { + isCanceled = true; + notifyAll(); + } +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/DynamicCallbackHandler.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/DynamicCallbackHandler.java new file mode 100644 index 000000000..b0c36c602 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/DynamicCallbackHandler.java @@ -0,0 +1,34 @@ +package org.argeo.cms.swt.auth; + +import java.io.IOException; + +import javax.security.auth.callback.Callback; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.callback.UnsupportedCallbackException; + +import org.argeo.eclipse.ui.dialogs.LightweightDialog; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; + +public class DynamicCallbackHandler implements CallbackHandler { + + @Override + public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { + Shell activeShell = Display.getCurrent().getActiveShell(); + LightweightDialog dialog = new LightweightDialog(activeShell) { + + @Override + protected Control createDialogArea(Composite parent) { + CompositeCallbackHandler cch = new CompositeCallbackHandler(parent, SWT.NONE); + cch.createCallbackHandlers(callbacks); + return cch; + } + }; + dialog.setBlockOnOpen(true); + dialog.open(); + } + +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/LocaleChoice.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/LocaleChoice.java new file mode 100644 index 000000000..e98e390ee --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/LocaleChoice.java @@ -0,0 +1,86 @@ +package org.argeo.cms.swt.auth; + +import java.util.Collections; +import java.util.List; +import java.util.Locale; + +import javax.security.auth.callback.LanguageCallback; + +import org.argeo.cms.CmsException; +import org.argeo.cms.LocaleUtils; + +/** Choose in a list of locales. TODO: replace with {@link LanguageCallback} */ +public class LocaleChoice { + private final List locales; + + private Integer selectedIndex = null; + private final Integer defaultIndex; + + public LocaleChoice(List locales, Locale defaultLocale) { + Integer defaultIndex = null; + this.locales = Collections.unmodifiableList(locales); + for (int i = 0; i < locales.size(); i++) + if (locales.get(i).equals(defaultLocale)) + defaultIndex = i; + + // based on language only + if (defaultIndex == null) + for (int i = 0; i < locales.size(); i++) + if (locales.get(i).getLanguage().equals(defaultLocale.getLanguage())) + defaultIndex = i; + + if (defaultIndex == null) + throw new CmsException("Default locale " + defaultLocale + " is not in available locales " + locales); + this.defaultIndex = defaultIndex; + + this.selectedIndex = defaultIndex; + } + + /** + * Convenience constructor based on a comma separated list of iso codes (en, + * en_US, fr_CA, etc.). Default selection is default locale. + */ + public LocaleChoice(String locales, Locale defaultLocale) { + this(LocaleUtils.asLocaleList(locales), defaultLocale); + } + + public String[] getSupportedLocalesLabels() { + String[] labels = new String[locales.size()]; + for (int i = 0; i < locales.size(); i++) { + Locale locale = locales.get(i); + if (locale.getCountry().equals("")) + labels[i] = locale.getDisplayLanguage(locale) + " [" + locale.getLanguage() + "]"; + else + labels[i] = locale.getDisplayLanguage(locale) + " (" + locale.getDisplayCountry(locale) + ") [" + + locale.getLanguage() + "_" + locale.getCountry() + "]"; + + } + return labels; + } + + public Locale getSelectedLocale() { + if (selectedIndex == null) + return null; + return locales.get(selectedIndex); + } + + public void setSelectedIndex(Integer selectedIndex) { + this.selectedIndex = selectedIndex; + } + + public Integer getSelectedIndex() { + return selectedIndex; + } + + public Integer getDefaultIndex() { + return defaultIndex; + } + + public List getLocales() { + return locales; + } + + public Locale getDefaultLocale() { + return locales.get(getDefaultIndex()); + } +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/package-info.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/package-info.java new file mode 100644 index 000000000..b431423d8 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/package-info.java @@ -0,0 +1,2 @@ +/** Argeo CMS authentication widgets, based on SWT. */ +package org.argeo.cms.swt.auth; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/ChangePasswordDialog.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/ChangePasswordDialog.java new file mode 100644 index 000000000..8ff086283 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/ChangePasswordDialog.java @@ -0,0 +1,83 @@ +package org.argeo.cms.swt.dialogs; + +import java.security.PrivilegedAction; +import java.util.Arrays; + +import org.argeo.api.cms.CmsView; +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.CmsMsg; +import org.argeo.cms.CmsUserManager; +import org.argeo.cms.swt.CmsSwtUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +/** Dialog to change a password. */ +public class ChangePasswordDialog extends CmsMessageDialog { + private final static CmsLog log = CmsLog.getLog(ChangePasswordDialog.class); + + private CmsUserManager cmsUserManager; + private CmsView cmsView; + + private PrivilegedAction doIt; + + public ChangePasswordDialog(Shell parentShell, String message, int kind, CmsUserManager cmsUserManager) { + super(parentShell, message, kind); + this.cmsUserManager = cmsUserManager; + cmsView = CmsSwtUtils.getCmsView(parentShell); + } + + @Override + protected Control createInputArea(Composite userSection) { + addFormLabel(userSection, CmsMsg.currentPassword.lead()); + Text previousPassword = new Text(userSection, SWT.BORDER | SWT.PASSWORD); + previousPassword.setLayoutData(CmsSwtUtils.fillWidth()); + addFormLabel(userSection, CmsMsg.newPassword.lead()); + Text newPassword = new Text(userSection, SWT.BORDER | SWT.PASSWORD); + newPassword.setLayoutData(CmsSwtUtils.fillWidth()); + addFormLabel(userSection, CmsMsg.repeatNewPassword.lead()); + Text confirmPassword = new Text(userSection, SWT.BORDER | SWT.PASSWORD); + confirmPassword.setLayoutData(CmsSwtUtils.fillWidth()); + + doIt = () -> { + if (Arrays.equals(newPassword.getTextChars(), confirmPassword.getTextChars())) { + try { + cmsUserManager.changeOwnPassword(previousPassword.getTextChars(), newPassword.getTextChars()); + return OK; + } catch (Exception e1) { + log.error("Could not change password", e1); + cancel(); + CmsMessageDialog.openError(CmsMsg.invalidPassword.lead()); + return CANCEL; + } + } else { + cancel(); + CmsMessageDialog.openError(CmsMsg.repeatNewPassword.lead()); + return CANCEL; + } + }; + + pack(); + return previousPassword; + } + + @Override + protected void okPressed() { + Integer returnCode = cmsView.doAs(doIt); + if (returnCode.equals(OK)) { + super.okPressed(); + CmsMessageDialog.openInformation(CmsMsg.passwordChanged.lead()); + } + } + + private static Label addFormLabel(Composite parent, String label) { + Label lbl = new Label(parent, SWT.WRAP); + lbl.setText(label); +// CmsUiUtils.style(lbl, SuiteStyle.simpleLabel); + return lbl; + } + +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsFeedback.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsFeedback.java new file mode 100644 index 000000000..a01c919e9 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsFeedback.java @@ -0,0 +1,100 @@ +package org.argeo.cms.swt.dialogs; + +import java.io.PrintWriter; +import java.io.StringWriter; + +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.CmsMsg; +import org.argeo.cms.swt.Selected; +import org.eclipse.swt.SWT; +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.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +/** A dialog feedback based on a {@link LightweightDialog}. */ +public class CmsFeedback extends LightweightDialog { + private final static CmsLog log = CmsLog.getLog(CmsFeedback.class); + + private String message; + private Throwable exception; + + public CmsFeedback(Shell parentShell, String message, Throwable e) { + super(parentShell); + this.message = message; + this.exception = e; + log.error(message, e); + } + + public static CmsFeedback show(String message, Throwable e) { + // rethrow ThreaDeath in order to make sure that RAP will properly clean + // up the UI thread + if (e instanceof ThreadDeath) + throw (ThreadDeath) e; + + try { + CmsFeedback cmsFeedback = new CmsFeedback(null, message, e); + cmsFeedback.setBlockOnOpen(false); + cmsFeedback.open(); + return cmsFeedback; + } catch (Throwable e1) { + log.error("Cannot open error feedback (" + e.getMessage() + "), original error below", e); + return null; + } + } + + public static CmsFeedback show(String message) { + CmsFeedback cmsFeedback = new CmsFeedback(null, message, null); + cmsFeedback.open(); + return cmsFeedback; + } + + /** Tries to find a display */ + // private static Display getDisplay() { + // try { + // Display display = Display.getCurrent(); + // if (display != null) + // return display; + // else + // return Display.getDefault(); + // } catch (Exception e) { + // return Display.getCurrent(); + // } + // } + + protected Control createDialogArea(Composite parent) { + parent.setLayout(new GridLayout(2, false)); + + Label messageLbl = new Label(parent, SWT.WRAP); + if (message != null) + messageLbl.setText(message); + else if (exception != null) + messageLbl.setText(exception.getLocalizedMessage()); + + Button close = new Button(parent, SWT.FLAT); + close.setText(CmsMsg.close.lead()); + close.setLayoutData(new GridData(SWT.END, SWT.TOP, false, false)); + close.addSelectionListener((Selected) (e) -> closeShell(OK)); + + // Composite composite = new Composite(dialogarea, SWT.NONE); + // composite.setLayout(new GridLayout(2, false)); + // composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + if (exception != null) { + Text stack = new Text(parent, SWT.MULTI | SWT.LEAD | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL); + stack.setEditable(false); + stack.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1)); + StringWriter sw = new StringWriter(); + exception.printStackTrace(new PrintWriter(sw)); + stack.setText(sw.toString()); + } + + // parent.pack(); + return messageLbl; + } + +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsMessageDialog.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsMessageDialog.java new file mode 100644 index 000000000..66e640595 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsMessageDialog.java @@ -0,0 +1,167 @@ +package org.argeo.cms.swt.dialogs; + +import org.argeo.cms.CmsMsg; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.swt.Selected; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.TraverseEvent; +import org.eclipse.swt.events.TraverseListener; +import org.eclipse.swt.graphics.Point; +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.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; + +/** Base class for dialogs displaying messages or small forms. */ +public class CmsMessageDialog extends LightweightDialog { + public final static int NONE = 0; + public final static int ERROR = 1; + public final static int INFORMATION = 2; + public final static int QUESTION = 3; + public final static int WARNING = 4; + public final static int CONFIRM = 5; + public final static int QUESTION_WITH_CANCEL = 6; + + private int kind; + private String message; + + public CmsMessageDialog(Shell parentShell, String message, int kind) { + super(parentShell); + this.kind = kind; + this.message = message; + } + + protected Control createDialogArea(Composite parent) { + parent.setLayout(new GridLayout()); + + TraverseListener traverseListener = new TraverseListener() { + private static final long serialVersionUID = -1158892811534971856L; + + public void keyTraversed(TraverseEvent e) { + if (e.detail == SWT.TRAVERSE_RETURN) + okPressed(); + else if (e.detail == SWT.TRAVERSE_ESCAPE) + cancelPressed(); + } + }; + + // message + Composite body = new Composite(parent, SWT.NONE); + body.addTraverseListener(traverseListener); + GridLayout bodyGridLayout = new GridLayout(); + bodyGridLayout.marginHeight = 20; + bodyGridLayout.marginWidth = 20; + body.setLayout(bodyGridLayout); + body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + if (message != null) { + Label messageLbl = new Label(body, SWT.WRAP); + CmsSwtUtils.markup(messageLbl); + messageLbl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + messageLbl.setFont(EclipseUiUtils.getBoldFont(parent)); + messageLbl.setText(message); + } + + // buttons + Composite buttons = new Composite(parent, SWT.NONE); + buttons.addTraverseListener(traverseListener); + buttons.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false)); + if (kind == INFORMATION || kind == WARNING || kind == ERROR || kind == ERROR) { + GridLayout layout = new GridLayout(1, true); + layout.marginWidth = 0; + layout.marginHeight = 0; + buttons.setLayout(layout); + + Button close = new Button(buttons, SWT.FLAT); + close.setText(CmsMsg.close.lead()); + close.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + close.addSelectionListener((Selected) (e) -> closeShell(OK)); + close.setFocus(); + close.addTraverseListener(traverseListener); + + buttons.setTabList(new Control[] { close }); + } else if (kind == CONFIRM || kind == QUESTION || kind == QUESTION_WITH_CANCEL) { + Control input = createInputArea(body); + if (input != null) { + input.addTraverseListener(traverseListener); + body.setTabList(new Control[] { input }); + } + GridLayout layout = new GridLayout(2, true); + layout.marginWidth = 0; + layout.marginHeight = 0; + buttons.setLayout(layout); + + Button cancel = new Button(buttons, SWT.FLAT); + cancel.setText(CmsMsg.cancel.lead()); + cancel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + cancel.addSelectionListener((Selected) (e) -> cancelPressed()); + cancel.addTraverseListener(traverseListener); + + Button ok = new Button(buttons, SWT.FLAT); + ok.setText(CmsMsg.ok.lead()); + ok.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + ok.addSelectionListener((Selected) (e) -> okPressed()); + ok.addTraverseListener(traverseListener); + if (input == null) + ok.setFocus(); + else + input.setFocus(); + + buttons.setTabList(new Control[] { ok, cancel }); + } + // pack(); + parent.setTabList(new Control[] { body, buttons }); + return body; + } + + protected Control createInputArea(Composite parent) { + return null; + } + + protected void okPressed() { + closeShell(OK); + } + + protected void cancelPressed() { + closeShell(CANCEL); + } + + protected void cancel() { + closeShell(CANCEL); + } + + protected Point getInitialSize() { + return new Point(400, 200); + } + + public static boolean open(int kind, Shell parent, String message) { + CmsMessageDialog dialog = new CmsMessageDialog(parent, message, kind); + return dialog.open() == 0; + } + + public static boolean openConfirm(String message) { + return open(CONFIRM, Display.getCurrent().getActiveShell(), message); + } + + public static void openInformation(String message) { + open(INFORMATION, Display.getCurrent().getActiveShell(), message); + } + + public static boolean openQuestion(String message) { + return open(QUESTION, Display.getCurrent().getActiveShell(), message); + } + + public static void openWarning(String message) { + open(WARNING, Display.getCurrent().getActiveShell(), message); + } + + public static void openError(String message) { + open(ERROR, Display.getCurrent().getActiveShell(), message); + } + +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsWizardDialog.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsWizardDialog.java new file mode 100644 index 000000000..59d9ab7f5 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsWizardDialog.java @@ -0,0 +1,220 @@ +package org.argeo.cms.swt.dialogs; + +import java.lang.reflect.InvocationTargetException; + +import org.argeo.cms.CmsMsg; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.swt.Selected; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.eclipse.jface.operation.IRunnableWithProgress; +import org.eclipse.jface.wizard.IWizard; +import org.eclipse.jface.wizard.IWizardContainer2; +import org.eclipse.jface.wizard.IWizardPage; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.FormAttachment; +import org.eclipse.swt.layout.FormData; +import org.eclipse.swt.layout.FormLayout; +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.eclipse.swt.widgets.Shell; + +/** A wizard dialog based on {@link LightweightDialog}. */ +public class CmsWizardDialog extends LightweightDialog implements IWizardContainer2 { + private static final long serialVersionUID = -2123153353654812154L; + + private IWizard wizard; + private IWizardPage currentPage; + private int currentPageIndex; + + private Label titleBar; + private Label message; + private Composite[] pageBodies; + private Composite buttons; + private Button back; + private Button next; + private Button finish; + + public CmsWizardDialog(Shell parentShell, IWizard wizard) { + super(parentShell); + this.wizard = wizard; + wizard.setContainer(this); + // create the pages + wizard.addPages(); + currentPage = wizard.getStartingPage(); + if (currentPage == null) + throw new IllegalArgumentException("At least one wizard page is required"); + } + + @Override + protected Control createDialogArea(Composite parent) { + updateWindowTitle(); + + Composite messageArea = new Composite(parent, SWT.NONE); + messageArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + { + messageArea.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(2, false))); + titleBar = new Label(messageArea, SWT.WRAP); + titleBar.setFont(EclipseUiUtils.getBoldFont(parent)); + titleBar.setLayoutData(new GridData(SWT.BEGINNING, SWT.FILL, true, false)); + updateTitleBar(); + Button cancelButton = new Button(messageArea, SWT.FLAT); + cancelButton.setText(CmsMsg.cancel.lead()); + cancelButton.setLayoutData(new GridData(SWT.END, SWT.TOP, false, false, 1, 3)); + cancelButton.addSelectionListener((Selected) (e) -> closeShell(CANCEL)); + message = new Label(messageArea, SWT.WRAP); + message.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 2)); + updateMessage(); + } + + Composite body = new Composite(parent, SWT.BORDER); + body.setLayout(new FormLayout()); + body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + pageBodies = new Composite[wizard.getPageCount()]; + IWizardPage[] pages = wizard.getPages(); + for (int i = 0; i < pages.length; i++) { + pageBodies[i] = new Composite(body, SWT.NONE); + pageBodies[i].setLayout(CmsSwtUtils.noSpaceGridLayout()); + setSwitchingFormData(pageBodies[i]); + pages[i].createControl(pageBodies[i]); + } + showPage(currentPage); + + buttons = new Composite(parent, SWT.NONE); + buttons.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false)); + { + boolean singlePage = wizard.getPageCount() == 1; + // singlePage = false;// dev + GridLayout layout = new GridLayout(singlePage ? 1 : 3, true); + layout.marginWidth = 0; + layout.marginHeight = 0; + buttons.setLayout(layout); + // TODO revert order for right-to-left languages + + if (!singlePage) { + back = new Button(buttons, SWT.PUSH); + back.setText(CmsMsg.wizardBack.lead()); + back.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + back.addSelectionListener((Selected) (e) -> backPressed()); + + next = new Button(buttons, SWT.PUSH); + next.setText(CmsMsg.wizardNext.lead()); + next.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + next.addSelectionListener((Selected) (e) -> nextPressed()); + } + finish = new Button(buttons, SWT.PUSH); + finish.setText(CmsMsg.wizardFinish.lead()); + finish.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + finish.addSelectionListener((Selected) (e) -> finishPressed()); + + updateButtons(); + } + return body; + } + + @Override + public IWizardPage getCurrentPage() { + return currentPage; + } + + @Override + public Shell getShell() { + return getForegoundShell(); + } + + @Override + public void showPage(IWizardPage page) { + IWizardPage[] pages = wizard.getPages(); + int index = -1; + for (int i = 0; i < pages.length; i++) { + if (page == pages[i]) { + index = i; + break; + } + } + if (index < 0) + throw new IllegalArgumentException("Cannot find index of wizard page " + page); + pageBodies[index].moveAbove(pageBodies[currentPageIndex]); + + // // clear + // for (Control c : body.getChildren()) + // c.dispose(); + // page.createControl(body); + // body.layout(true, true); + currentPageIndex = index; + currentPage = page; + } + + @Override + public void updateButtons() { + if (back != null) + back.setEnabled(wizard.getPreviousPage(currentPage) != null); + if (next != null) + next.setEnabled(wizard.getNextPage(currentPage) != null && currentPage.canFlipToNextPage()); + if (finish != null) { + finish.setEnabled(wizard.canFinish()); + } + } + + @Override + public void updateMessage() { + if (currentPage.getMessage() != null) + message.setText(currentPage.getMessage()); + } + + @Override + public void updateTitleBar() { + if (currentPage.getTitle() != null) + titleBar.setText(currentPage.getTitle()); + } + + @Override + public void updateWindowTitle() { + setTitle(wizard.getWindowTitle()); + } + + @Override + public void run(boolean fork, boolean cancelable, IRunnableWithProgress runnable) + throws InvocationTargetException, InterruptedException { + runnable.run(null); + } + + @Override + public void updateSize() { + // TODO pack? + } + + protected boolean onCancel() { + return wizard.performCancel(); + } + + protected void nextPressed() { + IWizardPage page = wizard.getNextPage(currentPage); + showPage(page); + updateButtons(); + } + + protected void backPressed() { + IWizardPage page = wizard.getPreviousPage(currentPage); + showPage(page); + updateButtons(); + } + + protected void finishPressed() { + if (wizard.performFinish()) + closeShell(OK); + } + + private static void setSwitchingFormData(Composite composite) { + FormData fdLabel = new FormData(); + fdLabel.top = new FormAttachment(0, 0); + fdLabel.left = new FormAttachment(0, 0); + fdLabel.right = new FormAttachment(100, 0); + fdLabel.bottom = new FormAttachment(100, 0); + composite.setLayoutData(fdLabel); + } + +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/LightweightDialog.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/LightweightDialog.java new file mode 100644 index 000000000..bf6417bea --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/LightweightDialog.java @@ -0,0 +1,255 @@ +package org.argeo.cms.swt.dialogs; + +import org.argeo.api.cms.CmsLog; +import org.argeo.eclipse.ui.EclipseUiException; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.ShellAdapter; +import org.eclipse.swt.events.ShellEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +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.Display; +import org.eclipse.swt.widgets.Shell; + +/** Generic lightweight dialog, not based on JFace. */ +public class LightweightDialog { + private final static CmsLog log = CmsLog.getLog(LightweightDialog.class); + + // must be the same value as org.eclipse.jface.window.Window#OK + public final static int OK = 0; + // must be the same value as org.eclipse.jface.window.Window#CANCEL + public final static int CANCEL = 1; + + private Shell parentShell; + private Shell backgroundShell; + private Shell foregoundShell; + + private Integer returnCode = null; + private boolean block = true; + + private String title; + + /** Tries to find a display */ + private static Display getDisplay() { + try { + Display display = Display.getCurrent(); + if (display != null) + return display; + else + return Display.getDefault(); + } catch (Exception e) { + return Display.getCurrent(); + } + } + + public LightweightDialog(Shell parentShell) { + this.parentShell = parentShell; + } + + public int open() { + if (foregoundShell != null) + throw new EclipseUiException("There is already a shell"); + backgroundShell = new Shell(parentShell, SWT.ON_TOP); + backgroundShell.setFullScreen(true); + // if (parentShell != null) { + // backgroundShell.setBounds(parentShell.getBounds()); + // } else + // backgroundShell.setMaximized(true); + backgroundShell.setAlpha(128); + backgroundShell.setBackground(getDisplay().getSystemColor(SWT.COLOR_BLACK)); + foregoundShell = new Shell(backgroundShell, SWT.NO_TRIM | SWT.ON_TOP); + if (title != null) + setTitle(title); + foregoundShell.setLayout(new GridLayout()); + foregoundShell.setSize(getInitialSize()); + createDialogArea(foregoundShell); + // shell.pack(); + // shell.layout(); + + Rectangle shellBounds = parentShell != null ? parentShell.getBounds() : Display.getCurrent().getBounds();// RAP + Point dialogSize = foregoundShell.getSize(); + int x = shellBounds.x + (shellBounds.width - dialogSize.x) / 2; + int y = shellBounds.y + (shellBounds.height - dialogSize.y) / 2; + foregoundShell.setLocation(x, y); + + foregoundShell.addShellListener(new ShellAdapter() { + private static final long serialVersionUID = -2701270481953688763L; + + @Override + public void shellDeactivated(ShellEvent e) { + if (hasChildShells()) + return; + if (returnCode == null)// not yet closed + closeShell(CANCEL); + } + + @Override + public void shellClosed(ShellEvent e) { + notifyClose(); + } + + }); + + backgroundShell.open(); + foregoundShell.open(); + // after the foreground shell has been opened + backgroundShell.addFocusListener(new FocusListener() { + private static final long serialVersionUID = 3137408447474661070L; + + @Override + public void focusLost(FocusEvent event) { + } + + @Override + public void focusGained(FocusEvent event) { + if (hasChildShells()) + return; + if (returnCode == null)// not yet closed + closeShell(CANCEL); + } + }); + + if (block) { + block(); + } + if (returnCode == null) + returnCode = OK; + return returnCode; + } + + public void block() { + try { + runEventLoop(foregoundShell); + } catch (ThreadDeath t) { + returnCode = CANCEL; + if (log.isTraceEnabled()) + log.error("Thread death, canceling dialog", t); + } catch (Throwable t) { + returnCode = CANCEL; + log.error("Cannot open blocking lightweight dialog", t); + } + } + + private boolean hasChildShells() { + if (foregoundShell == null) + return false; + return foregoundShell.getShells().length != 0; + } + + // public synchronized int openAndWait() { + // open(); + // while (returnCode == null) + // try { + // wait(100); + // } catch (InterruptedException e) { + // // silent + // } + // return returnCode; + // } + + private synchronized void notifyClose() { + if (returnCode == null) + returnCode = CANCEL; + notifyAll(); + } + + protected void closeShell(int returnCode) { + this.returnCode = returnCode; + if (CANCEL == returnCode) + onCancel(); + if (foregoundShell != null && !foregoundShell.isDisposed()) { + foregoundShell.close(); + foregoundShell.dispose(); + foregoundShell = null; + } + + if (backgroundShell != null && !backgroundShell.isDisposed()) { + backgroundShell.close(); + backgroundShell.dispose(); + } + } + + protected Point getInitialSize() { + // if (exception != null) + // return new Point(800, 600); + // else + return new Point(600, 400); + } + + protected Control createDialogArea(Composite parent) { + Composite dialogarea = new Composite(parent, SWT.NONE); + dialogarea.setLayout(new GridLayout()); + dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + return dialogarea; + } + + protected Shell getBackgroundShell() { + return backgroundShell; + } + + protected Shell getForegoundShell() { + return foregoundShell; + } + + public void setBlockOnOpen(boolean shouldBlock) { + block = shouldBlock; + } + + public void pack() { + foregoundShell.pack(); + } + + private void runEventLoop(Shell loopShell) { + Display display; + if (foregoundShell == null) { + display = Display.getCurrent(); + } else { + display = loopShell.getDisplay(); + } + + while (loopShell != null && !loopShell.isDisposed()) { + try { + if (!display.readAndDispatch()) { + display.sleep(); + } + } catch (UnsupportedOperationException e) { + throw e; + } catch (Throwable e) { + handleException(e); + } + } + if (!display.isDisposed()) + display.update(); + } + + protected void handleException(Throwable t) { + if (t instanceof ThreadDeath) { + // Don't catch ThreadDeath as this is a normal occurrence when + // the thread dies + throw (ThreadDeath) t; + } + // Try to keep running. + t.printStackTrace(); + } + + /** @return false, if the dialog should not be closed. */ + protected boolean onCancel() { + return true; + } + + public void setTitle(String title) { + this.title = title; + if (title != null && getForegoundShell() != null) + getForegoundShell().setText(title); + } + + public Integer getReturnCode() { + return returnCode; + } + +} \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/SingleValueDialog.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/SingleValueDialog.java new file mode 100644 index 000000000..9404b81da --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/SingleValueDialog.java @@ -0,0 +1,82 @@ +package org.argeo.cms.swt.dialogs; + +import org.eclipse.jface.window.Window; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +/** A dialog asking a for a single value. */ +public class SingleValueDialog extends CmsMessageDialog { + private Text valueT; + private String value; + private String defaultValue; + + public SingleValueDialog(Shell parentShell, String message) { + super(parentShell, message, QUESTION); + } + + public SingleValueDialog(Shell parentShell, String message, String defaultValue) { + super(parentShell, message, QUESTION); + this.defaultValue = defaultValue; + } + + @Override + protected Control createInputArea(Composite parent) { + valueT = new Text(parent, SWT.LEAD | SWT.BORDER | SWT.SINGLE); + valueT.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true)); + if (defaultValue != null) + valueT.setText(defaultValue); + return valueT; + } + + @Override + protected void okPressed() { + value = valueT.getText(); + super.okPressed(); + } + + public String getString() { + return value; + } + + public Long getLong() { + return Long.valueOf(getString()); + } + + public Double getDouble() { + return Double.valueOf(getString()); + } + + public static String ask(String message) { + return ask(message, null); + } + + public static String ask(String message, String defaultValue) { + SingleValueDialog svd = new SingleValueDialog(Display.getCurrent().getActiveShell(), message, defaultValue); + if (svd.open() == Window.OK) + return svd.getString(); + else + return null; + } + + public static Long askLong(String message) { + SingleValueDialog svd = new SingleValueDialog(Display.getCurrent().getActiveShell(), message); + if (svd.open() == Window.OK) + return svd.getLong(); + else + return null; + } + + public static Double askDouble(String message) { + SingleValueDialog svd = new SingleValueDialog(Display.getCurrent().getActiveShell(), message); + if (svd.open() == Window.OK) + return svd.getDouble(); + else + return null; + } + +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/package-info.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/package-info.java new file mode 100644 index 000000000..ac76dba81 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/package-info.java @@ -0,0 +1,2 @@ +/** SWT/JFace dialogs. */ +package org.argeo.cms.swt.dialogs; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/GcrContentTreeView.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/GcrContentTreeView.java new file mode 100644 index 000000000..4039f2baa --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/GcrContentTreeView.java @@ -0,0 +1,130 @@ +package org.argeo.cms.swt.gcr; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import javax.xml.namespace.QName; + +import org.argeo.api.acr.Content; +import org.argeo.cms.acr.fs.FsContentProvider; +import org.argeo.cms.swt.CmsSwtUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.FillLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.swt.widgets.TableItem; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.TreeItem; + +public class GcrContentTreeView extends Composite { + private Tree tree; + private Table table; + private Content rootContent; + + private Content selected; + + public GcrContentTreeView(Composite parent, int style, Content content) { + super(parent, style); + this.rootContent = content; + this.selected = rootContent; + setLayout(new GridLayout(2, false)); + initTree(); + GridData treeGd = CmsSwtUtils.fillHeight(); + treeGd.widthHint = 300; + tree.setLayoutData(treeGd); + initTable(); + + table.setLayoutData(CmsSwtUtils.fillAll()); + } + + protected void initTree() { + tree = new Tree(this, 0); + for (Content c : rootContent) { + TreeItem root = new TreeItem(tree, 0); + root.setText(c.getName().toString()); + root.setData(c); + new TreeItem(root, 0); + } + tree.addListener(SWT.Expand, event -> { + final TreeItem root = (TreeItem) event.item; + TreeItem[] items = root.getItems(); + for (TreeItem item : items) { + if (item.getData() != null) + return; + item.dispose(); + } + Content content = (Content) root.getData(); + for (Content c : content) { + TreeItem item = new TreeItem(root, 0); + item.setText(c.getName().toString()); + item.setData(c); + boolean hasChildren = true; + if (hasChildren) { + new TreeItem(item, 0); + } + } + }); + tree.addListener(SWT.Selection, event -> { + TreeItem item = (TreeItem) event.item; + selected = (Content) item.getData(); + refreshTable(); + }); + } + + protected void initTable() { + table = new Table(this, 0); + table.setLinesVisible(true); + table.setHeaderVisible(true); + TableColumn keyCol = new TableColumn(table, SWT.NONE); + keyCol.setText("Attribute"); + keyCol.setWidth(200); + TableColumn valueCol = new TableColumn(table, SWT.NONE); + valueCol.setText("Value"); + keyCol.setWidth(300); + refreshTable(); + } + + protected void refreshTable() { + for (TableItem item : table.getItems()) { + item.dispose(); + } + for (QName key : selected.keySet()) { + TableItem item = new TableItem(table, 0); + item.setText(0, key.toString()); + Object value = selected.get(key); + item.setText(1, value.toString()); + } + table.getColumn(0).pack(); + table.getColumn(1).pack(); + } + + public static void main(String[] args) { + Path basePath; + if (args.length > 0) { + basePath = Paths.get(args[0]); + } else { + basePath = Paths.get(System.getProperty("user.home")); + } + + final Display display = new Display(); + final Shell shell = new Shell(display); + shell.setText(basePath.toString()); + shell.setLayout(new FillLayout()); + + FsContentProvider contentSession = new FsContentProvider(basePath); +// GcrContentTreeView treeView = new GcrContentTreeView(shell, 0, contentSession.get("/")); + + shell.setSize(shell.computeSize(800, 600)); + shell.open(); + while (!shell.isDisposed()) { + if (!display.readAndDispatch()) + display.sleep(); + } + display.dispose(); + } +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/SwtUiProvider.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/SwtUiProvider.java new file mode 100644 index 000000000..8109c40ac --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/SwtUiProvider.java @@ -0,0 +1,11 @@ +package org.argeo.cms.swt.gcr; + +import org.argeo.api.acr.Content; +import org.argeo.api.cms.MvcProvider; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + +@FunctionalInterface +public interface SwtUiProvider extends MvcProvider { + +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleCmsSwtTheme.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleCmsSwtTheme.java new file mode 100644 index 000000000..b9b2751a7 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleCmsSwtTheme.java @@ -0,0 +1,93 @@ +package org.argeo.cms.swt.osgi; + +import java.io.IOException; +import java.io.InputStream; +import java.util.HashMap; +import java.util.Map; + +import org.argeo.cms.osgi.BundleCmsTheme; +import org.argeo.cms.swt.CmsSwtTheme; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.widgets.Display; + +/** Centralises some generic {@link CmsSwtTheme} patterns. */ +public class BundleCmsSwtTheme extends BundleCmsTheme implements CmsSwtTheme { + private Map imageCache = new HashMap<>(); + + private Map> iconPaths = new HashMap<>(); + + public Image getImage(String path) { + if (!imageCache.containsKey(path)) { + try (InputStream in = getResourceAsStream(path)) { + if (in == null) + return null; + ImageData imageData = new ImageData(in); + imageCache.put(path, imageData); + } catch (IOException e) { + throw new IllegalStateException(e); + } + } + ImageData imageData = imageCache.get(path); + Image image = new Image(Display.getCurrent(), imageData); + return image; + } + + /** + * And icon with this file name (without the extension), with a best effort to + * find the appropriate size, or null if not found. + * + * @param name An icon file name without path and extension. + * @param preferredSize the preferred size, if null, + * {@link #getDefaultIconSize()} will be tried. + */ + public Image getIcon(String name, Integer preferredSize) { + if (preferredSize == null) + preferredSize = getDefaultIconSize(); + Map subCache; + if (!iconPaths.containsKey(name)) + subCache = new HashMap<>(); + else + subCache = iconPaths.get(name); + Image image = null; + if (!subCache.containsKey(preferredSize)) { + Image bestMatchSoFar = null; + paths: for (String p : getImagesPaths()) { + int lastSlash = p.lastIndexOf('/'); + String fileName = p; + if (lastSlash >= 0) + fileName = p.substring(lastSlash + 1); + int lastDot = fileName.lastIndexOf('.'); + if (lastDot >= 0) + fileName = fileName.substring(0, lastDot); + if (fileName.equals(name)) {// matched + Image img = getImage(p); + int width = img.getBounds().width; + if (width == preferredSize) {// perfect match + subCache.put(preferredSize, p); + image = img; + break paths; + } + if (bestMatchSoFar == null) { + bestMatchSoFar = img; + } else { + if (Math.abs(width - preferredSize) < Math + .abs(bestMatchSoFar.getBounds().width - preferredSize)) + bestMatchSoFar = img; + } + } + } + + if (image == null) + image = bestMatchSoFar; + } else { + image = getImage(subCache.get(preferredSize)); + } + + if (image != null && !iconPaths.containsKey(name)) + iconPaths.put(name, subCache); + + return image; + } + +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/PickUpUserDialog.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/PickUpUserDialog.java new file mode 100644 index 000000000..ed1bfd868 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/PickUpUserDialog.java @@ -0,0 +1,246 @@ +package org.argeo.cms.swt.useradmin; + +import java.util.ArrayList; +import java.util.List; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.eclipse.ui.ColumnDefinition; +import org.argeo.eclipse.ui.EclipseUiException; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.argeo.eclipse.ui.parts.LdifUsersTable; +import org.argeo.util.naming.LdapAttrs; +import org.argeo.util.naming.LdapObjs; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.dialogs.TrayDialog; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IDoubleClickListener; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.layout.FillLayout; +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.Shell; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.service.useradmin.Group; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.User; +import org.osgi.service.useradmin.UserAdmin; + +/** Dialog with a user (or group) list to pick up one */ +public class PickUpUserDialog extends TrayDialog { + private static final long serialVersionUID = -1420106871173920369L; + + // Business objects + private final UserAdmin userAdmin; + private User selectedUser; + + // this page widgets and UI objects + private String title; + private LdifUsersTable userTableViewerCmp; + private TableViewer userViewer; + private List columnDefs = new ArrayList(); + + /** + * A dialog to pick up a group or a user, showing a table with default + * columns + */ + public PickUpUserDialog(Shell parentShell, String title, UserAdmin userAdmin) { + super(parentShell); + this.title = title; + this.userAdmin = userAdmin; + + columnDefs.add(new ColumnDefinition(new UserLP(UserLP.COL_ICON), "", + 24, 24)); + columnDefs.add(new ColumnDefinition( + new UserLP(UserLP.COL_DISPLAY_NAME), "Common Name", 150, 100)); + columnDefs.add(new ColumnDefinition(new UserLP(UserLP.COL_DOMAIN), + "Domain", 100, 120)); + columnDefs.add(new ColumnDefinition(new UserLP(UserLP.COL_DN), + "Distinguished Name", 300, 100)); + } + + /** A dialog to pick up a group or a user */ + public PickUpUserDialog(Shell parentShell, String title, + UserAdmin userAdmin, List columnDefs) { + super(parentShell); + this.title = title; + this.userAdmin = userAdmin; + this.columnDefs = columnDefs; + } + + @Override + protected void okPressed() { + if (getSelected() == null) + MessageDialog.openError(getShell(), "No user chosen", + "Please, choose a user or press Cancel."); + else + super.okPressed(); + } + + protected Control createDialogArea(Composite parent) { + Composite dialogArea = (Composite) super.createDialogArea(parent); + dialogArea.setLayout(new FillLayout()); + + Composite bodyCmp = new Composite(dialogArea, SWT.NO_FOCUS); + bodyCmp.setLayout(new GridLayout()); + + // Create and configure the table + userTableViewerCmp = new MyUserTableViewer(bodyCmp, SWT.MULTI + | SWT.H_SCROLL | SWT.V_SCROLL); + + userTableViewerCmp.setColumnDefinitions(columnDefs); + userTableViewerCmp.populateWithStaticFilters(false, false); + GridData gd = EclipseUiUtils.fillAll(); + gd.minimumHeight = 300; + userTableViewerCmp.setLayoutData(gd); + userTableViewerCmp.refresh(); + + // Controllers + userViewer = userTableViewerCmp.getTableViewer(); + userViewer.addDoubleClickListener(new MyDoubleClickListener()); + userViewer + .addSelectionChangedListener(new MySelectionChangedListener()); + + parent.pack(); + return dialogArea; + } + + public User getSelected() { + if (selectedUser == null) + return null; + else + return selectedUser; + } + + protected void configureShell(Shell shell) { + super.configureShell(shell); + shell.setText(title); + } + + class MyDoubleClickListener implements IDoubleClickListener { + public void doubleClick(DoubleClickEvent evt) { + if (evt.getSelection().isEmpty()) + return; + + Object obj = ((IStructuredSelection) evt.getSelection()) + .getFirstElement(); + if (obj instanceof User) { + selectedUser = (User) obj; + okPressed(); + } + } + } + + class MySelectionChangedListener implements ISelectionChangedListener { + @Override + public void selectionChanged(SelectionChangedEvent event) { + if (event.getSelection().isEmpty()) { + selectedUser = null; + return; + } + Object obj = ((IStructuredSelection) event.getSelection()) + .getFirstElement(); + if (obj instanceof Group) { + selectedUser = (Group) obj; + } + } + } + + private class MyUserTableViewer extends LdifUsersTable { + private static final long serialVersionUID = 8467999509931900367L; + + private final String[] knownProps = { LdapAttrs.uid.name(), + LdapAttrs.cn.name(), LdapAttrs.DN }; + + private Button showSystemRoleBtn; + private Button showUserBtn; + + public MyUserTableViewer(Composite parent, int style) { + super(parent, style); + } + + protected void populateStaticFilters(Composite staticFilterCmp) { + staticFilterCmp.setLayout(new GridLayout()); + showSystemRoleBtn = new Button(staticFilterCmp, SWT.CHECK); + showSystemRoleBtn.setText("Show system roles "); + + showUserBtn = new Button(staticFilterCmp, SWT.CHECK); + showUserBtn.setText("Show users "); + + SelectionListener sl = new SelectionAdapter() { + private static final long serialVersionUID = -7033424592697691676L; + + @Override + public void widgetSelected(SelectionEvent e) { + refresh(); + } + }; + + showSystemRoleBtn.addSelectionListener(sl); + showUserBtn.addSelectionListener(sl); + } + + @Override + protected List listFilteredElements(String filter) { + Role[] roles; + try { + StringBuilder builder = new StringBuilder(); + + StringBuilder filterBuilder = new StringBuilder(); + if (notNull(filter)) + for (String prop : knownProps) { + filterBuilder.append("("); + filterBuilder.append(prop); + filterBuilder.append("=*"); + filterBuilder.append(filter); + filterBuilder.append("*)"); + } + + String typeStr = "(" + LdapAttrs.objectClass.name() + "=" + + LdapObjs.groupOfNames.name() + ")"; + if ((showUserBtn.getSelection())) + typeStr = "(|(" + LdapAttrs.objectClass.name() + "=" + + LdapObjs.inetOrgPerson.name() + ")" + typeStr + + ")"; + + if (!showSystemRoleBtn.getSelection()) + typeStr = "(& " + typeStr + "(!(" + LdapAttrs.DN + "=*" + + CmsConstants.ROLES_BASEDN + ")))"; + + if (filterBuilder.length() > 1) { + builder.append("(&" + typeStr); + builder.append("(|"); + builder.append(filterBuilder.toString()); + builder.append("))"); + } else { + builder.append(typeStr); + } + roles = userAdmin.getRoles(builder.toString()); + } catch (InvalidSyntaxException e) { + throw new EclipseUiException( + "Unable to get roles with filter: " + filter, e); + } + List users = new ArrayList(); + for (Role role : roles) + if (!users.contains(role)) + users.add((User) role); + return users; + } + } + + private boolean notNull(String string) { + if (string == null) + return false; + else + return !"".equals(string.trim()); + } +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/UserLP.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/UserLP.java new file mode 100644 index 000000000..d1c90a43f --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/UserLP.java @@ -0,0 +1,76 @@ +package org.argeo.cms.swt.useradmin; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.cms.auth.UserAdminUtils; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Display; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.User; + +/** Centralize label providers for the group table */ +class UserLP extends ColumnLabelProvider { + private static final long serialVersionUID = -4645930210988368571L; + + final static String COL_ICON = "colID.icon"; + final static String COL_DN = "colID.dn"; + final static String COL_DISPLAY_NAME = "colID.displayName"; + final static String COL_DOMAIN = "colID.domain"; + + final String currType; + + // private Font italic; + private Font bold; + + UserLP(String colId) { + this.currType = colId; + } + + @Override + public Font getFont(Object element) { + // Current user as bold + if (UserAdminUtils.isCurrentUser(((User) element))) { + if (bold == null) + bold = JFaceResources.getFontRegistry().defaultFontDescriptor().setStyle(SWT.BOLD) + .createFont(Display.getCurrent()); + return bold; + } + return null; + } + + @Override + public Image getImage(Object element) { + if (COL_ICON.equals(currType)) { + User user = (User) element; + String dn = user.getName(); + if (dn.endsWith(CmsConstants.ROLES_BASEDN)) + return UsersImages.ICON_ROLE; + else if (user.getType() == Role.GROUP) + return UsersImages.ICON_GROUP; + else + return UsersImages.ICON_USER; + } else + return null; + } + + @Override + public String getText(Object element) { + User user = (User) element; + return getText(user); + + } + + public String getText(User user) { + if (COL_DN.equals(currType)) + return user.getName(); + else if (COL_DISPLAY_NAME.equals(currType)) + return UserAdminUtils.getCommonName(user); + else if (COL_DOMAIN.equals(currType)) + return UserAdminUtils.getDomainName(user); + else + return ""; + } +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/UsersImages.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/UsersImages.java new file mode 100644 index 000000000..21fc5afba --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/UsersImages.java @@ -0,0 +1,14 @@ +package org.argeo.cms.swt.useradmin; + +import org.argeo.cms.ui.theme.CmsImages; +import org.eclipse.swt.graphics.Image; + +/** Specific users icons. */ +public class UsersImages { + private final static String PREFIX = "icons/"; + + public final static Image ICON_USER = CmsImages.createImg(PREFIX + "person.png"); + public final static Image ICON_GROUP = CmsImages.createImg(PREFIX + "group.png"); + public final static Image ICON_ROLE = CmsImages.createImg(PREFIX + "role.gif"); + public final static Image ICON_CHANGE_PASSWORD = CmsImages.createImg(PREFIX + "security.gif"); +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/package-info.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/package-info.java new file mode 100644 index 000000000..3597bfc57 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/package-info.java @@ -0,0 +1,2 @@ +/** SWT/JFace users management components. */ +package org.argeo.cms.swt.useradmin; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/ui/theme/CmsImages.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/ui/theme/CmsImages.java new file mode 100644 index 000000000..1c4d79eee --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/ui/theme/CmsImages.java @@ -0,0 +1,49 @@ +package org.argeo.cms.ui.theme; + +import java.net.URL; + +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Display; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; + +public class CmsImages { + private static BundleContext themeBc = FrameworkUtil.getBundle(CmsImages.class).getBundleContext(); + + final public static String ICONS_BASE = "icons/"; + final public static String TYPES_BASE = ICONS_BASE + "types/"; + final public static String ACTIONS_BASE = ICONS_BASE + "actions/"; + + public static Image createIcon(String name) { + return createImg(CmsImages.ICONS_BASE + name); + } + + public static Image createAction(String name) { + return createImg(CmsImages.ACTIONS_BASE + name); + } + + public static Image createType(String name) { + return createImg(CmsImages.TYPES_BASE + name); + } + + public static Image createImg(String name) { + return CmsImages.createDesc(name).createImage(Display.getDefault()); + } + + public static ImageDescriptor createDesc(String name) { + return createDesc(themeBc, name); + } + + public static ImageDescriptor createDesc(BundleContext bc, String name) { + URL url = bc.getBundle().getResource(name); + if (url == null) + return ImageDescriptor.getMissingImageDescriptor(); + return ImageDescriptor.createFromURL(url); + } + + public static Image createImg(BundleContext bc, String name) { + return createDesc(bc, name).createImage(Display.getDefault()); + } + +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/cms/ui/theme/package-info.java b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/ui/theme/package-info.java new file mode 100644 index 000000000..7d3a260f3 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/cms/ui/theme/package-info.java @@ -0,0 +1,2 @@ +/** Argeo CMS core theme images. */ +package org.argeo.cms.ui.theme; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/AbstractTreeContentProvider.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/AbstractTreeContentProvider.java new file mode 100644 index 000000000..c882eb766 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/AbstractTreeContentProvider.java @@ -0,0 +1,42 @@ +package org.argeo.eclipse.ui; + +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.Viewer; + +/** + * Tree content provider dealing with tree objects and providing reasonable + * defaults. + */ +public abstract class AbstractTreeContentProvider implements + ITreeContentProvider { + private static final long serialVersionUID = 8246126401957763868L; + + /** Does nothing */ + public void dispose() { + } + + /** Does nothing */ + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + + public Object[] getChildren(Object element) { + if (element instanceof TreeParent) { + return ((TreeParent) element).getChildren(); + } + return new Object[0]; + } + + public Object getParent(Object element) { + if (element instanceof TreeParent) { + return ((TreeParent) element).getParent(); + } + return null; + } + + public boolean hasChildren(Object element) { + if (element instanceof TreeParent) { + return ((TreeParent) element).hasChildren(); + } + return false; + } +} \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnDefinition.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnDefinition.java new file mode 100644 index 000000000..a38552c07 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnDefinition.java @@ -0,0 +1,68 @@ +package org.argeo.eclipse.ui; + +import org.eclipse.jface.viewers.ColumnLabelProvider; + +/** + * Wraps the definition of a column to be used in the various JFace viewers + * (typically tree and table). It enables definition of generic viewers which + * column can be then defined externally. Also used to generate export. + */ +public class ColumnDefinition { + private ColumnLabelProvider labelProvider; + private String label; + private int weight = 0; + private int minWidth = 120; + + public ColumnDefinition(ColumnLabelProvider labelProvider, String label) { + this.labelProvider = labelProvider; + this.label = label; + } + + public ColumnDefinition(ColumnLabelProvider labelProvider, String label, + int weight) { + this.labelProvider = labelProvider; + this.label = label; + this.weight = weight; + this.minWidth = weight; + } + + public ColumnDefinition(ColumnLabelProvider labelProvider, String label, + int weight, int minimumWidth) { + this.labelProvider = labelProvider; + this.label = label; + this.weight = weight; + this.minWidth = minimumWidth; + } + + public ColumnLabelProvider getLabelProvider() { + return labelProvider; + } + + public void setLabelProvider(ColumnLabelProvider labelProvider) { + this.labelProvider = labelProvider; + } + + public String getLabel() { + return label; + } + + public void setLabel(String label) { + this.label = label; + } + + public int getWeight() { + return weight; + } + + public void setWeight(int weight) { + this.weight = weight; + } + + public int getMinWidth() { + return minWidth; + } + + public void setMinWidth(int minWidth) { + this.minWidth = minWidth; + } +} \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnViewerComparator.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnViewerComparator.java new file mode 100644 index 000000000..9430a2083 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnViewerComparator.java @@ -0,0 +1,81 @@ +package org.argeo.eclipse.ui; + +import org.eclipse.jface.viewers.ColumnViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerComparator; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; + +/** Generic column viewer sorter */ +public class ColumnViewerComparator extends ViewerComparator { + private static final long serialVersionUID = -2266218906355859909L; + + public static final int ASC = 1; + + public static final int NONE = 0; + + public static final int DESC = -1; + + private int direction = 0; + + private TableViewerColumn column; + + private ColumnViewer viewer; + + public ColumnViewerComparator(TableViewerColumn column) { + super(null); + this.column = column; + this.viewer = column.getViewer(); + this.column.getColumn().addSelectionListener(new SelectionAdapter() { + private static final long serialVersionUID = 7586796298965472189L; + + public void widgetSelected(SelectionEvent e) { + if (ColumnViewerComparator.this.viewer.getComparator() != null) { + if (ColumnViewerComparator.this.viewer.getComparator() == ColumnViewerComparator.this) { + int tdirection = ColumnViewerComparator.this.direction; + + if (tdirection == ASC) { + setSortDirection(DESC); + } else if (tdirection == DESC) { + setSortDirection(NONE); + } + } else { + setSortDirection(ASC); + } + } else { + setSortDirection(ASC); + } + } + }); + } + + private void setSortDirection(int direction) { + if (direction == NONE) { + column.getColumn().getParent().setSortColumn(null); + column.getColumn().getParent().setSortDirection(SWT.NONE); + viewer.setComparator(null); + } else { + column.getColumn().getParent().setSortColumn(column.getColumn()); + this.direction = direction; + + if (direction == ASC) { + column.getColumn().getParent().setSortDirection(SWT.DOWN); + } else { + column.getColumn().getParent().setSortDirection(SWT.UP); + } + + if (viewer.getComparator() == this) { + viewer.refresh(); + } else { + viewer.setComparator(this); + } + + } + } + + public int compare(Viewer viewer, Object e1, Object e2) { + return direction * super.compare(viewer, e1, e2); + } +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiException.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiException.java new file mode 100644 index 000000000..37a36e859 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiException.java @@ -0,0 +1,15 @@ +package org.argeo.eclipse.ui; + +/** CMS specific exceptions. */ +public class EclipseUiException extends RuntimeException { + private static final long serialVersionUID = -5341764743356771313L; + + public EclipseUiException(String message) { + super(message); + } + + public EclipseUiException(String message, Throwable e) { + super(message, e); + } + +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiUtils.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiUtils.java new file mode 100644 index 000000000..95b45fed6 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiUtils.java @@ -0,0 +1,195 @@ +package org.argeo.eclipse.ui; + +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyListener; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.layout.FormAttachment; +import org.eclipse.swt.layout.FormData; +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.Label; +import org.eclipse.swt.widgets.Text; + +/** Utilities to simplify UI development. */ +public class EclipseUiUtils { + + /** Dispose all children of a Composite */ + public static void clear(Composite composite) { + for (Control child : composite.getChildren()) + child.dispose(); + } + + /** + * Enables efficient call to the layout method of a composite, refreshing only + * some of the children controls. + */ + public static void layout(Composite parent, Control... toUpdateControls) { + parent.layout(toUpdateControls); + } + + // + // FONTS + // + /** Shortcut to retrieve default italic font from display */ + public static Font getItalicFont(Composite parent) { + return JFaceResources.getFontRegistry().defaultFontDescriptor().setStyle(SWT.ITALIC) + .createFont(parent.getDisplay()); + } + + /** Shortcut to retrieve default bold font from display */ + public static Font getBoldFont(Composite parent) { + return JFaceResources.getFontRegistry().defaultFontDescriptor().setStyle(SWT.BOLD) + .createFont(parent.getDisplay()); + } + + /** Shortcut to retrieve default bold italic font from display */ + public static Font getBoldItalicFont(Composite parent) { + return JFaceResources.getFontRegistry().defaultFontDescriptor().setStyle(SWT.BOLD | SWT.ITALIC) + .createFont(parent.getDisplay()); + } + + // + // Simplify grid layouts management + // + public static GridLayout noSpaceGridLayout() { + return noSpaceGridLayout(new GridLayout()); + } + + public static GridLayout noSpaceGridLayout(int columns) { + return noSpaceGridLayout(new GridLayout(columns, false)); + } + + public static GridLayout noSpaceGridLayout(GridLayout layout) { + layout.horizontalSpacing = 0; + layout.verticalSpacing = 0; + layout.marginWidth = 0; + layout.marginHeight = 0; + return layout; + } + + public static GridData fillWidth() { + return grabWidth(SWT.FILL, SWT.FILL); + } + + public static GridData fillWidth(int colSpan) { + GridData gd = grabWidth(SWT.FILL, SWT.FILL); + gd.horizontalSpan = colSpan; + return gd; + } + + public static GridData fillAll() { + return new GridData(SWT.FILL, SWT.FILL, true, true); + } + + public static GridData fillAll(int colSpan, int rowSpan) { + return new GridData(SWT.FILL, SWT.FILL, true, true, colSpan, rowSpan); + } + + public static GridData grabWidth(int horizontalAlignment, int verticalAlignment) { + return new GridData(horizontalAlignment, horizontalAlignment, true, false); + } + + // + // Simplify Form layout management + // + + /** + * Creates a basic form data that is attached to the 4 corners of the parent + * composite + */ + public static FormData fillFormData() { + FormData formData = new FormData(); + formData.top = new FormAttachment(0, 0); + formData.left = new FormAttachment(0, 0); + formData.right = new FormAttachment(100, 0); + formData.bottom = new FormAttachment(100, 0); + return formData; + } + + /** + * Create a label and a text field for a grid layout, the text field grabbing + * excess horizontal + * + * @param parent + * the parent composite + * @param label + * the label to display + * @param modifyListener + * a {@link ModifyListener} to listen on events on the text, can be + * null + * @return the created text + * + */ + // FIXME why was this deprecated. + // * @ deprecated use { @ link #createGridLT(Composite, String)} instead + // @ Deprecated + public static Text createGridLT(Composite parent, String label, ModifyListener modifyListener) { + Label lbl = new Label(parent, SWT.LEAD); + lbl.setText(label); + lbl.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false)); + Text txt = new Text(parent, SWT.LEAD | SWT.BORDER); + txt.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); + if (modifyListener != null) + txt.addModifyListener(modifyListener); + return txt; + } + + /** + * Create a label and a text field for a grid layout, the text field grabbing + * excess horizontal + */ + public static Text createGridLT(Composite parent, String label) { + return createGridLT(parent, label, null); + } + + /** + * Creates one label and a text field not editable with background colour of the + * parent (like a label but with selectable text) + */ + public static Text createGridLL(Composite parent, String label, String text) { + Text txt = createGridLT(parent, label); + txt.setText(text); + txt.setEditable(false); + txt.setBackground(parent.getBackground()); + return txt; + } + + /** + * Create a label and a text field with password display for a grid layout, the + * text field grabbing excess horizontal + */ + public static Text createGridLP(Composite parent, String label) { + return createGridLP(parent, label, null); + } + + /** + * Create a label and a text field with password display for a grid layout, the + * text field grabbing excess horizontal. The given modify listener will be + * added to the newly created text field if not null. + */ + public static Text createGridLP(Composite parent, String label, ModifyListener modifyListener) { + Label lbl = new Label(parent, SWT.LEAD); + lbl.setText(label); + lbl.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false)); + Text txt = new Text(parent, SWT.LEAD | SWT.BORDER | SWT.PASSWORD); + txt.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); + if (modifyListener != null) + txt.addModifyListener(modifyListener); + return txt; + } + + // MISCELLANEOUS + + /** Simply checks if a string is not null nor empty */ + public static boolean notEmpty(String stringToTest) { + return !(stringToTest == null || "".equals(stringToTest.trim())); + } + + /** Simply checks if a string is null or empty */ + public static boolean isEmpty(String stringToTest) { + return stringToTest == null || "".equals(stringToTest.trim()); + } +} \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/FileProvider.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/FileProvider.java new file mode 100644 index 000000000..e82505df5 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/FileProvider.java @@ -0,0 +1,16 @@ +package org.argeo.eclipse.ui; + +import java.io.InputStream; + +/** + * Used for file download : subclasses must implement model specific methods to + * get a byte array representing a file given is ID. + */ +@Deprecated +public interface FileProvider { + + public byte[] getByteArrayFileFromId(String fileId); + + public InputStream getInputStreamFromFileId(String fileId); + +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/GenericTableComparator.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/GenericTableComparator.java new file mode 100644 index 000000000..e1d8b05ea --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/GenericTableComparator.java @@ -0,0 +1,39 @@ +package org.argeo.eclipse.ui; + +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerComparator; + +public abstract class GenericTableComparator extends ViewerComparator { + private static final long serialVersionUID = -1175894935075325810L; + protected int propertyIndex; + public static final int ASCENDING = 0, DESCENDING = 1; + protected int direction = DESCENDING; + + /** + * Creates an instance of a sorter for TableViewer. + * + * @param defaultColumnIndex + * the default sorter column + */ + + public GenericTableComparator(int defaultColumnIndex, int direction) { + propertyIndex = defaultColumnIndex; + this.direction = direction; + } + + public void setColumn(int column) { + if (column == this.propertyIndex) { + // Same column as last sort; toggle the direction + direction = 1 - direction; + } else { + // New column; do a descending sort + this.propertyIndex = column; + direction = DESCENDING; + } + } + + /** + * Must be Overriden in each view. + */ + public abstract int compare(Viewer viewer, Object e1, Object e2); +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/IListProvider.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/IListProvider.java new file mode 100644 index 000000000..ac7b2d8fb --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/IListProvider.java @@ -0,0 +1,20 @@ +package org.argeo.eclipse.ui; + +import java.util.List; + +/** + * Views and editors can implement this interface so that one of the list that + * is displayed in the part (For instance in a Table or a Tree Viewer) can be + * rebuilt externally. Typically to generate csv or calc extract. + */ +public interface IListProvider { + /** + * Returns an array of current and relevant elements + */ + public Object[] getElements(String extractId); + + /** + * Returns the column definition for passed ID + */ + public List getColumnDefinition(String extractId); +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/TreeParent.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/TreeParent.java new file mode 100644 index 000000000..cf3c15795 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/TreeParent.java @@ -0,0 +1,133 @@ +package org.argeo.eclipse.ui; + +import java.util.ArrayList; +import java.util.List; + +/** Parent / children semantic to be used for simple UI Tree structure */ +public class TreeParent { + private String name; + private TreeParent parent; + + private List children; + + /** + * Unique id within the context of a tree display. If set, equals() and + * hashCode() methods will be based on it + */ + private String path = null; + + /** False until at least one child has been added, then true until cleared */ + private boolean loaded = false; + + public TreeParent(String name) { + this.name = name; + children = new ArrayList(); + } + + public synchronized void addChild(Object child) { + loaded = true; + children.add(child); + if (child instanceof TreeParent) + ((TreeParent) child).setParent(this); + } + + /** + * Remove this child. The child is disposed. + */ + public synchronized void removeChild(Object child) { + children.remove(child); + if (child instanceof TreeParent) { + ((TreeParent) child).dispose(); + } + } + + public synchronized void clearChildren() { + for (Object obj : children) { + if (obj instanceof TreeParent) + ((TreeParent) obj).dispose(); + } + loaded = false; + children.clear(); + } + + /** + * If overridden, super.dispose() must be called, typically + * after custom cleaning. + */ + public synchronized void dispose() { + clearChildren(); + parent = null; + children = null; + } + + public synchronized Object[] getChildren() { + return children.toArray(new Object[children.size()]); + } + + @SuppressWarnings("unchecked") + public synchronized List getChildrenOfType(Class clss) { + List lst = new ArrayList(); + for (Object obj : children) { + if (clss.isAssignableFrom(obj.getClass())) + lst.add((T) obj); + } + return lst; + } + + public synchronized boolean hasChildren() { + return children.size() > 0; + } + + public Object getChildByName(String name) { + for (Object child : children) { + if (child.toString().equals(name)) + return child; + } + return null; + } + + public synchronized Boolean isLoaded() { + return loaded; + } + + public String getName() { + return name; + } + + public void setParent(TreeParent parent) { + this.parent = parent; + if (parent != null && parent.path != null) + this.path = parent.path + '/' + name; + else + this.path = '/' + name; + } + + public TreeParent getParent() { + return parent; + } + + public String toString() { + return getName(); + } + + public int compareTo(TreeParent o) { + return name.compareTo(o.name); + } + + @Override + public int hashCode() { + if (path != null) + return path.hashCode(); + else + return name.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (path != null && obj instanceof TreeParent) + return path.equals(((TreeParent) obj).path); + else + return name.equals(obj.toString()); + } + +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/ErrorFeedback.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/ErrorFeedback.java new file mode 100644 index 000000000..a388e745e --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/ErrorFeedback.java @@ -0,0 +1,106 @@ +package org.argeo.eclipse.ui.dialogs; + +import java.io.PrintWriter; +import java.io.StringWriter; + +import org.argeo.api.cms.CmsLog; +import org.eclipse.jface.dialogs.IMessageProvider; +import org.eclipse.jface.dialogs.TitleAreaDialog; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Point; +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.Display; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +/** + * Generic error dialog to be used in try/catch blocks. + * + * @deprecated Use CMS dialogs instead. + */ +@Deprecated +public class ErrorFeedback extends TitleAreaDialog { + private static final long serialVersionUID = -8918084784628179044L; + + private final static CmsLog log = CmsLog.getLog(ErrorFeedback.class); + + private final String message; + private final Throwable exception; + + public static void show(String message, Throwable e) { + // rethrow ThreaDeath in order to make sure that RAP will properly clean + // up the UI thread + if (e instanceof ThreadDeath) + throw (ThreadDeath) e; + + new ErrorFeedback(newShell(), message, e).open(); + } + + public static void show(String message) { + new ErrorFeedback(newShell(), message, null).open(); + } + + private static Shell newShell() { + return new Shell(getDisplay(), SWT.NO_TRIM); + } + + /** Tries to find a display */ + private static Display getDisplay() { + try { + Display display = Display.getCurrent(); + if (display != null) + return display; + else + return Display.getDefault(); + } catch (Exception e) { + return Display.getCurrent(); + } + } + + public ErrorFeedback(Shell parentShell, String message, Throwable e) { + super(parentShell); + setShellStyle(SWT.NO_TRIM); + this.message = message; + this.exception = e; + log.error(message, e); + } + + protected Point getInitialSize() { + if (exception != null) + return new Point(800, 600); + else + return new Point(400, 300); + } + + @Override + protected Control createDialogArea(Composite parent) { + Composite dialogarea = (Composite) super.createDialogArea(parent); + dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + Composite composite = new Composite(dialogarea, SWT.NONE); + composite.setLayout(new GridLayout(2, false)); + composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + setMessage(message != null ? message + (exception != null ? ": " + exception.getMessage() : "") + : exception != null ? exception.getMessage() : "Unkown Error", IMessageProvider.ERROR); + + if (exception != null) { + Text stack = new Text(composite, SWT.MULTI | SWT.LEAD | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL); + stack.setEditable(false); + stack.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + StringWriter sw = new StringWriter(); + exception.printStackTrace(new PrintWriter(sw)); + stack.setText(sw.toString()); + } + + parent.pack(); + return composite; + } + + protected void configureShell(Shell shell) { + super.configureShell(shell); + shell.setText("Error"); + } +} \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/FeedbackDialog.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/FeedbackDialog.java new file mode 100644 index 000000000..f2715bc05 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/FeedbackDialog.java @@ -0,0 +1,141 @@ +package org.argeo.eclipse.ui.dialogs; + +import java.io.PrintWriter; +import java.io.StringWriter; + +import org.argeo.api.cms.CmsLog; +import org.argeo.eclipse.ui.EclipseUiException; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ShellAdapter; +import org.eclipse.swt.events.ShellEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +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.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +/** + * Generic lightweight dialog, not based on JFace. + * + * @deprecated Use CMS dialogs instead. + */ +@Deprecated +public class FeedbackDialog extends LightweightDialog { + private final static CmsLog log = CmsLog.getLog(FeedbackDialog.class); + + private String message; + private Throwable exception; + +// private Shell parentShell; + private Shell shell; + + public static void show(String message, Throwable e) { + // rethrow ThreaDeath in order to make sure that RAP will properly clean + // up the UI thread + if (e instanceof ThreadDeath) + throw (ThreadDeath) e; + + new FeedbackDialog(getDisplay().getActiveShell(), message, e).open(); + } + + public static void show(String message) { + new FeedbackDialog(getDisplay().getActiveShell(), message, null).open(); + } + + /** Tries to find a display */ + private static Display getDisplay() { + try { + Display display = Display.getCurrent(); + if (display != null) + return display; + else + return Display.getDefault(); + } catch (Exception e) { + return Display.getCurrent(); + } + } + + public FeedbackDialog(Shell parentShell, String message, Throwable e) { + super(parentShell); + this.message = message; + this.exception = e; + log.error(message, e); + } + + public int open() { + if (shell != null) + throw new EclipseUiException("There is already a shell"); + shell = new Shell(getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP); + shell.setLayout(new GridLayout()); + // shell.setText("Error"); + shell.setSize(getInitialSize()); + createDialogArea(shell); + // shell.pack(); + // shell.layout(); + + Rectangle shellBounds = Display.getCurrent().getBounds();// RAP + Point dialogSize = shell.getSize(); + int x = shellBounds.x + (shellBounds.width - dialogSize.x) / 2; + int y = shellBounds.y + (shellBounds.height - dialogSize.y) / 2; + shell.setLocation(x, y); + + shell.addShellListener(new ShellAdapter() { + private static final long serialVersionUID = -2701270481953688763L; + + @Override + public void shellDeactivated(ShellEvent e) { + closeShell(); + } + }); + + shell.open(); + return OK; + } + + protected void closeShell() { + shell.close(); + shell.dispose(); + shell = null; + } + + protected Point getInitialSize() { + // if (exception != null) + // return new Point(800, 600); + // else + return new Point(400, 300); + } + + protected Control createDialogArea(Composite parent) { + Composite dialogarea = new Composite(parent, SWT.NONE); + dialogarea.setLayout(new GridLayout()); + // Composite dialogarea = (Composite) super.createDialogArea(parent); + dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + Label messageLbl = new Label(dialogarea, SWT.NONE); + if (message != null) + messageLbl.setText(message); + else if (exception != null) + messageLbl.setText(exception.getLocalizedMessage()); + + Composite composite = new Composite(dialogarea, SWT.NONE); + composite.setLayout(new GridLayout(2, false)); + composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + + if (exception != null) { + Text stack = new Text(composite, SWT.MULTI | SWT.LEAD | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL); + stack.setEditable(false); + stack.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + StringWriter sw = new StringWriter(); + exception.printStackTrace(new PrintWriter(sw)); + stack.setText(sw.toString()); + } + + // parent.pack(); + return composite; + } +} \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/LightweightDialog.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/LightweightDialog.java new file mode 100644 index 000000000..615e1417a --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/LightweightDialog.java @@ -0,0 +1,256 @@ +package org.argeo.eclipse.ui.dialogs; + +import org.argeo.api.cms.CmsLog; +import org.argeo.eclipse.ui.EclipseUiException; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.ShellAdapter; +import org.eclipse.swt.events.ShellEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +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.Display; +import org.eclipse.swt.widgets.Shell; + +/** Generic lightweight dialog, not based on JFace. */ +@Deprecated +public class LightweightDialog { + private final static CmsLog log = CmsLog.getLog(LightweightDialog.class); + + // must be the same value as org.eclipse.jface.window.Window#OK + public final static int OK = 0; + // must be the same value as org.eclipse.jface.window.Window#CANCEL + public final static int CANCEL = 1; + + private Shell parentShell; + private Shell backgroundShell; + private Shell foregoundShell; + + private Integer returnCode = null; + private boolean block = true; + + private String title; + + /** Tries to find a display */ + private static Display getDisplay() { + try { + Display display = Display.getCurrent(); + if (display != null) + return display; + else + return Display.getDefault(); + } catch (Exception e) { + return Display.getCurrent(); + } + } + + public LightweightDialog(Shell parentShell) { + this.parentShell = parentShell; + } + + public int open() { + if (foregoundShell != null) + throw new EclipseUiException("There is already a shell"); + backgroundShell = new Shell(parentShell, SWT.ON_TOP); + backgroundShell.setFullScreen(true); + // if (parentShell != null) { + // backgroundShell.setBounds(parentShell.getBounds()); + // } else + // backgroundShell.setMaximized(true); + backgroundShell.setAlpha(128); + backgroundShell.setBackground(getDisplay().getSystemColor(SWT.COLOR_BLACK)); + foregoundShell = new Shell(backgroundShell, SWT.NO_TRIM | SWT.ON_TOP); + if (title != null) + setTitle(title); + foregoundShell.setLayout(new GridLayout()); + foregoundShell.setSize(getInitialSize()); + createDialogArea(foregoundShell); + // shell.pack(); + // shell.layout(); + + Rectangle shellBounds = parentShell != null ? parentShell.getBounds() : Display.getCurrent().getBounds();// RAP + Point dialogSize = foregoundShell.getSize(); + int x = shellBounds.x + (shellBounds.width - dialogSize.x) / 2; + int y = shellBounds.y + (shellBounds.height - dialogSize.y) / 2; + foregoundShell.setLocation(x, y); + + foregoundShell.addShellListener(new ShellAdapter() { + private static final long serialVersionUID = -2701270481953688763L; + + @Override + public void shellDeactivated(ShellEvent e) { + if (hasChildShells()) + return; + if (returnCode == null)// not yet closed + closeShell(CANCEL); + } + + @Override + public void shellClosed(ShellEvent e) { + notifyClose(); + } + + }); + + backgroundShell.open(); + foregoundShell.open(); + // after the foreground shell has been opened + backgroundShell.addFocusListener(new FocusListener() { + private static final long serialVersionUID = 3137408447474661070L; + + @Override + public void focusLost(FocusEvent event) { + } + + @Override + public void focusGained(FocusEvent event) { + if (hasChildShells()) + return; + if (returnCode == null)// not yet closed + closeShell(CANCEL); + } + }); + + if (block) { + block(); + } + if (returnCode == null) + returnCode = OK; + return returnCode; + } + + public void block() { + try { + runEventLoop(foregoundShell); + } catch (ThreadDeath t) { + returnCode = CANCEL; + if (log.isTraceEnabled()) + log.error("Thread death, canceling dialog", t); + } catch (Throwable t) { + returnCode = CANCEL; + log.error("Cannot open blocking lightweight dialog", t); + } + } + + private boolean hasChildShells() { + if (foregoundShell == null) + return false; + return foregoundShell.getShells().length != 0; + } + + // public synchronized int openAndWait() { + // open(); + // while (returnCode == null) + // try { + // wait(100); + // } catch (InterruptedException e) { + // // silent + // } + // return returnCode; + // } + + private synchronized void notifyClose() { + if (returnCode == null) + returnCode = CANCEL; + notifyAll(); + } + + protected void closeShell(int returnCode) { + this.returnCode = returnCode; + if (CANCEL == returnCode) + onCancel(); + if (foregoundShell != null && !foregoundShell.isDisposed()) { + foregoundShell.close(); + foregoundShell.dispose(); + foregoundShell = null; + } + + if (backgroundShell != null && !backgroundShell.isDisposed()) { + backgroundShell.close(); + backgroundShell.dispose(); + } + } + + protected Point getInitialSize() { + // if (exception != null) + // return new Point(800, 600); + // else + return new Point(600, 400); + } + + protected Control createDialogArea(Composite parent) { + Composite dialogarea = new Composite(parent, SWT.NONE); + dialogarea.setLayout(new GridLayout()); + dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + return dialogarea; + } + + protected Shell getBackgroundShell() { + return backgroundShell; + } + + protected Shell getForegoundShell() { + return foregoundShell; + } + + public void setBlockOnOpen(boolean shouldBlock) { + block = shouldBlock; + } + + public void pack() { + foregoundShell.pack(); + } + + private void runEventLoop(Shell loopShell) { + Display display; + if (foregoundShell == null) { + display = Display.getCurrent(); + } else { + display = loopShell.getDisplay(); + } + + while (loopShell != null && !loopShell.isDisposed()) { + try { + if (!display.readAndDispatch()) { + display.sleep(); + } + } catch (UnsupportedOperationException e) { + throw e; + } catch (Throwable e) { + handleException(e); + } + } + if (!display.isDisposed()) + display.update(); + } + + protected void handleException(Throwable t) { + if (t instanceof ThreadDeath) { + // Don't catch ThreadDeath as this is a normal occurrence when + // the thread dies + throw (ThreadDeath) t; + } + // Try to keep running. + t.printStackTrace(); + } + + /** @return false, if the dialog should not be closed. */ + protected boolean onCancel() { + return true; + } + + public void setTitle(String title) { + this.title = title; + if (title != null && getForegoundShell() != null) + getForegoundShell().setText(title); + } + + public Integer getReturnCode() { + return returnCode; + } + +} \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/SingleValue.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/SingleValue.java new file mode 100644 index 000000000..8ce9b44fb --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/SingleValue.java @@ -0,0 +1,130 @@ +package org.argeo.eclipse.ui.dialogs; + +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.eclipse.jface.dialogs.IMessageProvider; +import org.eclipse.jface.dialogs.TitleAreaDialog; +import org.eclipse.jface.window.Window; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Point; +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.Display; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +/** + * Dialog to retrieve a single value. + * + * @deprecated Use CMS dialogs instead. + */ +@Deprecated +public class SingleValue extends TitleAreaDialog { + private static final long serialVersionUID = 2843538207460082349L; + + private Text valueT; + private String value; + private final String title, message, label; + private final Boolean multiline; + + public static String ask(String label, String message) { + SingleValue svd = new SingleValue(label, message); + if (svd.open() == Window.OK) + return svd.getString(); + else + return null; + } + + public static Long askLong(String label, String message) { + SingleValue svd = new SingleValue(label, message); + if (svd.open() == Window.OK) + return svd.getLong(); + else + return null; + } + + public static Double askDouble(String label, String message) { + SingleValue svd = new SingleValue(label, message); + if (svd.open() == Window.OK) + return svd.getDouble(); + else + return null; + } + + public SingleValue(String label, String message) { + this(Display.getDefault().getActiveShell(), label, message, label, false); + } + + public SingleValue(Shell parentShell, String title, String message, String label, Boolean multiline) { + super(parentShell); + this.title = title; + this.message = message; + this.label = label; + this.multiline = multiline; + } + + protected Point getInitialSize() { + if (multiline) + return new Point(450, 350); + + else + return new Point(400, 270); + } + + protected Control createDialogArea(Composite parent) { + Composite dialogarea = (Composite) super.createDialogArea(parent); + dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + Composite composite = new Composite(dialogarea, SWT.NONE); + composite.setLayoutData(EclipseUiUtils.fillAll()); + GridLayout layout = new GridLayout(2, false); + layout.marginWidth = layout.marginHeight = 20; + composite.setLayout(layout); + + valueT = createLT(composite, label); + + setMessage(message, IMessageProvider.NONE); + + parent.pack(); + valueT.setFocus(); + return composite; + } + + @Override + protected void okPressed() { + value = valueT.getText(); + super.okPressed(); + } + + /** Creates label and text. */ + protected Text createLT(Composite parent, String label) { + new Label(parent, SWT.NONE).setText(label); + Text text; + if (multiline) { + text = new Text(parent, SWT.LEAD | SWT.BORDER | SWT.MULTI); + text.setLayoutData(EclipseUiUtils.fillAll()); + } else { + text = new Text(parent, SWT.LEAD | SWT.BORDER | SWT.SINGLE); + text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true)); + } + return text; + } + + protected void configureShell(Shell shell) { + super.configureShell(shell); + shell.setText(title); + } + + public String getString() { + return value; + } + + public Long getLong() { + return Long.valueOf(getString()); + } + + public Double getDouble() { + return Double.valueOf(getString()); + } +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/package-info.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/package-info.java new file mode 100644 index 000000000..d6ab1481e --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/package-info.java @@ -0,0 +1,2 @@ +/** Generic SWT/JFace dialogs. */ +package org.argeo.eclipse.ui.dialogs; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/AdvancedFsBrowser.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/AdvancedFsBrowser.java new file mode 100644 index 000000000..c01b2d751 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/AdvancedFsBrowser.java @@ -0,0 +1,450 @@ +package org.argeo.eclipse.ui.fs; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.LinkedHashMap; + +import org.argeo.api.cms.CmsLog; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.SashForm; +import org.eclipse.swt.custom.ScrolledComposite; +import org.eclipse.swt.events.ControlAdapter; +import org.eclipse.swt.events.ControlEvent; +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.graphics.Rectangle; +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.Label; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.Text; + +/** Simple UI provider that populates a composite parent given a NIO path */ +public class AdvancedFsBrowser { + private final static CmsLog log = CmsLog.getLog(AdvancedFsBrowser.class); + + // Some local constants to experiment. should be cleaned + // private final static int THUMBNAIL_WIDTH = 400; + // private Point imageWidth = new Point(250, 0); + private final static int COLUMN_WIDTH = 160; + + private Path initialPath; + private Path currEdited; + // Filter + private Composite displayBoxCmp; + private Text parentPathTxt; + private Text filterTxt; + // Browser columns + private ScrolledComposite scrolledCmp; + // Keep a cache of the opened directories + private LinkedHashMap browserCols = new LinkedHashMap<>(); + private Composite scrolledCmpBody; + + public Control createUi(Composite parent, Path basePath) { + if (basePath == null) + throw new IllegalArgumentException("Context cannot be null"); + parent.setLayout(new GridLayout()); + + // top filter + Composite filterCmp = new Composite(parent, SWT.NO_FOCUS); + filterCmp.setLayoutData(EclipseUiUtils.fillWidth()); + addFilterPanel(filterCmp); + + // Bottom part a sash with browser on the left + SashForm form = new SashForm(parent, SWT.HORIZONTAL); + // form.setLayout(new FillLayout()); + form.setLayoutData(EclipseUiUtils.fillAll()); + Composite leftCmp = new Composite(form, SWT.NO_FOCUS); + displayBoxCmp = new Composite(form, SWT.NONE); + form.setWeights(new int[] { 3, 1 }); + + createBrowserPart(leftCmp, basePath); + // leftCmp.addControlListener(new ControlAdapter() { + // @Override + // public void controlResized(ControlEvent e) { + // Rectangle r = leftCmp.getClientArea(); + // log.warn("Browser resized: " + r.toString()); + // scrolledCmp.setMinSize(browserCols.size() * (COLUMN_WIDTH + 2), + // SWT.DEFAULT); + // // scrolledCmp.setMinSize(scrolledCmpBody.computeSize(SWT.DEFAULT, + // // r.height)); + // } + // }); + + populateCurrEditedDisplay(displayBoxCmp, basePath); + + // INIT + setEdited(basePath); + initialPath = basePath; + // form.layout(true, true); + return parent; + } + + private void createBrowserPart(Composite parent, Path context) { + parent.setLayout(EclipseUiUtils.noSpaceGridLayout()); + + // scrolled composite + scrolledCmp = new ScrolledComposite(parent, SWT.H_SCROLL | SWT.BORDER | SWT.NO_FOCUS); + scrolledCmp.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + scrolledCmp.setExpandVertical(true); + scrolledCmp.setExpandHorizontal(true); + scrolledCmp.setShowFocusedControl(true); + + scrolledCmpBody = new Composite(scrolledCmp, SWT.NO_FOCUS); + scrolledCmp.setContent(scrolledCmpBody); + scrolledCmpBody.addControlListener(new ControlAdapter() { + private static final long serialVersionUID = 183238447102854553L; + + @Override + public void controlResized(ControlEvent e) { + Rectangle r = scrolledCmp.getClientArea(); + scrolledCmp.setMinSize(scrolledCmpBody.computeSize(SWT.DEFAULT, r.height)); + } + }); + initExplorer(scrolledCmpBody, context); + scrolledCmpBody.layout(true, true); + scrolledCmp.layout(); + + } + + private Control initExplorer(Composite parent, Path context) { + parent.setLayout(EclipseUiUtils.noSpaceGridLayout()); + return createBrowserColumn(parent, context); + } + + private Control createBrowserColumn(Composite parent, Path context) { + // TODO style is not correctly managed. + FilterEntitiesVirtualTable table = new FilterEntitiesVirtualTable(parent, SWT.BORDER | SWT.NO_FOCUS, context); + // CmsUtils.style(table, ArgeoOrgStyle.browserColumn.style()); + table.filterList("*"); + table.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, false, true)); + browserCols.put(context, table); + parent.layout(true, true); + return table; + } + + public void addFilterPanel(Composite parent) { + parent.setLayout(EclipseUiUtils.noSpaceGridLayout(new GridLayout(2, false))); + + parentPathTxt = new Text(parent, SWT.NO_FOCUS); + parentPathTxt.setEditable(false); + + filterTxt = new Text(parent, SWT.SEARCH | SWT.ICON_CANCEL); + filterTxt.setMessage("Filter current list"); + filterTxt.setLayoutData(EclipseUiUtils.fillWidth()); + filterTxt.addModifyListener(new ModifyListener() { + private static final long serialVersionUID = 1L; + + public void modifyText(ModifyEvent event) { + modifyFilter(false); + } + }); + filterTxt.addKeyListener(new KeyListener() { + private static final long serialVersionUID = 2533535233583035527L; + + @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; + FilterEntitiesVirtualTable currTable = null; + if (currEdited != null) { + FilterEntitiesVirtualTable table = browserCols.get(currEdited); + if (table != null && !table.isDisposed()) + currTable = table; + } + + if (e.keyCode == SWT.ARROW_DOWN) + currTable.setFocus(); + else if (e.keyCode == SWT.BS) { + if (filterTxt.getText().equals("") + && !(currEdited.getNameCount() == 1 || currEdited.equals(initialPath))) { + Path oldEdited = currEdited; + Path parentPath = currEdited.getParent(); + setEdited(parentPath); + if (browserCols.containsKey(parentPath)) + browserCols.get(parentPath).setSelected(oldEdited); + filterTxt.setFocus(); + e.doit = false; + } + } else if (e.keyCode == SWT.TAB && !shiftPressed) { + Path uniqueChild = getOnlyChild(currEdited, filterTxt.getText()); + if (uniqueChild != null) { + // Highlight the unique chosen child + currTable.setSelected(uniqueChild); + setEdited(uniqueChild); + } + filterTxt.setFocus(); + e.doit = false; + } + } + }); + } + + private Path getOnlyChild(Path parent, String filter) { + try (DirectoryStream stream = Files.newDirectoryStream(currEdited, filter + "*")) { + Path uniqueChild = null; + boolean moreThanOne = false; + loop: for (Path entry : stream) { + if (uniqueChild == null) { + uniqueChild = entry; + } else { + moreThanOne = true; + break loop; + } + } + if (!moreThanOne) + return uniqueChild; + return null; + } catch (IOException ioe) { + throw new FsUiException( + "Unable to determine unique child existence and get it under " + parent + " with filter " + filter, + ioe); + } + } + + private void setEdited(Path path) { + currEdited = path; + EclipseUiUtils.clear(displayBoxCmp); + populateCurrEditedDisplay(displayBoxCmp, currEdited); + refreshFilters(path); + refreshBrowser(path); + } + + private void refreshFilters(Path path) { + parentPathTxt.setText(path.toUri().toString()); + filterTxt.setText(""); + filterTxt.getParent().layout(); + } + + private void refreshBrowser(Path currPath) { + Path currParPath = currPath.getParent(); + Object[][] colMatrix = new Object[browserCols.size()][2]; + + int i = 0, currPathIndex = -1, lastLeftOpenedIndex = -1; + for (Path path : browserCols.keySet()) { + colMatrix[i][0] = path; + colMatrix[i][1] = browserCols.get(path); + if (currPathIndex >= 0 && lastLeftOpenedIndex < 0 && currParPath != null) { + boolean leaveOpened = path.startsWith(currPath); + if (!leaveOpened) + lastLeftOpenedIndex = i; + } + if (currParPath.equals(path)) + currPathIndex = i; + i++; + } + + if (currPathIndex >= 0 && lastLeftOpenedIndex >= 0) { + // dispose and remove useless cols + for (int l = i - 1; l >= lastLeftOpenedIndex; l--) { + ((FilterEntitiesVirtualTable) colMatrix[l][1]).dispose(); + browserCols.remove(colMatrix[l][0]); + } + } + + if (browserCols.containsKey(currPath)) { + FilterEntitiesVirtualTable currCol = browserCols.get(currPath); + if (currCol.isDisposed()) { + // Does it still happen ? + log.warn(currPath + " browser column was disposed and still listed"); + browserCols.remove(currPath); + } + } + + if (!browserCols.containsKey(currPath) && Files.isDirectory(currPath)) + createBrowserColumn(scrolledCmpBody, currPath); + + scrolledCmpBody.setLayout(EclipseUiUtils.noSpaceGridLayout(new GridLayout(browserCols.size(), false))); + scrolledCmpBody.layout(true, true); + // also resize the scrolled composite + scrolledCmp.layout(); + } + + private void modifyFilter(boolean fromOutside) { + if (!fromOutside) + if (currEdited != null) { + String filter = filterTxt.getText() + "*"; + FilterEntitiesVirtualTable table = browserCols.get(currEdited); + if (table != null && !table.isDisposed()) + table.filterList(filter); + } + } + + /** + * Recreates the content of the box that displays information about the current + * selected node. + */ + private void populateCurrEditedDisplay(Composite parent, Path context) { + parent.setLayout(new GridLayout()); + + // if (isImg(context)) { + // EditableImage image = new Img(parent, RIGHT, context, imageWidth); + // image.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false, + // 2, 1)); + // } + + try { + Label contextL = new Label(parent, SWT.NONE); + contextL.setText(context.getFileName().toString()); + contextL.setFont(EclipseUiUtils.getBoldFont(parent)); + addProperty(parent, "Last modified", Files.getLastModifiedTime(context).toString()); + addProperty(parent, "Owner", Files.getOwner(context).getName()); + if (Files.isDirectory(context)) { + addProperty(parent, "Type", "Folder"); + } else { + String mimeType = Files.probeContentType(context); + if (EclipseUiUtils.isEmpty(mimeType)) + mimeType = "Unknown"; + addProperty(parent, "Type", mimeType); + addProperty(parent, "Size", FsUiUtils.humanReadableByteCount(Files.size(context), false)); + } + parent.layout(true, true); + } catch (IOException e) { + throw new FsUiException("Cannot display details for " + context, e); + } + } + + private void addProperty(Composite parent, String propName, String value) { + Label contextL = new Label(parent, SWT.NONE); + contextL.setText(propName + ": " + value); + } + + /** + * Almost canonical implementation of a table that displays the content of a + * directory + */ + private class FilterEntitiesVirtualTable extends Composite { + private static final long serialVersionUID = 2223410043691844875L; + + // Context + private Path context; + private Path currSelected = null; + + // UI Objects + private FsTableViewer viewer; + + @Override + public boolean setFocus() { + if (viewer.getTable().isDisposed()) + return false; + if (currSelected != null) + viewer.setSelection(new StructuredSelection(currSelected), true); + else if (viewer.getSelection().isEmpty()) { + Object first = viewer.getElementAt(0); + if (first != null) + viewer.setSelection(new StructuredSelection(first), true); + } + return viewer.getTable().setFocus(); + } + + /** + * Enable highlighting the correct element in the table when externally browsing + * (typically via the command-line-like Text field) + */ + void setSelected(Path selected) { + // to prevent change selection event to be thrown + currSelected = selected; + viewer.setSelection(new StructuredSelection(currSelected), true); + } + + void filterList(String filter) { + viewer.setInput(context, filter); + } + + public FilterEntitiesVirtualTable(Composite parent, int style, Path context) { + super(parent, SWT.NO_FOCUS); + this.context = context; + createTableViewer(this); + } + + private void createTableViewer(final Composite parent) { + parent.setLayout(EclipseUiUtils.noSpaceGridLayout()); + + // We must limit the size of the table otherwise the full list is + // loaded before the layout happens + // Composite listCmp = new Composite(parent, SWT.NO_FOCUS); + // GridData gd = new GridData(SWT.LEFT, SWT.FILL, false, true); + // gd.widthHint = COLUMN_WIDTH; + // listCmp.setLayoutData(gd); + // listCmp.setLayout(EclipseUiUtils.noSpaceGridLayout()); + // viewer = new TableViewer(listCmp, SWT.VIRTUAL | SWT.MULTI | + // SWT.V_SCROLL); + // Table table = viewer.getTable(); + // table.setLayoutData(EclipseUiUtils.fillAll()); + + viewer = new FsTableViewer(parent, SWT.MULTI); + Table table = viewer.configureDefaultSingleColumnTable(COLUMN_WIDTH); + + viewer.addSelectionChangedListener(new ISelectionChangedListener() { + + @Override + public void selectionChanged(SelectionChangedEvent event) { + IStructuredSelection selection = (IStructuredSelection) viewer.getSelection(); + if (selection.isEmpty()) + return; + Object obj = selection.getFirstElement(); + Path newSelected; + if (obj instanceof Path) + newSelected = (Path) obj; + else if (obj instanceof ParentDir) + newSelected = ((ParentDir) obj).getPath(); + else + return; + if (newSelected.equals(currSelected)) + return; + currSelected = newSelected; + setEdited(newSelected); + + } + }); + + table.addKeyListener(new KeyListener() { + private static final long serialVersionUID = -8083424284436715709L; + + @Override + public void keyReleased(KeyEvent e) { + } + + @Override + public void keyPressed(KeyEvent e) { + IStructuredSelection selection = (IStructuredSelection) viewer.getSelection(); + Path selected = null; + if (!selection.isEmpty()) + selected = ((Path) selection.getFirstElement()); + if (e.keyCode == SWT.ARROW_RIGHT) { + if (!Files.isDirectory(selected)) + return; + if (selected != null) { + setEdited(selected); + browserCols.get(selected).setFocus(); + } + } else if (e.keyCode == SWT.ARROW_LEFT) { + if (context.equals(initialPath)) + return; + Path parent = context.getParent(); + if (parent == null) + return; + + setEdited(parent); + browserCols.get(parent).setFocus(); + } + } + }); + } + } +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FileIconNameLabelProvider.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FileIconNameLabelProvider.java new file mode 100644 index 000000000..d3fc1c903 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FileIconNameLabelProvider.java @@ -0,0 +1,84 @@ +package org.argeo.eclipse.ui.fs; + +import java.nio.file.Files; +import java.nio.file.Path; + +import org.eclipse.jface.resource.ImageDescriptor; +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.swt.graphics.Image; + +/** Basic label provider with icon for NIO file viewers */ +public class FileIconNameLabelProvider extends ColumnLabelProvider { + private static final long serialVersionUID = 8187902187946523148L; + + private Image folderIcon; + private Image fileIcon; + + public FileIconNameLabelProvider() { + // if (!PlatformUI.isWorkbenchRunning()) { + folderIcon = ImageDescriptor.createFromFile(getClass(), "folder.png").createImage(); + fileIcon = ImageDescriptor.createFromFile(getClass(), "file.png").createImage(); + // } + } + + @Override + public void dispose() { + if (folderIcon != null) + folderIcon.dispose(); + if (fileIcon != null) + fileIcon.dispose(); + super.dispose(); + } + + @Override + public String getText(Object element) { + if (element instanceof Path) { + Path curr = ((Path) element); + Path name = curr.getFileName(); + if (name == null) + return "[No name]"; + else + return name.toString(); + } else if (element instanceof ParentDir) { + return ".."; + } + return null; + } + + @Override + public Image getImage(Object element) { + if (element instanceof Path) { + Path curr = ((Path) element); + if (Files.isDirectory(curr)) + // if (folderIcon != null) + return folderIcon; + // else + // return + // PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FOLDER); + // else if (fileIcon != null) + return fileIcon; + // else + // return + // PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FILE); + } else if (element instanceof ParentDir) { + return folderIcon; + } + return null; + } + + @Override + public String getToolTipText(Object element) { + if (element instanceof Path) { + Path curr = ((Path) element); + Path name = curr.getFileName(); + if (name == null) + return "[No name]"; + else + return name.toAbsolutePath().toString(); + } else if (element instanceof ParentDir) { + return ((ParentDir) element).getPath().toAbsolutePath().toString(); + } + return null; + } + +} \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTableViewer.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTableViewer.java new file mode 100644 index 000000000..3b126e90b --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTableViewer.java @@ -0,0 +1,141 @@ +package org.argeo.eclipse.ui.fs; + +import java.nio.file.Path; +import java.util.List; + +import org.argeo.eclipse.ui.ColumnDefinition; +import org.eclipse.jface.viewers.CellLabelProvider; +import org.eclipse.jface.viewers.ILazyContentProvider; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; + +/** + * Canonical implementation of a JFace table viewer to display the content of a + * file folder + */ +public class FsTableViewer extends TableViewer { + private static final long serialVersionUID = -5632407542678477234L; + + private boolean showHiddenItems = false; + private boolean folderFirst = true; + private boolean reverseOrder = false; + private String orderProperty = FsUiConstants.PROPERTY_NAME; + + private Path initialPath = null; + + public FsTableViewer(Composite parent, int style) { + super(parent, style | SWT.VIRTUAL); + } + + public Table configureDefaultSingleColumnTable(int tableWidthHint) { + + return configureDefaultSingleColumnTable(tableWidthHint, new FileIconNameLabelProvider()); + } + + public Table configureDefaultSingleColumnTable(int tableWidthHint, CellLabelProvider labelProvider) { + Table table = this.getTable(); + table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + table.setLinesVisible(false); + table.setHeaderVisible(false); + // CmsUtils.markup(table); + // CmsUtils.style(table, MaintenanceStyles.BROWSER_COLUMN); + + TableViewerColumn column = new TableViewerColumn(this, SWT.NONE); + TableColumn tcol = column.getColumn(); + tcol.setWidth(tableWidthHint); + column.setLabelProvider(labelProvider); + this.setContentProvider(new MyLazyCP()); + return table; + } + + public Table configureDefaultTable(List columns) { + this.setContentProvider(new MyLazyCP()); + Table table = this.getTable(); + table.setLinesVisible(true); + table.setHeaderVisible(true); + // CmsUtils.markup(table); + // CmsUtils.style(table, MaintenanceStyles.BROWSER_COLUMN); + for (ColumnDefinition colDef : columns) { + TableViewerColumn column = new TableViewerColumn(this, SWT.NONE); + column.setLabelProvider(colDef.getLabelProvider()); + TableColumn tcol = column.getColumn(); + tcol.setResizable(true); + tcol.setText(colDef.getLabel()); + tcol.setWidth(colDef.getMinWidth()); + } + return table; + } + + public void setInput(Path dir, String filter) { + Path[] rows = FsUiUtils.getChildren(dir, filter, showHiddenItems, folderFirst, orderProperty, reverseOrder); + if (rows == null) { + this.setInput(null); + this.setItemCount(0); + return; + } + boolean isRoot; + try { + isRoot = dir.getRoot().equals(dir); + } catch (Exception e) { + // FIXME Workaround for JCR root node access + isRoot = dir.toString().equals("/"); + } + final Object[] res; + if (isRoot) + res = rows; + else if (initialPath != null && initialPath.equals(dir)) + res = rows; + else { + res = new Object[rows.length + 1]; + res[0] = new ParentDir(dir.getParent()); + for (int i = 1; i < res.length; i++) { + res[i] = rows[i - 1]; + } + } + this.setInput(res); + int length = res.length; + this.setItemCount(length); + this.refresh(); + } + + /** Directly displays bookmarks **/ + public void setPathsInput(Path... paths) { + this.setInput((Object[]) paths); + this.setItemCount(paths.length); + this.refresh(); + } + + /** + * A path which is to be considered as root (and thus provide no link to a + * parent directory) + */ + public void setInitialPath(Path initialPath) { + this.initialPath = initialPath; + } + + private class MyLazyCP implements ILazyContentProvider { + private static final long serialVersionUID = 9096550041395433128L; + private Object[] elements; + + public void dispose() { + } + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + // IMPORTANT: don't forget this: an exception will be thrown if + // a selected object is not part of the results anymore. + viewer.setSelection(null); + this.elements = (Object[]) newInput; + } + + public void updateElement(int index) { + if (index < elements.length) + FsTableViewer.this.replace(elements[index], index); + } + } +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTreeViewer.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTreeViewer.java new file mode 100644 index 000000000..f55ead718 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTreeViewer.java @@ -0,0 +1,144 @@ +package org.argeo.eclipse.ui.fs; + +import java.io.IOException; +import java.nio.file.DirectoryIteratorException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import org.argeo.eclipse.ui.ColumnDefinition; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.TreeViewerColumn; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.TreeColumn; + +/** + * Canonical implementation of a JFace TreeViewer to display the content of a + * repository + */ +public class FsTreeViewer extends TreeViewer { + private static final long serialVersionUID = -5632407542678477234L; + + private boolean showHiddenItems = false; + private boolean showDirectoryFirst = true; + private String orderingProperty = FsUiConstants.PROPERTY_NAME; + + public FsTreeViewer(Composite parent, int style) { + super(parent, style | SWT.VIRTUAL); + } + + public Tree configureDefaultSingleColumnTable(int tableWidthHint) { + Tree tree = this.getTree(); + tree.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); + tree.setLinesVisible(true); + tree.setHeaderVisible(false); +// CmsUtils.markup(tree); + + TreeViewerColumn column = new TreeViewerColumn(this, SWT.NONE); + TreeColumn tcol = column.getColumn(); + tcol.setWidth(tableWidthHint); + column.setLabelProvider(new FileIconNameLabelProvider()); + + this.setContentProvider(new MyCP()); + return tree; + } + + public Tree configureDefaultTable(List columns) { + this.setContentProvider(new MyCP()); + Tree tree = this.getTree(); + tree.setLinesVisible(true); + tree.setHeaderVisible(true); +// CmsUtils.markup(tree); +// CmsUtils.style(tree, MaintenanceStyles.BROWSER_COLUMN); + for (ColumnDefinition colDef : columns) { + TreeViewerColumn column = new TreeViewerColumn(this, SWT.NONE); + column.setLabelProvider(colDef.getLabelProvider()); + TreeColumn tcol = column.getColumn(); + tcol.setResizable(true); + tcol.setText(colDef.getLabel()); + tcol.setWidth(colDef.getMinWidth()); + } + return tree; + } + + public void setInput(Path dir, String filter) { + try (DirectoryStream stream = Files.newDirectoryStream(dir, filter)) { + // TODO make this lazy + List paths = new ArrayList<>(); + for (Path entry : stream) { + paths.add(entry); + } + Object[] rows = paths.toArray(new Object[0]); + this.setInput(rows); + // this.setItemCount(rows.length); + this.refresh(); + } catch (IOException | DirectoryIteratorException e) { + throw new FsUiException("Unable to filter " + dir + " children with filter " + filter, e); + } + } + + /** Directly displays bookmarks **/ + public void setPathsInput(Path... paths) { + this.setInput((Object[]) paths); + // this.setItemCount(paths.length); + this.refresh(); + } + + private class MyCP implements ITreeContentProvider { + private static final long serialVersionUID = 9096550041395433128L; + private Object[] elements; + + public void dispose() { + } + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + // IMPORTANT: don't forget this: an exception will be thrown if + // a selected object is not part of the results anymore. + viewer.setSelection(null); + this.elements = (Object[]) newInput; + } + + @Override + public Object[] getElements(Object inputElement) { + return elements; + } + + @Override + public Object[] getChildren(Object parentElement) { + Path path = (Path) parentElement; + if (!Files.isDirectory(path)) + return null; + else + return FsUiUtils.getChildren(path, "*", showHiddenItems, showDirectoryFirst, orderingProperty, false); + } + + @Override + public Object getParent(Object element) { + Path path = (Path) element; + return path.getParent(); + } + + @Override + public boolean hasChildren(Object element) { + Path path = (Path) element; + try { + if (!Files.isDirectory(path)) + return false; + else + try (DirectoryStream children = Files.newDirectoryStream(path, "*")) { + return children.iterator().hasNext(); + } + } catch (IOException e) { + throw new FsUiException("Unable to check child existence on " + path, e); + } + } + + } +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiConstants.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiConstants.java new file mode 100644 index 000000000..2b51e71a2 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiConstants.java @@ -0,0 +1,11 @@ +package org.argeo.eclipse.ui.fs; + +/** Centralize constants used by the Nio FS UI parts */ +public interface FsUiConstants { + + // TODO use standard properties + String PROPERTY_NAME = "name"; + String PROPERTY_SIZE = "size"; + String PROPERTY_LAST_MODIFIED = "last-modified"; + String PROPERTY_TYPE = "type"; +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiException.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiException.java new file mode 100644 index 000000000..422b0e1ad --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiException.java @@ -0,0 +1,14 @@ +package org.argeo.eclipse.ui.fs; + +/** Files specific exception */ +public class FsUiException extends RuntimeException { + private static final long serialVersionUID = 1L; + + public FsUiException(String message) { + super(message); + } + + public FsUiException(String message, Throwable e) { + super(message, e); + } +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiUtils.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiUtils.java new file mode 100644 index 000000000..956d96bb5 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiUtils.java @@ -0,0 +1,132 @@ +package org.argeo.eclipse.ui.fs; + +import java.io.IOException; +import java.nio.file.DirectoryIteratorException; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** Centralise additional utilitary methods to manage Java7 NIO files */ +public class FsUiUtils { + + /** + * thanks to + * http://programming.guide/java/formatting-byte-size-to-human-readable-format.html + */ + public static String humanReadableByteCount(long bytes, boolean si) { + int unit = si ? 1000 : 1024; + if (bytes < unit) + return bytes + " B"; + int exp = (int) (Math.log(bytes) / Math.log(unit)); + String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i"); + return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); + } + + public static Path[] getChildren(Path parent, String filter, boolean showHiddenItems, boolean folderFirst, + String orderProperty, boolean reverseOrder) { + if (!Files.isDirectory(parent)) + return null; + List pairs = new ArrayList<>(); + try (DirectoryStream stream = Files.newDirectoryStream(parent, filter)) { + loop: for (Path entry : stream) { + if (!showHiddenItems) + if (Files.isHidden(entry)) + continue loop; + switch (orderProperty) { + case FsUiConstants.PROPERTY_SIZE: + if (folderFirst) + pairs.add(new LPair(entry, Files.size(entry), Files.isDirectory(entry))); + else + pairs.add(new LPair(entry, Files.size(entry))); + break; + case FsUiConstants.PROPERTY_LAST_MODIFIED: + if (folderFirst) + pairs.add(new LPair(entry, Files.getLastModifiedTime(entry).toMillis(), + Files.isDirectory(entry))); + else + pairs.add(new LPair(entry, Files.getLastModifiedTime(entry).toMillis())); + break; + case FsUiConstants.PROPERTY_NAME: + if (folderFirst) + pairs.add(new SPair(entry, entry.getFileName().toString(), Files.isDirectory(entry))); + else + pairs.add(new SPair(entry, entry.getFileName().toString())); + break; + default: + throw new FsUiException("Unable to prepare sort for property " + orderProperty); + } + } + Pair[] rows = pairs.toArray(new Pair[0]); + Arrays.sort(rows); + Path[] results = new Path[rows.length]; + if (reverseOrder) { + int j = rows.length - 1; + for (int i = 0; i < rows.length; i++) + results[i] = rows[j - i].p; + } else + for (int i = 0; i < rows.length; i++) + results[i] = rows[i].p; + return results; + } catch (IOException | DirectoryIteratorException e) { + throw new FsUiException("Unable to filter " + parent + " children with filter " + filter, e); + } + } + + static abstract class Pair implements Comparable { + Path p; + Boolean i; + }; + + static class LPair extends Pair { + long v; + + public LPair(Path path, long propValue) { + p = path; + v = propValue; + } + + public LPair(Path path, long propValue, boolean isDir) { + p = path; + v = propValue; + i = isDir; + } + + public int compareTo(Object o) { + if (i != null) { + Boolean j = ((LPair) o).i; + if (i.booleanValue() != j.booleanValue()) + return i.booleanValue() ? -1 : 1; + } + long u = ((LPair) o).v; + return v < u ? -1 : v == u ? 0 : 1; + } + }; + + static class SPair extends Pair { + String v; + + public SPair(Path path, String propValue) { + p = path; + v = propValue; + } + + public SPair(Path path, String propValue, boolean isDir) { + p = path; + v = propValue; + i = isDir; + } + + public int compareTo(Object o) { + if (i != null) { + Boolean j = ((SPair) o).i; + if (i.booleanValue() != j.booleanValue()) + return i.booleanValue() ? -1 : 1; + } + String u = ((SPair) o).v; + return v.compareTo(u); + } + }; +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/NioFileLabelProvider.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/NioFileLabelProvider.java new file mode 100644 index 000000000..2bb65eed0 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/NioFileLabelProvider.java @@ -0,0 +1,78 @@ +package org.argeo.eclipse.ui.fs; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.attribute.FileTime; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.eclipse.jface.viewers.ColumnLabelProvider; + +/** Expect a {@link Path} as input element */ +public class NioFileLabelProvider extends ColumnLabelProvider { + private final static FileTime EPOCH = FileTime.fromMillis(0); + private static final long serialVersionUID = 2160026425187796930L; + private final String propName; + private DateFormat dateFormat = new SimpleDateFormat("YYYY-MM-dd HH:mm"); + + // TODO use new formatting + // DateTimeFormatter formatter = + // DateTimeFormatter.ofLocalizedDateTime( FormatStyle.SHORT ) + // .withLocale( Locale.UK ) + // .withZone( ZoneId.systemDefault() ); + public NioFileLabelProvider(String propName) { + this.propName = propName; + } + + @Override + public String getText(Object element) { + try { + Path path; + if (element instanceof ParentDir) { +// switch (propName) { +// case FsUiConstants.PROPERTY_SIZE: +// return "-"; +// case FsUiConstants.PROPERTY_LAST_MODIFIED: +// return "-"; +// // return Files.getLastModifiedTime(((ParentDir) element).getPath()).toString(); +// case FsUiConstants.PROPERTY_TYPE: +// return "Folder"; +// } + path = ((ParentDir) element).getPath(); + } else + path = (Path) element; + switch (propName) { + case FsUiConstants.PROPERTY_SIZE: + if (Files.isDirectory(path)) + return "-"; + else + return FsUiUtils.humanReadableByteCount(Files.size(path), false); + case FsUiConstants.PROPERTY_LAST_MODIFIED: + if (Files.isDirectory(path)) + return "-"; + FileTime time = Files.getLastModifiedTime(path); + if (time.equals(EPOCH)) + return "-"; + else + return dateFormat.format(new Date(time.toMillis())); + case FsUiConstants.PROPERTY_TYPE: + if (Files.isDirectory(path)) + return "Folder"; + else { + String mimeType = Files.probeContentType(path); + if (EclipseUiUtils.isEmpty(mimeType)) + return "Unknown"; + else + return mimeType; + } + default: + throw new IllegalArgumentException("Unsupported property " + propName); + } + } catch (IOException ioe) { + throw new FsUiException("Cannot get property " + propName + " on " + element); + } + } +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/ParentDir.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/ParentDir.java new file mode 100644 index 000000000..6f09c2905 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/ParentDir.java @@ -0,0 +1,28 @@ +package org.argeo.eclipse.ui.fs; + +import java.nio.file.Path; + +/** A parent directory (..) reference. */ +public class ParentDir { + Path path; + + public ParentDir(Path path) { + super(); + this.path = path; + } + + public Path getPath() { + return path; + } + + @Override + public int hashCode() { + return path.hashCode(); + } + + @Override + public String toString() { + return "Parent dir " + path; + } + +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsBrowser.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsBrowser.java new file mode 100644 index 000000000..2e3d6b405 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsBrowser.java @@ -0,0 +1,211 @@ +package org.argeo.eclipse.ui.fs; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import org.argeo.api.cms.CmsLog; +import org.argeo.eclipse.ui.ColumnDefinition; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IDoubleClickListener; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.SashForm; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Table; + +/** + * Experimental UI upon Java 7 nio files api: SashForm layout with bookmarks on + * the left hand side and a simple table on the right hand side. + */ +public class SimpleFsBrowser extends Composite { + private final static CmsLog log = CmsLog.getLog(SimpleFsBrowser.class); + private static final long serialVersionUID = -40347919096946585L; + + private Path currSelected; + private FsTableViewer bookmarksViewer; + private FsTableViewer directoryDisplayViewer; + + public SimpleFsBrowser(Composite parent, int style) { + super(parent, style); + createContent(this); + // parent.layout(true, true); + } + + public Viewer getViewer() { + return directoryDisplayViewer; + } + + private void createContent(Composite parent) { + parent.setLayout(EclipseUiUtils.noSpaceGridLayout()); + + SashForm form = new SashForm(parent, SWT.HORIZONTAL); + Composite leftCmp = new Composite(form, SWT.NONE); + populateBookmarks(leftCmp); + + Composite rightCmp = new Composite(form, SWT.BORDER); + populateDisplay(rightCmp); + form.setLayoutData(EclipseUiUtils.fillAll()); + form.setWeights(new int[] { 1, 3 }); + } + + public void setInput(Path... paths) { + bookmarksViewer.setPathsInput(paths); + bookmarksViewer.getTable().getParent().layout(true, true); + } + + private void populateBookmarks(final Composite parent) { + // GridLayout layout = EclipseUiUtils.noSpaceGridLayout(); + // layout.verticalSpacing = 5; + parent.setLayout(new GridLayout()); + + ISelectionChangedListener selList = new MySelectionChangedListener(); + + appendTitle(parent, "My bookmarks"); + bookmarksViewer = new FsTableViewer(parent, SWT.MULTI | SWT.NO_SCROLL); + Table table = bookmarksViewer.configureDefaultSingleColumnTable(500); + GridData gd = EclipseUiUtils.fillWidth(); + gd.horizontalIndent = 10; + table.setLayoutData(gd); + bookmarksViewer.addSelectionChangedListener(selList); + + appendTitle(parent, "Jcr + File"); + + FsTableViewer jcrFilesViewers = new FsTableViewer(parent, SWT.MULTI | SWT.NO_SCROLL); + table = jcrFilesViewers.configureDefaultSingleColumnTable(500); + gd = EclipseUiUtils.fillWidth(); + gd.horizontalIndent = 10; + table.setLayoutData(gd); + jcrFilesViewers.addSelectionChangedListener(selList); + + // FileSystemProvider fsProvider = new JackrabbitMemoryFsProvider(); + // try { + // Path testPath = fsProvider.getPath(new URI("jcr+memory:/")); + // jcrFilesViewers.setPathsInput(testPath); + // } catch (URISyntaxException e) { + // // TODO Auto-generated catch block + // e.printStackTrace(); + // } + } + + private Label appendTitle(Composite parent, String value) { + Label titleLbl = new Label(parent, SWT.NONE); + titleLbl.setText(value); + titleLbl.setFont(EclipseUiUtils.getBoldFont(parent)); + GridData gd = EclipseUiUtils.fillWidth(); + gd.horizontalIndent = 5; + gd.verticalIndent = 5; + titleLbl.setLayoutData(gd); + return titleLbl; + } + + private class MySelectionChangedListener implements ISelectionChangedListener { + @Override + public void selectionChanged(SelectionChangedEvent event) { + IStructuredSelection selection = (IStructuredSelection) bookmarksViewer.getSelection(); + if (selection.isEmpty()) + return; + else { + Path newSelected = (Path) selection.getFirstElement(); + if (newSelected.equals(currSelected)) + return; + currSelected = newSelected; + directoryDisplayViewer.setInput(currSelected, "*"); + } + } + } + + private void populateDisplay(final Composite parent) { + parent.setLayout(EclipseUiUtils.noSpaceGridLayout()); + directoryDisplayViewer = new FsTableViewer(parent, SWT.MULTI); + List colDefs = new ArrayList<>(); + colDefs.add(new ColumnDefinition(new FileIconNameLabelProvider(), "Name", 200)); + colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_SIZE), "Size", 100)); + colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_TYPE), "Type", 250)); + colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_LAST_MODIFIED), + "Last modified", 200)); + Table table = directoryDisplayViewer.configureDefaultTable(colDefs); + table.setLayoutData(EclipseUiUtils.fillAll()); + + table.addKeyListener(new KeyListener() { + private static final long serialVersionUID = -8083424284436715709L; + + @Override + public void keyReleased(KeyEvent e) { + } + + @Override + public void keyPressed(KeyEvent e) { + log.debug("Key event received: " + e.keyCode); + IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection(); + Path selected = null; + if (!selection.isEmpty()) + selected = ((Path) selection.getFirstElement()); + if (e.keyCode == SWT.CR) { + if (!Files.isDirectory(selected)) + return; + if (selected != null) { + currSelected = selected; + directoryDisplayViewer.setInput(currSelected, "*"); + } + } else if (e.keyCode == SWT.BS) { + currSelected = currSelected.getParent(); + directoryDisplayViewer.setInput(currSelected, "*"); + directoryDisplayViewer.getTable().setFocus(); + } + } + }); + +// directoryDisplayViewer.addDoubleClickListener(new IDoubleClickListener() { +// @Override +// public void doubleClick(DoubleClickEvent event) { +// IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection(); +// Path selected = null; +// if (!selection.isEmpty()) { +// Object obj = selection.getFirstElement(); +// if (obj instanceof Path) +// selected = (Path) obj; +// else if (obj instanceof ParentDir) +// selected = ((ParentDir) obj).getPath(); +// } +// if (selected != null) { +// if (!Files.isDirectory(selected)) +// return; +// currSelected = selected; +// directoryDisplayViewer.setInput(currSelected, "*"); +// } +// } +// }); + + directoryDisplayViewer.addDoubleClickListener(new IDoubleClickListener() { + @Override + public void doubleClick(DoubleClickEvent event) { + IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection(); + Path selected = null; + if (!selection.isEmpty()) { + Object obj = selection.getFirstElement(); + if (obj instanceof Path) + selected = (Path) obj; + else if (obj instanceof ParentDir) + selected = ((ParentDir) obj).getPath(); + } + if (selected != null) { + if (!Files.isDirectory(selected)) + return; + currSelected = selected; + directoryDisplayViewer.setInput(currSelected, "*"); + } + } + }); + } +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsTreeBrowser.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsTreeBrowser.java new file mode 100644 index 000000000..401e5cb5e --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsTreeBrowser.java @@ -0,0 +1,128 @@ +package org.argeo.eclipse.ui.fs; + +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; + +import org.argeo.api.cms.CmsLog; +import org.argeo.eclipse.ui.ColumnDefinition; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.SashForm; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.KeyListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.Tree; + +/** A simple Java 7 nio files browser with a tree */ +public class SimpleFsTreeBrowser extends Composite { + private final static CmsLog log = CmsLog.getLog(SimpleFsTreeBrowser.class); + private static final long serialVersionUID = -40347919096946585L; + + private Path currSelected; + private FsTreeViewer treeViewer; + private FsTableViewer directoryDisplayViewer; + + public SimpleFsTreeBrowser(Composite parent, int style) { + super(parent, style); + createContent(this); + // parent.layout(true, true); + } + + private void createContent(Composite parent) { + parent.setLayout(EclipseUiUtils.noSpaceGridLayout()); + SashForm form = new SashForm(parent, SWT.HORIZONTAL); + Composite child1 = new Composite(form, SWT.NONE); + populateTree(child1); + Composite child2 = new Composite(form, SWT.BORDER); + populateDisplay(child2); + form.setLayoutData(EclipseUiUtils.fillAll()); + form.setWeights(new int[] { 1, 3 }); + } + + public void setInput(Path... paths) { + treeViewer.setPathsInput(paths); + treeViewer.getControl().getParent().layout(true, true); + } + + private void populateTree(final Composite parent) { + // GridLayout layout = EclipseUiUtils.noSpaceGridLayout(); + // layout.verticalSpacing = 5; + parent.setLayout(new GridLayout()); + + ISelectionChangedListener selList = new MySelectionChangedListener(); + + treeViewer = new FsTreeViewer(parent, SWT.MULTI); + Tree tree = treeViewer.configureDefaultSingleColumnTable(500); + GridData gd = EclipseUiUtils.fillAll(); + // gd.horizontalIndent = 10; + tree.setLayoutData(gd); + treeViewer.addSelectionChangedListener(selList); + } + + private class MySelectionChangedListener implements ISelectionChangedListener { + @Override + public void selectionChanged(SelectionChangedEvent event) { + IStructuredSelection selection = (IStructuredSelection) treeViewer.getSelection(); + if (selection.isEmpty()) + return; + else { + Path newSelected = (Path) selection.getFirstElement(); + if (newSelected.equals(currSelected)) + return; + currSelected = newSelected; + if (Files.isDirectory(currSelected)) + directoryDisplayViewer.setInput(currSelected, "*"); + } + } + } + + private void populateDisplay(final Composite parent) { + parent.setLayout(EclipseUiUtils.noSpaceGridLayout()); + directoryDisplayViewer = new FsTableViewer(parent, SWT.MULTI); + List colDefs = new ArrayList<>(); + colDefs.add(new ColumnDefinition(new FileIconNameLabelProvider(), "Name", 200, 200)); + colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_SIZE), "Size", 100, 100)); + colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_TYPE), "Type", 300, 300)); + colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_LAST_MODIFIED), + "Last modified", 100, 100)); + Table table = directoryDisplayViewer.configureDefaultTable(colDefs); + table.setLayoutData(EclipseUiUtils.fillAll()); + + table.addKeyListener(new KeyListener() { + private static final long serialVersionUID = -8083424284436715709L; + + @Override + public void keyReleased(KeyEvent e) { + } + + @Override + public void keyPressed(KeyEvent e) { + log.debug("Key event received: " + e.keyCode); + IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection(); + Path selected = null; + if (!selection.isEmpty()) + selected = ((Path) selection.getFirstElement()); + if (e.keyCode == SWT.CR) { + if (!Files.isDirectory(selected)) + return; + if (selected != null) { + currSelected = selected; + directoryDisplayViewer.setInput(currSelected, "*"); + } + } else if (e.keyCode == SWT.BS) { + currSelected = currSelected.getParent(); + directoryDisplayViewer.setInput(currSelected, "*"); + directoryDisplayViewer.getTable().setFocus(); + } + } + }); + } +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/file.png b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/file.png new file mode 100644 index 000000000..ce2f2a8f4 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/file.png differ diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/folder.png b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/folder.png new file mode 100644 index 000000000..c31f37e07 Binary files /dev/null and b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/folder.png differ diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/package-info.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/package-info.java new file mode 100644 index 000000000..d7f83c3e1 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/package-info.java @@ -0,0 +1,2 @@ +/** Generic SWT/JFace file system utilities. */ +package org.argeo.eclipse.ui.fs; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/package-info.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/package-info.java new file mode 100644 index 000000000..0d245db07 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/package-info.java @@ -0,0 +1,2 @@ +/** Generic SWT/JFace utilities. */ +package org.argeo.eclipse.ui; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/parts/LdifUsersTable.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/parts/LdifUsersTable.java new file mode 100644 index 000000000..57139056c --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/parts/LdifUsersTable.java @@ -0,0 +1,402 @@ +package org.argeo.eclipse.ui.parts; + +import java.util.ArrayList; +import java.util.List; + +import org.argeo.eclipse.ui.ColumnDefinition; +import org.argeo.eclipse.ui.EclipseUiException; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.argeo.eclipse.ui.util.ViewerUtils; +import org.eclipse.jface.layout.TableColumnLayout; +import org.eclipse.jface.viewers.CheckboxTableViewer; +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.jface.viewers.ColumnWeightData; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ModifyEvent; +import org.eclipse.swt.events.ModifyListener; +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.Composite; +import org.eclipse.swt.widgets.Link; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.swt.widgets.Text; +import org.osgi.service.useradmin.User; + +/** + * Generic composite that display a filter and a table viewer to display users + * (can also be groups) + * + * Warning: this class does not extends TableViewer. Use the + * getTableViewer method to access it. + * + */ +public abstract class LdifUsersTable extends Composite { + private static final long serialVersionUID = -7385959046279360420L; + + // Context + // private UserAdmin userAdmin; + + // Configuration + private List columnDefs = new ArrayList(); + private boolean hasFilter; + private boolean preventTableLayout = false; + private boolean hasSelectionColumn; + private int tableStyle; + + // Local UI Objects + private TableViewer usersViewer; + private Text filterTxt; + + /* EXPOSED METHODS */ + + /** + * @param parent + * @param style + */ + public LdifUsersTable(Composite parent, int style) { + super(parent, SWT.NO_FOCUS); + this.tableStyle = style; + } + + // TODO workaround the bug of the table layout in the Form + public LdifUsersTable(Composite parent, int style, boolean preventTableLayout) { + super(parent, SWT.NO_FOCUS); + this.tableStyle = style; + this.preventTableLayout = preventTableLayout; + } + + /** This must be called before the call to populate method */ + public void setColumnDefinitions(List columnDefinitions) { + this.columnDefs = columnDefinitions; + } + + /** + * + * @param addFilter + * choose to add a field to filter results or not + * @param addSelection + * choose to add a column to select some of the displayed results or + * not + */ + public void populate(boolean addFilter, boolean addSelection) { + // initialization + Composite parent = this; + hasFilter = addFilter; + hasSelectionColumn = addSelection; + + // Main Layout + GridLayout layout = EclipseUiUtils.noSpaceGridLayout(); + layout.verticalSpacing = 5; + this.setLayout(layout); + if (hasFilter) + createFilterPart(parent); + + Composite tableComp = new Composite(parent, SWT.NO_FOCUS); + tableComp.setLayoutData(EclipseUiUtils.fillAll()); + usersViewer = createTableViewer(tableComp); + usersViewer.setContentProvider(new UsersContentProvider()); + } + + /** + * + * @param showMore + * display static filters on creation + * @param addSelection + * choose to add a column to select some of the displayed results or + * not + */ + public void populateWithStaticFilters(boolean showMore, boolean addSelection) { + // initialization + Composite parent = this; + hasFilter = true; + hasSelectionColumn = addSelection; + + // Main Layout + GridLayout layout = EclipseUiUtils.noSpaceGridLayout(); + layout.verticalSpacing = 5; + this.setLayout(layout); + createStaticFilterPart(parent, showMore); + + Composite tableComp = new Composite(parent, SWT.NO_FOCUS); + tableComp.setLayoutData(EclipseUiUtils.fillAll()); + usersViewer = createTableViewer(tableComp); + usersViewer.setContentProvider(new UsersContentProvider()); + } + + /** Enable access to the selected users or groups */ + public List getSelectedUsers() { + if (hasSelectionColumn) { + Object[] elements = ((CheckboxTableViewer) usersViewer).getCheckedElements(); + + List result = new ArrayList(); + for (Object obj : elements) { + result.add((User) obj); + } + return result; + } else + throw new EclipseUiException( + "Unvalid request: no selection column " + "has been created for the current table"); + } + + /** Returns the User table viewer, typically to add doubleclick listener */ + public TableViewer getTableViewer() { + return usersViewer; + } + + /** + * Force the refresh of the underlying table using the current filter string if + * relevant + */ + public void refresh() { + String filter = hasFilter ? filterTxt.getText().trim() : null; + if ("".equals(filter)) + filter = null; + refreshFilteredList(filter); + } + + /** Effective repository request: caller must implement this method */ + abstract protected List listFilteredElements(String filter); + + // protected List listFilteredElements(String filter) { + // List users = new ArrayList(); + // try { + // Role[] roles = userAdmin.getRoles(filter); + // // Display all users and groups + // for (Role role : roles) + // users.add((User) role); + // } catch (InvalidSyntaxException e) { + // throw new EclipseUiException("Unable to get roles with filter: " + // + filter, e); + // } + // return users; + // } + + /* GENERIC COMPOSITE METHODS */ + @Override + public boolean setFocus() { + if (hasFilter) + return filterTxt.setFocus(); + else + return usersViewer.getTable().setFocus(); + } + + @Override + public void dispose() { + super.dispose(); + } + + /* LOCAL CLASSES AND METHODS */ + // Will be usefull to rather use a virtual table viewer + private void refreshFilteredList(String filter) { + List users = listFilteredElements(filter); + usersViewer.setInput(users.toArray()); + } + + private class UsersContentProvider implements IStructuredContentProvider { + private static final long serialVersionUID = 1L; + + public Object[] getElements(Object inputElement) { + return (Object[]) inputElement; + } + + public void dispose() { + } + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + } + + /* MANAGE FILTER */ + private void createFilterPart(Composite parent) { + // Text Area for the filter + filterTxt = new Text(parent, SWT.BORDER | SWT.SEARCH | SWT.ICON_SEARCH | SWT.ICON_CANCEL); + filterTxt.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, false)); + filterTxt.addModifyListener(new ModifyListener() { + private static final long serialVersionUID = 1L; + + public void modifyText(ModifyEvent event) { + refreshFilteredList(filterTxt.getText()); + } + }); + } + + private void createStaticFilterPart(Composite parent, boolean showMore) { + Composite filterComp = new Composite(parent, SWT.NO_FOCUS); + filterComp.setLayout(new GridLayout(2, false)); + filterComp.setLayoutData(EclipseUiUtils.fillWidth()); + // generic search + filterTxt = new Text(filterComp, SWT.BORDER | SWT.SEARCH | SWT.ICON_SEARCH | SWT.ICON_CANCEL); + filterTxt.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, false)); + // filterTxt.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL | + // GridData.HORIZONTAL_ALIGN_FILL)); + filterTxt.addModifyListener(new ModifyListener() { + private static final long serialVersionUID = 1L; + + public void modifyText(ModifyEvent event) { + refreshFilteredList(filterTxt.getText()); + } + }); + + // add static filter abilities + Link moreLk = new Link(filterComp, SWT.NONE); + Composite staticFilterCmp = new Composite(filterComp, SWT.NO_FOCUS); + staticFilterCmp.setLayoutData(EclipseUiUtils.fillWidth(2)); + populateStaticFilters(staticFilterCmp); + + MoreLinkListener listener = new MoreLinkListener(moreLk, staticFilterCmp, showMore); + // initialise the layout + listener.refresh(); + moreLk.addSelectionListener(listener); + } + + /** Overwrite to add static filters */ + protected void populateStaticFilters(Composite staticFilterCmp) { + } + + // private void addMoreSL(final Link more) { + // more.addSelectionListener( } + + private class MoreLinkListener extends SelectionAdapter { + private static final long serialVersionUID = -524987616510893463L; + private boolean isShown; + private final Composite staticFilterCmp; + private final Link moreLk; + + public MoreLinkListener(Link moreLk, Composite staticFilterCmp, boolean isShown) { + this.moreLk = moreLk; + this.staticFilterCmp = staticFilterCmp; + this.isShown = isShown; + } + + @Override + public void widgetSelected(SelectionEvent e) { + isShown = !isShown; + refresh(); + } + + public void refresh() { + GridData gd = (GridData) staticFilterCmp.getLayoutData(); + if (isShown) { + moreLk.setText(" Less... "); + gd.heightHint = SWT.DEFAULT; + } else { + moreLk.setText(" More... "); + gd.heightHint = 0; + } + forceLayout(); + } + } + + private void forceLayout() { + LdifUsersTable.this.getParent().layout(true, true); + } + + private TableViewer createTableViewer(final Composite parent) { + + int style = tableStyle | SWT.H_SCROLL | SWT.V_SCROLL; + if (hasSelectionColumn) + style = style | SWT.CHECK; + Table table = new Table(parent, style); + TableColumnLayout layout = new TableColumnLayout(); + + // TODO the table layout does not works with the scrolled form + + if (preventTableLayout) { + parent.setLayout(EclipseUiUtils.noSpaceGridLayout()); + table.setLayoutData(EclipseUiUtils.fillAll()); + } else + parent.setLayout(layout); + + TableViewer viewer; + if (hasSelectionColumn) + viewer = new CheckboxTableViewer(table); + else + viewer = new TableViewer(table); + table.setLinesVisible(true); + table.setHeaderVisible(true); + + TableViewerColumn column; + // int offset = 0; + if (hasSelectionColumn) { + // offset = 1; + column = ViewerUtils.createTableViewerColumn(viewer, "", SWT.NONE, 25); + column.setLabelProvider(new ColumnLabelProvider() { + private static final long serialVersionUID = 1L; + + @Override + public String getText(Object element) { + return null; + } + }); + layout.setColumnData(column.getColumn(), new ColumnWeightData(25, 25, false)); + + SelectionAdapter selectionAdapter = new SelectionAdapter() { + private static final long serialVersionUID = 1L; + + boolean allSelected = false; + + @Override + public void widgetSelected(SelectionEvent e) { + allSelected = !allSelected; + ((CheckboxTableViewer) usersViewer).setAllChecked(allSelected); + } + }; + column.getColumn().addSelectionListener(selectionAdapter); + } + + // NodeViewerComparator comparator = new NodeViewerComparator(); + // TODO enable the sort by click on the header + // int i = offset; + for (ColumnDefinition colDef : columnDefs) + createTableColumn(viewer, layout, colDef); + + // column = ViewerUtils.createTableViewerColumn(viewer, + // colDef.getHeaderLabel(), SWT.NONE, colDef.getColumnSize()); + // column.setLabelProvider(new CLProvider(colDef.getPropertyName())); + // column.getColumn().addSelectionListener( + // JcrUiUtils.getNodeSelectionAdapter(i, + // colDef.getPropertyType(), colDef.getPropertyName(), + // comparator, viewer)); + // i++; + // } + + // IMPORTANT: initialize comparator before setting it + // JcrColumnDefinition firstCol = colDefs.get(0); + // comparator.setColumn(firstCol.getPropertyType(), + // firstCol.getPropertyName()); + // viewer.setComparator(comparator); + + return viewer; + } + + /** Default creation of a column for a user table */ + private TableViewerColumn createTableColumn(TableViewer tableViewer, TableColumnLayout layout, + ColumnDefinition columnDef) { + + boolean resizable = true; + TableViewerColumn tvc = new TableViewerColumn(tableViewer, SWT.NONE); + TableColumn column = tvc.getColumn(); + + column.setText(columnDef.getLabel()); + column.setWidth(columnDef.getMinWidth()); + column.setResizable(resizable); + + ColumnLabelProvider lp = columnDef.getLabelProvider(); + // add a reference to the display to enable font management + // if (lp instanceof UserAdminAbstractLP) + // ((UserAdminAbstractLP) lp).setDisplay(tableViewer.getTable() + // .getDisplay()); + tvc.setLabelProvider(lp); + + layout.setColumnData(column, new ColumnWeightData(columnDef.getWeight(), columnDef.getMinWidth(), resizable)); + + return tvc; + } +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/parts/package-info.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/parts/package-info.java new file mode 100644 index 000000000..9e93b1106 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/parts/package-info.java @@ -0,0 +1,2 @@ +/** Generic SWT/JFace composites. */ +package org.argeo.eclipse.ui.parts; \ No newline at end of file diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/util/ViewerUtils.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/util/ViewerUtils.java new file mode 100644 index 000000000..8f4df1799 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/util/ViewerUtils.java @@ -0,0 +1,58 @@ +package org.argeo.eclipse.ui.util; + +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.jface.viewers.TableViewerColumn; +import org.eclipse.jface.viewers.TreeViewer; +import org.eclipse.jface.viewers.TreeViewerColumn; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.TableColumn; +import org.eclipse.swt.widgets.TreeColumn; + +/** + * Centralise useful methods to manage JFace Table, Tree and TreeColumn viewers. + */ +public class ViewerUtils { + + /** + * Creates a basic column for the given table. For the time being, we do not + * support movable columns. + */ + public static TableColumn createColumn(Table parent, String name, int style, int width) { + TableColumn result = new TableColumn(parent, style); + result.setText(name); + result.setWidth(width); + result.setResizable(true); + return result; + } + + /** + * Creates a TableViewerColumn for the given viewer. For the time being, we do + * not support movable columns. + */ + public static TableViewerColumn createTableViewerColumn(TableViewer parent, String name, int style, int width) { + TableViewerColumn tvc = new TableViewerColumn(parent, style); + TableColumn column = tvc.getColumn(); + column.setText(name); + column.setWidth(width); + column.setResizable(true); + return tvc; + } + + // public static TableViewerColumn createTableViewerColumn(TableViewer parent, + // Localized name, int style, int width) { + // return createTableViewerColumn(parent, name.lead(), style, width); + // } + + /** + * Creates a TreeViewerColumn for the given viewer. For the time being, we do + * not support movable columns. + */ + public static TreeViewerColumn createTreeViewerColumn(TreeViewer parent, String name, int style, int width) { + TreeViewerColumn tvc = new TreeViewerColumn(parent, style); + TreeColumn column = tvc.getColumn(); + column.setText(name); + column.setWidth(width); + column.setResizable(true); + return tvc; + } +} diff --git a/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/util/package-info.java b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/util/package-info.java new file mode 100644 index 000000000..798d17482 --- /dev/null +++ b/eclipse/org.argeo.cms.swt/src/org/argeo/eclipse/ui/util/package-info.java @@ -0,0 +1,2 @@ +/** Generic SWT/JFace JCR helpers. */ +package org.argeo.eclipse.ui.util; \ No newline at end of file diff --git a/eclipse/pom.xml b/eclipse/pom.xml new file mode 100644 index 000000000..abd787893 --- /dev/null +++ b/eclipse/pom.xml @@ -0,0 +1,21 @@ + + + 4.0.0 + + org.argeo.commons + argeo-commons + 2.3-SNAPSHOT + .. + + org.argeo.commons + eclipse + Eclipse Specific + pom + + org.argeo.cms.servlet + org.argeo.cms.swt + org.argeo.cms.e4 + + \ No newline at end of file diff --git a/jcr/cnf/maven.bnd b/jcr/cnf/maven.bnd new file mode 100644 index 000000000..4bd5c0cfe --- /dev/null +++ b/jcr/cnf/maven.bnd @@ -0,0 +1 @@ +-include: ../../cnf/maven.bnd \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/.classpath b/jcr/org.argeo.cms.jcr/.classpath new file mode 100644 index 000000000..4a00becd8 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/.classpath @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/jcr/org.argeo.cms.jcr/.project b/jcr/org.argeo.cms.jcr/.project new file mode 100644 index 000000000..3e470f829 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/.project @@ -0,0 +1,33 @@ + + + org.argeo.cms.jcr + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + org.eclipse.pde.ds.core.builder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/jcr/org.argeo.cms.jcr/.settings/org.eclipse.jdt.core.prefs b/jcr/org.argeo.cms.jcr/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..7e2e11935 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,101 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled +org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore +org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull +org.eclipse.jdt.core.compiler.annotation.nonnull.secondary= +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary= +org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable +org.eclipse.jdt.core.compiler.annotation.nullable.secondary= +org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled +org.eclipse.jdt.core.compiler.problem.APILeak=warning +org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning +org.eclipse.jdt.core.compiler.problem.autoboxing=ignore +org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning +org.eclipse.jdt.core.compiler.problem.deadCode=warning +org.eclipse.jdt.core.compiler.problem.deprecation=warning +org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled +org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled +org.eclipse.jdt.core.compiler.problem.discouragedReference=warning +org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore +org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore +org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore +org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled +org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore +org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning +org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning +org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled +org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning +org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning +org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore +org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore +org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning +org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore +org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled +org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore +org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled +org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning +org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore +org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning +org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning +org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore +org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning +org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning +org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error +org.eclipse.jdt.core.compiler.problem.nullReference=warning +org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error +org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning +org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning +org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore +org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning +org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore +org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore +org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore +org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning +org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning +org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore +org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore +org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore +org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore +org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore +org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled +org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning +org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled +org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled +org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled +org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore +org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning +org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning +org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled +org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning +org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning +org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore +org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning +org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning +org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled +org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info +org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore +org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore +org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore +org.eclipse.jdt.core.compiler.problem.unstableAutoModuleName=warning +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled +org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedImport=warning +org.eclipse.jdt.core.compiler.problem.unusedLabel=warning +org.eclipse.jdt.core.compiler.problem.unusedLocal=warning +org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled +org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled +org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning +org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore +org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning +org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning diff --git a/jcr/org.argeo.cms.jcr/OSGI-INF/dataServletContext.xml b/jcr/org.argeo.cms.jcr/OSGI-INF/dataServletContext.xml new file mode 100644 index 000000000..f5fc8deaa --- /dev/null +++ b/jcr/org.argeo.cms.jcr/OSGI-INF/dataServletContext.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/jcr/org.argeo.cms.jcr/OSGI-INF/filesServlet.xml b/jcr/org.argeo.cms.jcr/OSGI-INF/filesServlet.xml new file mode 100644 index 000000000..a283ef075 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/OSGI-INF/filesServlet.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/jcr/org.argeo.cms.jcr/OSGI-INF/filesServletContext.xml b/jcr/org.argeo.cms.jcr/OSGI-INF/filesServletContext.xml new file mode 100644 index 000000000..5fb56e3ed --- /dev/null +++ b/jcr/org.argeo.cms.jcr/OSGI-INF/filesServletContext.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/jcr/org.argeo.cms.jcr/OSGI-INF/jcrDeployment.xml b/jcr/org.argeo.cms.jcr/OSGI-INF/jcrDeployment.xml new file mode 100644 index 000000000..a94b15168 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/OSGI-INF/jcrDeployment.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/jcr/org.argeo.cms.jcr/OSGI-INF/jcrFsProvider.xml b/jcr/org.argeo.cms.jcr/OSGI-INF/jcrFsProvider.xml new file mode 100644 index 000000000..e26453b06 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/OSGI-INF/jcrFsProvider.xml @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/jcr/org.argeo.cms.jcr/OSGI-INF/jcrRepositoryFactory.xml b/jcr/org.argeo.cms.jcr/OSGI-INF/jcrRepositoryFactory.xml new file mode 100644 index 000000000..b43b51920 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/OSGI-INF/jcrRepositoryFactory.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/jcr/org.argeo.cms.jcr/OSGI-INF/jcrServletContext.xml b/jcr/org.argeo.cms.jcr/OSGI-INF/jcrServletContext.xml new file mode 100644 index 000000000..a0885bbc5 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/OSGI-INF/jcrServletContext.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/jcr/org.argeo.cms.jcr/OSGI-INF/repositoryContextsFactory.xml b/jcr/org.argeo.cms.jcr/OSGI-INF/repositoryContextsFactory.xml new file mode 100644 index 000000000..db2bfaa26 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/OSGI-INF/repositoryContextsFactory.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/jcr/org.argeo.cms.jcr/bnd.bnd b/jcr/org.argeo.cms.jcr/bnd.bnd new file mode 100644 index 000000000..71071f697 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/bnd.bnd @@ -0,0 +1,37 @@ +Bundle-Activator: org.argeo.cms.jcr.internal.osgi.CmsJcrActivator + +Provide-Capability:\ +cms.datamodel; name=jcrx; cnd=/org/argeo/jcr/jcrx.cnd; abstract=true,\ +cms.datamodel; name=argeo; cnd=/org/argeo/cms/jcr/argeo.cnd; abstract=true,\ +cms.datamodel;name=ldap; cnd=/org/argeo/cms/jcr/ldap.cnd; abstract=true,\ +osgi.service;objectClass="javax.jcr.Repository" + +Import-Package:\ +org.argeo.cms.servlet,\ +javax.jcr.security,\ +org.h2;resolution:=optional,\ +org.postgresql;version="[42,43)";resolution:=optional,\ +org.apache.jackrabbit.webdav.server,\ +org.apache.jackrabbit.webdav.jcr,\ +org.apache.commons.httpclient.cookie;resolution:=optional,\ +org.osgi.framework.namespace;version=0.0.0,\ +org.osgi.*;version=0.0.0,\ +org.osgi.service.http.whiteboard,\ +org.apache.jackrabbit.api,\ +org.apache.jackrabbit.commons,\ +org.apache.jackrabbit.spi,\ +org.apache.jackrabbit.spi2dav,\ +org.apache.jackrabbit.spi2davex,\ +org.apache.jackrabbit.webdav,\ +junit.*;resolution:=optional,\ +* + +Service-Component:\ +OSGI-INF/repositoryContextsFactory.xml,\ +OSGI-INF/jcrRepositoryFactory.xml,\ +OSGI-INF/jcrFsProvider.xml,\ +OSGI-INF/jcrDeployment.xml,\ +OSGI-INF/jcrServletContext.xml,\ +OSGI-INF/dataServletContext.xml,\ +OSGI-INF/filesServletContext.xml,\ +OSGI-INF/filesServlet.xml diff --git a/jcr/org.argeo.cms.jcr/build.properties b/jcr/org.argeo.cms.jcr/build.properties new file mode 100644 index 000000000..3ddcf97c6 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/build.properties @@ -0,0 +1,28 @@ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + OSGI-INF/jcrDeployment.xml,\ + OSGI-INF/repositoryContextsFactory.xml,\ + OSGI-INF/jcrRepositoryFactory.xml,\ + OSGI-INF/jcrFsProvider.xml +source.. = src/ +additional.bundles = org.apache.jackrabbit.core,\ + javax.jcr,\ + org.apache.jackrabbit.api,\ + org.apache.jackrabbit.data,\ + org.apache.jackrabbit.jcr.commons,\ + org.apache.jackrabbit.spi,\ + org.apache.jackrabbit.spi.commons,\ + org.slf4j.api,\ + org.apache.commons.collections,\ + EDU.oswego.cs.dl.util.concurrent,\ + org.apache.lucene,\ + org.apache.tika.core,\ + org.apache.commons.dbcp,\ + org.apache.commons.pool,\ + com.google.guava,\ + org.apache.jackrabbit.jcr2spi,\ + org.apache.jackrabbit.spi2dav,\ + org.apache.httpcomponents.httpclient,\ + org.apache.httpcomponents.httpcore,\ + org.apache.tika.parsers diff --git a/jcr/org.argeo.cms.jcr/pom.xml b/jcr/org.argeo.cms.jcr/pom.xml new file mode 100644 index 000000000..b6f63c5b8 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/pom.xml @@ -0,0 +1,24 @@ + + + 4.0.0 + + org.argeo.commons + jcr + 2.3-SNAPSHOT + .. + + org.argeo.cms.jcr + CMS JCR + + + org.argeo.commons + org.argeo.cms + 2.3-SNAPSHOT + + + org.argeo.commons + org.argeo.cms.servlet + 2.3-SNAPSHOT + + + \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/fs/CmsFsUtils.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/fs/CmsFsUtils.java new file mode 100644 index 000000000..40d38eec2 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/fs/CmsFsUtils.java @@ -0,0 +1,88 @@ +package org.argeo.cms.fs; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.FileSystem; +import java.nio.file.Path; +import java.nio.file.spi.FileSystemProvider; + +import javax.jcr.NoSuchWorkspaceException; +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.query.Query; +import javax.jcr.query.QueryManager; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.jcr.Jcr; + +/** Utilities around documents. */ +public class CmsFsUtils { + // TODO make it more robust and configurable + private static String baseWorkspaceName = CmsConstants.SYS_WORKSPACE; + + public static Node getNode(Repository repository, Path path) { + String workspaceName = path.getNameCount() == 0 ? baseWorkspaceName : path.getName(0).toString(); + String jcrPath = '/' + path.subpath(1, path.getNameCount()).toString(); + try { + Session newSession; + try { + newSession = repository.login(workspaceName); + } catch (NoSuchWorkspaceException e) { + // base workspace + newSession = repository.login(baseWorkspaceName); + jcrPath = path.toString(); + } + return newSession.getNode(jcrPath); + } catch (RepositoryException e) { + throw new IllegalStateException("Cannot get node from path " + path, e); + } + } + + public static NodeIterator getLastUpdatedDocuments(Session session) { + try { + String qStr = "//element(*, nt:file)"; + qStr += " order by @jcr:lastModified descending"; + QueryManager queryManager = session.getWorkspace().getQueryManager(); + @SuppressWarnings("deprecation") + Query xpathQuery = queryManager.createQuery(qStr, Query.XPATH); + xpathQuery.setLimit(8); + NodeIterator nit = xpathQuery.execute().getNodes(); + return nit; + } catch (RepositoryException e) { + throw new IllegalStateException("Unable to retrieve last updated documents", e); + } + } + + public static Path getPath(FileSystemProvider nodeFileSystemProvider, URI uri) { + try { + FileSystem fileSystem = nodeFileSystemProvider.getFileSystem(uri); + if (fileSystem == null) + fileSystem = nodeFileSystemProvider.newFileSystem(uri, null); + String path = uri.getPath(); + return fileSystem.getPath(path); + } catch (IOException e) { + throw new IllegalStateException("Unable to initialise file system for " + uri, e); + } + } + + public static Path getPath(FileSystemProvider nodeFileSystemProvider, Node node) { + String workspaceName = Jcr.getWorkspaceName(node); + String fullPath = baseWorkspaceName.equals(workspaceName) ? Jcr.getPath(node) + : '/' + workspaceName + Jcr.getPath(node); + URI uri; + try { + uri = new URI(CmsConstants.SCHEME_NODE, null, fullPath, null); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Cannot interpret " + fullPath + " as an URI", e); + } + return getPath(nodeFileSystemProvider, uri); + } + + /** Singleton. */ + private CmsFsUtils() { + } +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/CustomRepositoryConfigurationParser.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/CustomRepositoryConfigurationParser.java new file mode 100644 index 000000000..c2898577e --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/CustomRepositoryConfigurationParser.java @@ -0,0 +1,58 @@ +package org.argeo.cms.internal.jcr; + +import java.util.Properties; + +import org.apache.jackrabbit.core.config.BeanConfig; +import org.apache.jackrabbit.core.config.ConfigurationException; +import org.apache.jackrabbit.core.config.RepositoryConfigurationParser; +import org.apache.jackrabbit.core.config.WorkspaceSecurityConfig; +import org.apache.jackrabbit.core.util.db.ConnectionFactory; +import org.w3c.dom.Element; + +/** + * A {@link RepositoryConfigurationParser} providing more flexibility with + * classloaders. + */ +@SuppressWarnings("restriction") +class CustomRepositoryConfigurationParser extends RepositoryConfigurationParser { + private ClassLoader classLoader = null; + + public CustomRepositoryConfigurationParser(Properties variables) { + super(variables); + } + + public CustomRepositoryConfigurationParser(Properties variables, ConnectionFactory connectionFactory) { + super(variables, connectionFactory); + } + + @Override + protected RepositoryConfigurationParser createSubParser(Properties variables) { + Properties props = new Properties(getVariables()); + props.putAll(variables); + CustomRepositoryConfigurationParser subParser = new CustomRepositoryConfigurationParser(props, + connectionFactory); + subParser.setClassLoader(classLoader); + return subParser; + } + + @Override + public WorkspaceSecurityConfig parseWorkspaceSecurityConfig(Element parent) throws ConfigurationException { + WorkspaceSecurityConfig workspaceSecurityConfig = super.parseWorkspaceSecurityConfig(parent); + workspaceSecurityConfig.getAccessControlProviderConfig().setClassLoader(classLoader); + return workspaceSecurityConfig; + } + + @Override + protected BeanConfig parseBeanConfig(Element parent, String name) throws ConfigurationException { + BeanConfig beanConfig = super.parseBeanConfig(parent, name); + if (beanConfig.getClassName().startsWith("org.argeo")) { + beanConfig.setClassLoader(classLoader); + } + return beanConfig; + } + + public void setClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/JackrabbitType.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/JackrabbitType.java new file mode 100644 index 000000000..40c83f6df --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/JackrabbitType.java @@ -0,0 +1,21 @@ +package org.argeo.cms.internal.jcr; + +/** Pre-defined Jackrabbit repository configurations. */ +enum JackrabbitType { + /** Local file system */ + localfs, + /** Embedded Java H2 database */ + h2, + /** Embedded Java H2 database in PostgreSQL compatibility mode */ + h2_postgresql, + /** PostgreSQL */ + postgresql, + /** PostgreSQL with datastore */ + postgresql_ds, + /** PostgreSQL with cluster */ + postgresql_cluster, + /** PostgreSQL with cluster and datastore */ + postgresql_cluster_ds, + /** Memory */ + memory; +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/JcrInitUtils.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/JcrInitUtils.java new file mode 100644 index 000000000..0536fb645 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/JcrInitUtils.java @@ -0,0 +1,123 @@ +package org.argeo.cms.internal.jcr; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Dictionary; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; + +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.RepositoryFactory; + +import org.argeo.api.cms.CmsDeployment; +import org.argeo.api.cms.CmsLog; +import org.argeo.api.cms.CmsConstants; +import org.argeo.jackrabbit.client.ClientDavexRepositoryFactory; +import org.argeo.jcr.JcrException; +import org.argeo.util.naming.LdapAttrs; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.FrameworkUtil; + +/** JCR specific init utilities. */ +public class JcrInitUtils { + private final static CmsLog log = CmsLog.getLog(JcrInitUtils.class); + private final static BundleContext bundleContext = FrameworkUtil.getBundle(JcrInitUtils.class).getBundleContext(); + + public static void addToDeployment(CmsDeployment nodeDeployment) { + // node repository +// Dictionary provided = null; + Dictionary provided = nodeDeployment.getProps(CmsConstants.NODE_REPOS_FACTORY_PID, + CmsConstants.NODE); + Dictionary nodeConfig = JcrInitUtils.getNodeRepositoryConfig(provided); + // node repository is mandatory + nodeDeployment.addFactoryDeployConfig(CmsConstants.NODE_REPOS_FACTORY_PID, nodeConfig); + + // additional repositories +// dataModels: for (DataModels.DataModel dataModel : dataModels.getNonAbstractDataModels()) { +// if (NodeConstants.NODE_REPOSITORY.equals(dataModel.getName())) +// continue dataModels; +// Dictionary config = JcrInitUtils.getRepositoryConfig(dataModel.getName(), +// getProps(NodeConstants.NODE_REPOS_FACTORY_PID, dataModel.getName())); +// if (config.size() != 0) +// putFactoryDeployConfig(NodeConstants.NODE_REPOS_FACTORY_PID, config); +// } + + } + + /** Override the provided config with the framework properties */ + public static Dictionary getNodeRepositoryConfig(Dictionary provided) { + Dictionary props = provided != null ? provided : new Hashtable(); + for (RepoConf repoConf : RepoConf.values()) { + Object value = getFrameworkProp(CmsConstants.NODE_REPO_PROP_PREFIX + repoConf.name()); + if (value != null) { + props.put(repoConf.name(), value); + if (log.isDebugEnabled()) + log.debug("Set node repo configuration " + repoConf.name() + " to " + value); + } + } + props.put(CmsConstants.CN, CmsConstants.NODE_REPOSITORY); + return props; + } + + public static Dictionary getRepositoryConfig(String dataModelName, + Dictionary provided) { + if (dataModelName.equals(CmsConstants.NODE_REPOSITORY) || dataModelName.equals(CmsConstants.EGO_REPOSITORY)) + throw new IllegalArgumentException("Data model '" + dataModelName + "' is reserved."); + Dictionary props = provided != null ? provided : new Hashtable(); + for (RepoConf repoConf : RepoConf.values()) { + Object value = getFrameworkProp( + CmsConstants.NODE_REPOS_PROP_PREFIX + dataModelName + '.' + repoConf.name()); + if (value != null) { + props.put(repoConf.name(), value); + if (log.isDebugEnabled()) + log.debug("Set " + dataModelName + " repo configuration " + repoConf.name() + " to " + value); + } + } + if (props.size() != 0) + props.put(CmsConstants.CN, dataModelName); + return props; + } + + private static void registerRemoteInit(String uri) { + try { + Repository repository = createRemoteRepository(new URI(uri)); + Hashtable properties = new Hashtable<>(); + properties.put(CmsConstants.CN, CmsConstants.NODE_INIT); + properties.put(LdapAttrs.labeledURI.name(), uri); + properties.put(Constants.SERVICE_RANKING, -1000); + bundleContext.registerService(Repository.class, repository, properties); + } catch (RepositoryException e) { + throw new JcrException(e); + } catch (URISyntaxException e) { + throw new IllegalArgumentException(e); + } + } + + private static Repository createRemoteRepository(URI uri) throws RepositoryException { + RepositoryFactory repositoryFactory = new ClientDavexRepositoryFactory(); + Map params = new HashMap(); + params.put(ClientDavexRepositoryFactory.JACKRABBIT_DAVEX_URI, uri.toString()); + // TODO make it configurable + params.put(ClientDavexRepositoryFactory.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, CmsConstants.SYS_WORKSPACE); + return repositoryFactory.getRepository(params); + } + + private static String getFrameworkProp(String key, String def) { + String value; + if (bundleContext != null) + value = bundleContext.getProperty(key); + else + value = System.getProperty(key); + if (value == null) + return def; + return value; + } + + private static String getFrameworkProp(String key) { + return getFrameworkProp(key, null); + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/LocalFsDataStore.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/LocalFsDataStore.java new file mode 100644 index 000000000..dba005cb4 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/LocalFsDataStore.java @@ -0,0 +1,68 @@ +package org.argeo.cms.internal.jcr; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.nio.file.Paths; + +import org.apache.jackrabbit.core.data.DataIdentifier; +import org.apache.jackrabbit.core.data.DataRecord; +import org.apache.jackrabbit.core.data.DataStoreException; +import org.apache.jackrabbit.core.data.FileDataStore; + +/** + * experimental Duplicate added entries in another directory (typically a + * remote mount). + */ +@SuppressWarnings("restriction") +public class LocalFsDataStore extends FileDataStore { + String redundantPath; + FileDataStore redundantStore; + + @Override + public void init(String homeDir) { + // init primary first + super.init(homeDir); + + if (redundantPath != null) { + // redundant directory must be created first + // TODO implement some polling? + if (Files.exists(Paths.get(redundantPath))) { + redundantStore = new FileDataStore(); + redundantStore.setPath(redundantPath); + redundantStore.init(homeDir); + } + } + } + + @Override + public DataRecord addRecord(InputStream input) throws DataStoreException { + DataRecord dataRecord = super.addRecord(input); + syncRedundantRecord(dataRecord); + return dataRecord; + } + + @Override + public DataRecord getRecord(DataIdentifier identifier) throws DataStoreException { + DataRecord dataRecord = super.getRecord(identifier); + syncRedundantRecord(dataRecord); + return dataRecord; + } + + protected void syncRedundantRecord(DataRecord dataRecord) throws DataStoreException { + if (redundantStore == null) + return; + if (redundantStore.getRecordIfStored(dataRecord.getIdentifier()) == null) { + try (InputStream redundant = dataRecord.getStream()) { + redundantStore.addRecord(redundant); + } catch (IOException e) { + throw new DataStoreException("Cannot add redundant record.", e); + } + } + } + + public void setRedundantPath(String redundantPath) { + this.redundantPath = redundantPath; + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/RepoConf.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/RepoConf.java new file mode 100644 index 000000000..a45656cf5 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/RepoConf.java @@ -0,0 +1,72 @@ +package org.argeo.cms.internal.jcr; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.osgi.metatype.EnumAD; +import org.argeo.osgi.metatype.EnumOCD; + +/** JCR repository configuration */ +public enum RepoConf implements EnumAD { + /** Repository type */ + type("h2"), + /** Default workspace */ + defaultWorkspace(CmsConstants.SYS_WORKSPACE), + /** Database URL */ + dburl(null), + /** Database user */ + dbuser(null), + /** Database password */ + dbpassword(null), + + /** The identifier (can be an URL locating the repo) */ + labeledUri(null), + // + // JACKRABBIT SPECIFIC + // + /** Maximum database pool size */ + maxPoolSize(10), + /** Maximum cache size in MB */ + maxCacheMB(null), + /** Bundle cache size in MB */ + bundleCacheMB(8), + /** Extractor pool size */ + extractorPoolSize(0), + /** Search cache size */ + searchCacheSize(1000), + /** Max volatile index size */ + maxVolatileIndexSize(1048576), + /** Cluster id (if appropriate configuration) */ + clusterId("default"), + /** Indexes base path */ + indexesBase(null); + + /** The default value. */ + private Object def; + private String oid; + + RepoConf(String oid, Object def) { + this.oid = oid; + this.def = def; + } + + RepoConf(Object def) { + this.def = def; + } + + public Object getDefault() { + return def; + } + + @Override + public String getID() { + if (oid != null) + return oid; + return EnumAD.super.getID(); + } + + public static class OCD extends EnumOCD { + public OCD(String locale) { + super(RepoConf.class, locale); + } + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/RepositoryBuilder.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/RepositoryBuilder.java new file mode 100644 index 000000000..3db97167c --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/RepositoryBuilder.java @@ -0,0 +1,225 @@ +package org.argeo.cms.internal.jcr; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.Properties; +import java.util.UUID; + +import javax.jcr.RepositoryException; + +import org.apache.jackrabbit.core.RepositoryContext; +import org.apache.jackrabbit.core.RepositoryImpl; +import org.apache.jackrabbit.core.cache.CacheManager; +import org.apache.jackrabbit.core.config.RepositoryConfig; +import org.apache.jackrabbit.core.config.RepositoryConfigurationParser; +import org.argeo.api.cms.CmsConstants; +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.jcr.internal.CmsPaths; +import org.xml.sax.InputSource; + +/** Can interpret properties in order to create an actual JCR repository. */ +public class RepositoryBuilder { + private final static CmsLog log = CmsLog.getLog(RepositoryBuilder.class); + + public RepositoryContext createRepositoryContext(Dictionary properties) + throws RepositoryException, IOException { + RepositoryConfig repositoryConfig = createRepositoryConfig(properties); + RepositoryContext repositoryContext = createJackrabbitRepository(repositoryConfig); + RepositoryImpl repository = repositoryContext.getRepository(); + + // cache + Object maxCacheMbStr = prop(properties, RepoConf.maxCacheMB); + if (maxCacheMbStr != null) { + Integer maxCacheMB = Integer.parseInt(maxCacheMbStr.toString()); + CacheManager cacheManager = repository.getCacheManager(); + cacheManager.setMaxMemory(maxCacheMB * 1024l * 1024l); + cacheManager.setMaxMemoryPerCache((maxCacheMB / 4) * 1024l * 1024l); + } + + return repositoryContext; + } + + RepositoryConfig createRepositoryConfig(Dictionary properties) throws RepositoryException, IOException { + JackrabbitType type = JackrabbitType.valueOf(prop(properties, RepoConf.type).toString()); + ClassLoader cl = getClass().getClassLoader(); + final String base = "/org/argeo/cms/internal/jcr"; + try (InputStream in = cl.getResourceAsStream(base + "/repository-" + type.name() + ".xml")) { + if (in == null) + throw new IllegalArgumentException("Repository configuration not found"); + InputSource config = new InputSource(in); + Properties jackrabbitVars = getConfigurationProperties(type, properties); + // RepositoryConfig repositoryConfig = RepositoryConfig.create(config, + // jackrabbitVars); + + // custom configuration parser + CustomRepositoryConfigurationParser parser = new CustomRepositoryConfigurationParser(jackrabbitVars); + parser.setClassLoader(cl); + RepositoryConfig repositoryConfig = parser.parseRepositoryConfig(config); + repositoryConfig.init(); + + // set the proper classloaders + repositoryConfig.getSecurityConfig().getSecurityManagerConfig().setClassLoader(cl); + repositoryConfig.getSecurityConfig().getAccessManagerConfig().setClassLoader(cl); +// for (WorkspaceConfig workspaceConfig : repositoryConfig.getWorkspaceConfigs()) { +// workspaceConfig.getSecurityConfig().getAccessControlProviderConfig().setClassLoader(cl); +// } + return repositoryConfig; + } + } + + private Properties getConfigurationProperties(JackrabbitType type, Dictionary properties) { + Properties props = new Properties(); + for (Enumeration keys = properties.keys(); keys.hasMoreElements();) { + String key = keys.nextElement(); + props.put(key, properties.get(key)); + } + + // cluster id + // cf. https://wiki.apache.org/jackrabbit/Clustering + // TODO deal with multiple repos + String clusterId = System.getProperty("org.apache.jackrabbit.core.cluster.node_id"); + String clusterIdProp = props.getProperty(RepoConf.clusterId.name()); + if (clusterId != null) { + if (clusterIdProp != null) + throw new IllegalArgumentException("Cluster id defined as System properties and in deploy config"); + props.put(RepoConf.clusterId.name(), clusterId); + } else { + clusterId = clusterIdProp; + } + + // home + String homeUri = props.getProperty(RepoConf.labeledUri.name()); + Path homePath; + if (homeUri == null) { + String cn = props.getProperty(CmsConstants.CN); + assert cn != null; + if (clusterId != null) { + homePath = CmsPaths.getRepoDirPath(cn + '/' + clusterId); + } else { + homePath = CmsPaths.getRepoDirPath(cn); + } + } else { + try { + URI uri = new URI(homeUri); + String host = uri.getHost(); + if (host == null || host.trim().equals("")) { + homePath = Paths.get(uri).toAbsolutePath(); + } else { + // TODO remote at this stage? + throw new IllegalArgumentException("Cannot manage repository path for host " + host); + } + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Invalid repository home URI", e); + } + } + // TODO use Jackrabbit API (?) + Path rootUuidPath = homePath.resolve("repository/meta/rootUUID"); + try { + if (!Files.exists(rootUuidPath)) { + Files.createDirectories(rootUuidPath.getParent()); + Files.write(rootUuidPath, UUID.randomUUID().toString().getBytes()); + } + // File homeDir = homePath.toFile(); + // homeDir.mkdirs(); + } catch (IOException e) { + throw new RuntimeException("Cannot set up repository home " + homePath, e); + } + // home cannot be overridden + props.put(RepositoryConfigurationParser.REPOSITORY_HOME_VARIABLE, homePath.toString()); + + setProp(props, RepoConf.indexesBase, CmsPaths.getRepoIndexesBase().toString()); + // common + setProp(props, RepoConf.defaultWorkspace); + setProp(props, RepoConf.maxPoolSize); + // Jackrabbit defaults + setProp(props, RepoConf.bundleCacheMB); + // See http://wiki.apache.org/jackrabbit/Search + setProp(props, RepoConf.extractorPoolSize); + setProp(props, RepoConf.searchCacheSize); + setProp(props, RepoConf.maxVolatileIndexSize); + + // specific + String dburl; + switch (type) { + case h2: + dburl = "jdbc:h2:" + homePath.toAbsolutePath() + "/h2/repository"; + setProp(props, RepoConf.dburl, dburl); + setProp(props, RepoConf.dbuser, "sa"); + setProp(props, RepoConf.dbpassword, ""); + break; + case h2_postgresql: + dburl = "jdbc:h2:" + homePath.toAbsolutePath() + "/h2/repository;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE"; + setProp(props, RepoConf.dburl, dburl); + setProp(props, RepoConf.dbuser, "sa"); + setProp(props, RepoConf.dbpassword, ""); + break; + case postgresql: + case postgresql_ds: + case postgresql_cluster: + case postgresql_cluster_ds: + dburl = "jdbc:postgresql://localhost/demo"; + setProp(props, RepoConf.dburl, dburl); + setProp(props, RepoConf.dbuser, "argeo"); + setProp(props, RepoConf.dbpassword, "argeo"); + break; + case memory: + break; + case localfs: + break; + default: + throw new IllegalArgumentException("Unsupported node type " + type); + } + return props; + } + + private void setProp(Properties props, RepoConf key, String def) { + Object value = props.get(key.name()); + if (value == null) + value = def; + if (value == null) + value = key.getDefault(); + if (value != null) + props.put(key.name(), value.toString()); + } + + private void setProp(Properties props, RepoConf key) { + setProp(props, key, null); + } + + private String prop(Dictionary properties, RepoConf key) { + Object value = properties.get(key.name()); + if (value == null) + return key.getDefault() != null ? key.getDefault().toString() : null; + else + return value.toString(); + } + + private RepositoryContext createJackrabbitRepository(RepositoryConfig repositoryConfig) throws RepositoryException { + ClassLoader currentContextCl = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(RepositoryBuilder.class.getClassLoader()); + try { + long begin = System.currentTimeMillis(); + // + // Actual repository creation + // + RepositoryContext repositoryContext = RepositoryContext.create(repositoryConfig); + + double duration = ((double) (System.currentTimeMillis() - begin)) / 1000; + if (log.isDebugEnabled()) + log.debug( + "Created Jackrabbit repository in " + duration + " s, home: " + repositoryConfig.getHomeDir()); + + return repositoryContext; + } finally { + Thread.currentThread().setContextClassLoader(currentContextCl); + } + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-h2.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-h2.xml new file mode 100644 index 000000000..ace0fa5ee --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-h2.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-h2_postgresql.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-h2_postgresql.xml new file mode 100644 index 000000000..430367656 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-h2_postgresql.xml @@ -0,0 +1,86 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-localfs.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-localfs.xml new file mode 100644 index 000000000..b88907919 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-localfs.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-memory.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-memory.xml new file mode 100644 index 000000000..3630a149d --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-memory.xml @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql.xml new file mode 100644 index 000000000..de2f245ad --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster.xml new file mode 100644 index 000000000..488ad6b72 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster.xml @@ -0,0 +1,87 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster_ds.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster_ds.xml new file mode 100644 index 000000000..b430674c9 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster_ds.xml @@ -0,0 +1,103 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_ds.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_ds.xml new file mode 100644 index 000000000..5229d1660 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_ds.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/CmsJcrUtils.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/CmsJcrUtils.java new file mode 100644 index 000000000..b5d9adfca --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/CmsJcrUtils.java @@ -0,0 +1,276 @@ +package org.argeo.cms.jcr; + +import java.security.PrivilegedAction; +import java.util.HashMap; +import java.util.Map; + +import javax.jcr.NoSuchWorkspaceException; +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.RepositoryFactory; +import javax.jcr.Session; +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; +import javax.security.auth.AuthPermission; +import javax.security.auth.Subject; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; + +import org.argeo.api.cms.CmsAuth; +import org.argeo.api.cms.CmsConstants; + +/** Utilities related to Argeo model in JCR */ +public class CmsJcrUtils { + /** + * Wraps the call to the repository factory based on parameter + * {@link CmsConstants#CN} in order to simplify it and protect against future + * API changes. + */ + public static Repository getRepositoryByAlias(RepositoryFactory repositoryFactory, String alias) { + try { + Map parameters = new HashMap(); + parameters.put(CmsConstants.CN, alias); + return repositoryFactory.getRepository(parameters); + } catch (RepositoryException e) { + throw new RuntimeException("Unexpected exception when trying to retrieve repository with alias " + alias, + e); + } + } + + /** + * Wraps the call to the repository factory based on parameter + * {@link CmsConstants#LABELED_URI} in order to simplify it and protect against + * future API changes. + */ + public static Repository getRepositoryByUri(RepositoryFactory repositoryFactory, String uri) { + return getRepositoryByUri(repositoryFactory, uri, null); + } + + /** + * Wraps the call to the repository factory based on parameter + * {@link CmsConstants#LABELED_URI} in order to simplify it and protect against + * future API changes. + */ + public static Repository getRepositoryByUri(RepositoryFactory repositoryFactory, String uri, String alias) { + try { + Map parameters = new HashMap(); + parameters.put(CmsConstants.LABELED_URI, uri); + if (alias != null) + parameters.put(CmsConstants.CN, alias); + return repositoryFactory.getRepository(parameters); + } catch (RepositoryException e) { + throw new RuntimeException("Unexpected exception when trying to retrieve repository with uri " + uri, e); + } + } + + /** + * Returns the home node of the user or null if none was found. + * + * @param session the session to use in order to perform the search, this can + * be a session with a different user ID than the one searched, + * typically when a system or admin session is used. + * @param username the username of the user + */ + public static Node getUserHome(Session session, String username) { +// try { +// QueryObjectModelFactory qomf = session.getWorkspace().getQueryManager().getQOMFactory(); +// Selector sel = qomf.selector(NodeTypes.NODE_USER_HOME, "sel"); +// DynamicOperand dop = qomf.propertyValue(sel.getSelectorName(), NodeNames.LDAP_UID); +// StaticOperand sop = qomf.literal(session.getValueFactory().createValue(username)); +// Constraint constraint = qomf.comparison(dop, QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, sop); +// Query query = qomf.createQuery(sel, constraint, null, null); +// return querySingleNode(query); +// } catch (RepositoryException e) { +// throw new RuntimeException("Cannot find home for user " + username, e); +// } + + try { + checkUserWorkspace(session, username); + String homePath = getHomePath(username); + if (session.itemExists(homePath)) + return session.getNode(homePath); + // legacy + homePath = "/home/" + username; + if (session.itemExists(homePath)) + return session.getNode(homePath); + return null; + } catch (RepositoryException e) { + throw new RuntimeException("Cannot find home for user " + username, e); + } + } + + private static String getHomePath(String username) { + LdapName dn; + try { + dn = new LdapName(username); + } catch (InvalidNameException e) { + throw new IllegalArgumentException("Invalid name " + username, e); + } + String userId = dn.getRdn(dn.size() - 1).getValue().toString(); + return '/' + userId; + } + + private static void checkUserWorkspace(Session session, String username) { + String workspaceName = session.getWorkspace().getName(); + if (!CmsConstants.HOME_WORKSPACE.equals(workspaceName)) + throw new IllegalArgumentException(workspaceName + " is not the home workspace for user " + username); + } + + /** + * Returns the home node of the user or null if none was found. + * + * @param session the session to use in order to perform the search, this can + * be a session with a different user ID than the one searched, + * typically when a system or admin session is used. + * @param groupname the name of the group + */ + public static Node getGroupHome(Session session, String groupname) { +// try { +// QueryObjectModelFactory qomf = session.getWorkspace().getQueryManager().getQOMFactory(); +// Selector sel = qomf.selector(NodeTypes.NODE_GROUP_HOME, "sel"); +// DynamicOperand dop = qomf.propertyValue(sel.getSelectorName(), NodeNames.LDAP_CN); +// StaticOperand sop = qomf.literal(session.getValueFactory().createValue(cn)); +// Constraint constraint = qomf.comparison(dop, QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, sop); +// Query query = qomf.createQuery(sel, constraint, null, null); +// return querySingleNode(query); +// } catch (RepositoryException e) { +// throw new RuntimeException("Cannot find home for group " + cn, e); +// } + + try { + checkGroupWorkspace(session, groupname); + String homePath = getGroupPath(groupname); + if (session.itemExists(homePath)) + return session.getNode(homePath); + // legacy + homePath = "/groups/" + groupname; + if (session.itemExists(homePath)) + return session.getNode(homePath); + return null; + } catch (RepositoryException e) { + throw new RuntimeException("Cannot find home for group " + groupname, e); + } + + } + + private static String getGroupPath(String groupname) { + String cn; + try { + LdapName dn = new LdapName(groupname); + cn = dn.getRdn(dn.size() - 1).getValue().toString(); + } catch (InvalidNameException e) { + cn = groupname; + } + return '/' + cn; + } + + private static void checkGroupWorkspace(Session session, String groupname) { + String workspaceName = session.getWorkspace().getName(); + if (!CmsConstants.SRV_WORKSPACE.equals(workspaceName)) + throw new IllegalArgumentException(workspaceName + " is not the group workspace for group " + groupname); + } + + /** + * Queries one single node. + * + * @return one single node or null if none was found + * @throws ArgeoJcrException if more than one node was found + */ +// private static Node querySingleNode(Query query) { +// NodeIterator nodeIterator; +// try { +// QueryResult queryResult = query.execute(); +// nodeIterator = queryResult.getNodes(); +// } catch (RepositoryException e) { +// throw new RuntimeException("Cannot execute query " + query, e); +// } +// Node node; +// if (nodeIterator.hasNext()) +// node = nodeIterator.nextNode(); +// else +// return null; +// +// if (nodeIterator.hasNext()) +// throw new RuntimeException("Query returned more than one node."); +// return node; +// } + + /** Returns the home node of the session user or null if none was found. */ + public static Node getUserHome(Session session) { + String userID = session.getUserID(); + return getUserHome(session, userID); + } + + /** Whether this node is the home of the user of the underlying session. */ + public static boolean isUserHome(Node node) { + try { + String userID = node.getSession().getUserID(); + return node.hasProperty(Property.JCR_ID) && node.getProperty(Property.JCR_ID).getString().equals(userID); + } catch (RepositoryException e) { + throw new IllegalStateException(e); + } + } + + /** + * Translate the path to this node into a path containing the name of the + * repository and the name of the workspace. + */ + public static String getDataPath(String cn, Node node) { + assert node != null; + StringBuilder buf = new StringBuilder(CmsConstants.PATH_DATA); + try { + return buf.append('/').append(cn).append('/').append(node.getSession().getWorkspace().getName()) + .append(node.getPath()).toString(); + } catch (RepositoryException e) { + throw new IllegalStateException("Cannot get data path for " + node + " in repository " + cn, e); + } + } + + /** + * Translate the path to this node into a path containing the name of the + * repository and the name of the workspace. + */ + public static String getDataPath(Node node) { + return getDataPath(CmsConstants.NODE, node); + } + + /** + * Open a JCR session with full read/write rights on the data, as + * {@link CmsConstants#ROLE_USER_ADMIN}, using the + * {@link CmsAuth#LOGIN_CONTEXT_DATA_ADMIN} login context. For security + * hardened deployement, use {@link AuthPermission} on this login context. + */ + public static Session openDataAdminSession(Repository repository, String workspaceName) { + ClassLoader currentCl = Thread.currentThread().getContextClassLoader(); + LoginContext loginContext; + try { + loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_DATA_ADMIN); + loginContext.login(); + } catch (LoginException e1) { + throw new RuntimeException("Could not login as data admin", e1); + } finally { + Thread.currentThread().setContextClassLoader(currentCl); + } + return Subject.doAs(loginContext.getSubject(), new PrivilegedAction() { + + @Override + public Session run() { + try { + return repository.login(workspaceName); + } catch (NoSuchWorkspaceException e) { + throw new IllegalArgumentException("No workspace " + workspaceName + " available", e); + } catch (RepositoryException e) { + throw new RuntimeException("Cannot open data admin session", e); + } + } + + }); + } + + /** Singleton. */ + private CmsJcrUtils() { + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java new file mode 100644 index 000000000..04c5d2d8c --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java @@ -0,0 +1,201 @@ +package org.argeo.cms.jcr.acr; + +import java.util.Calendar; +import java.util.Iterator; +import java.util.Optional; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.Property; +import javax.jcr.PropertyIterator; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; +import javax.jcr.Value; +import javax.jcr.nodetype.NodeType; +import javax.xml.namespace.QName; + +import org.argeo.api.acr.Content; +import org.argeo.api.acr.spi.AbstractContent; +import org.argeo.api.acr.spi.ProvidedSession; +import org.argeo.jcr.Jcr; +import org.argeo.jcr.JcrException; + +public class JcrContent extends AbstractContent { + private Node jcrNode; + + private JcrContentProvider provider; + private ProvidedSession session; + + protected JcrContent(ProvidedSession session, JcrContentProvider provider, Node node) { + this.session = session; + this.provider = provider; + this.jcrNode = node; + } + + @Override + public QName getName() { + return session.parsePrefixedName(Jcr.getName(jcrNode)); + } + + @Override + public Optional get(QName key, Class clss) { + if (isDefaultAttrTypeRequested(clss)) { + return Optional.of((A) get(jcrNode, key.toString())); + } + return Optional.of((A) Jcr.get(jcrNode, key.toString())); + } + + @Override + public Iterator iterator() { + try { + return new JcrContentIterator(jcrNode.getNodes()); + } catch (RepositoryException e) { + throw new JcrException("Cannot list children of " + jcrNode, e); + } + } + + @Override + protected Iterable keys() { + return new Iterable() { + + @Override + public Iterator iterator() { + try { + PropertyIterator propertyIterator = jcrNode.getProperties(); + return new JcrKeyIterator(provider, propertyIterator); + } catch (RepositoryException e) { + throw new JcrException("Cannot retrive properties from " + jcrNode, e); + } + } + }; + } + + public Node getJcrNode() { + return jcrNode; + } + + /** Cast to a standard Java object. */ + static Object get(Node node, String property) { + try { + Value value = node.getProperty(property).getValue(); + switch (value.getType()) { + case PropertyType.STRING: + return value.getString(); + case PropertyType.DOUBLE: + return (Double) value.getDouble(); + case PropertyType.LONG: + return (Long) value.getLong(); + case PropertyType.BOOLEAN: + return (Boolean) value.getBoolean(); + case PropertyType.DATE: + Calendar calendar = value.getDate(); + return calendar.toInstant(); + case PropertyType.BINARY: + throw new IllegalArgumentException("Binary is not supported as an attribute"); + default: + return value.getString(); + } + } catch (RepositoryException e) { + throw new JcrException("Cannot cast value from " + property + " of node " + node, e); + } + } + + class JcrContentIterator implements Iterator { + private final NodeIterator nodeIterator; + // we keep track in order to be able to delete it + private JcrContent current = null; + + protected JcrContentIterator(NodeIterator nodeIterator) { + this.nodeIterator = nodeIterator; + } + + @Override + public boolean hasNext() { + return nodeIterator.hasNext(); + } + + @Override + public Content next() { + current = new JcrContent(session, provider, nodeIterator.nextNode()); + return current; + } + + @Override + public void remove() { + if (current != null) { + Jcr.remove(current.getJcrNode()); + } + } + + } + + @Override + public Content getParent() { + return new JcrContent(session, provider, Jcr.getParent(getJcrNode())); + } + + @Override + public Content add(QName name, QName... classes) { + if (classes.length > 0) { + QName primaryType = classes[0]; + Node child = Jcr.addNode(getJcrNode(), name.toString(), primaryType.toString()); + for (int i = 1; i < classes.length; i++) { + try { + child.addMixin(classes[i].toString()); + } catch (RepositoryException e) { + throw new JcrException("Cannot add child to " + getJcrNode(), e); + } + } + + } else { + Jcr.addNode(getJcrNode(), name.toString(), NodeType.NT_UNSTRUCTURED); + } + return null; + } + + @Override + public void remove() { + Jcr.remove(getJcrNode()); + } + + @Override + protected void removeAttr(QName key) { + Property property = Jcr.getProperty(getJcrNode(), key.toString()); + if (property != null) { + try { + property.remove(); + } catch (RepositoryException e) { + throw new JcrException("Cannot remove property " + key + " from " + getJcrNode(), e); + } + } + + } + + class JcrKeyIterator implements Iterator { + private final JcrContentProvider contentSession; + private final PropertyIterator propertyIterator; + + protected JcrKeyIterator(JcrContentProvider contentSession, PropertyIterator propertyIterator) { + this.contentSession = contentSession; + this.propertyIterator = propertyIterator; + } + + @Override + public boolean hasNext() { + return propertyIterator.hasNext(); + } + + @Override + public QName next() { + Property property = null; + try { + property = propertyIterator.nextProperty(); + // TODO map standard property names + return session.parsePrefixedName(property.getName()); + } catch (RepositoryException e) { + throw new JcrException("Cannot retrieve property " + property, null); + } + } + + } +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentProvider.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentProvider.java new file mode 100644 index 000000000..ef8e375d0 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentProvider.java @@ -0,0 +1,70 @@ +package org.argeo.cms.jcr.acr; + +import java.util.Arrays; +import java.util.Iterator; + +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.xml.namespace.NamespaceContext; + +import org.argeo.api.acr.Content; +import org.argeo.api.acr.spi.ContentProvider; +import org.argeo.api.acr.spi.ProvidedSession; +import org.argeo.cms.jcr.CmsJcrUtils; +import org.argeo.jcr.JcrException; +import org.argeo.jcr.JcrUtils; + +public class JcrContentProvider implements ContentProvider, NamespaceContext { + private Repository jcrRepository; + private Session adminSession; + + public void init() { + adminSession = CmsJcrUtils.openDataAdminSession(jcrRepository, null); + } + + public void destroy() { + JcrUtils.logoutQuietly(adminSession); + } + + public void setJcrRepository(Repository jcrRepository) { + this.jcrRepository = jcrRepository; + } + + @Override + public Content get(ProvidedSession session, String mountPath, String relativePath) { + // TODO Auto-generated method stub + return null; + } + + /* + * NAMESPACE CONTEXT + */ + @Override + public String getNamespaceURI(String prefix) { + try { + return adminSession.getNamespaceURI(prefix); + } catch (RepositoryException e) { + throw new JcrException(e); + } + } + + @Override + public String getPrefix(String namespaceURI) { + try { + return adminSession.getNamespacePrefix(namespaceURI); + } catch (RepositoryException e) { + throw new JcrException(e); + } + } + + @Override + public Iterator getPrefixes(String namespaceURI) { + try { + return Arrays.asList(adminSession.getNamespacePrefix(namespaceURI)).iterator(); + } catch (RepositoryException e) { + throw new JcrException(e); + } + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/argeo.cnd b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/argeo.cnd new file mode 100644 index 000000000..c9e6ee7e2 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/argeo.cnd @@ -0,0 +1,34 @@ + + +// GENERIC TYPES +[argeo:remoteRepository] > nt:unstructured +- argeo:uri (STRING) +- argeo:userID (STRING) ++ argeo:password (argeo:encrypted) + +// TABULAR CONTENT +[argeo:table] > nt:file ++ * (argeo:column) * + +[argeo:column] > mix:title +- jcr:requiredType (STRING) = 'STRING' + +[argeo:csv] > nt:resource + +// CRYPTO +[argeo:encrypted] +mixin +// initialization vector used by some algorithms +- argeo:iv (BINARY) + +[argeo:pbeKeySpec] +mixin +- argeo:secretKeyFactory (STRING) +- argeo:salt (BINARY) +- argeo:iterationCount (LONG) +- argeo:keyLength (LONG) +- argeo:secretKeyEncryption (STRING) + +[argeo:pbeSpec] > argeo:pbeKeySpec +mixin +- argeo:cipher (STRING) diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/dn.cnd b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/dn.cnd new file mode 100644 index 000000000..80849be95 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/dn.cnd @@ -0,0 +1,10 @@ +// DN (see https://tools.ietf.org/html/rfc4514) + + + + + + + + + diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrDeployment.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrDeployment.java new file mode 100644 index 000000000..340d13782 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrDeployment.java @@ -0,0 +1,481 @@ +package org.argeo.cms.jcr.internal; + +import static org.argeo.cms.osgi.DataModelNamespace.CMS_DATA_MODEL_NAMESPACE; +import static org.osgi.service.http.whiteboard.HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX; + +import java.io.File; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Hashtable; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.security.auth.callback.CallbackHandler; +import javax.servlet.Servlet; + +import org.apache.jackrabbit.commons.cnd.CndImporter; +import org.apache.jackrabbit.core.RepositoryContext; +import org.apache.jackrabbit.core.RepositoryImpl; +import org.argeo.api.cms.CmsConstants; +import org.argeo.api.cms.CmsDeployment; +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.ArgeoNames; +import org.argeo.cms.internal.jcr.JcrInitUtils; +import org.argeo.cms.jcr.CmsJcrUtils; +import org.argeo.cms.jcr.internal.servlet.CmsRemotingServlet; +import org.argeo.cms.jcr.internal.servlet.CmsWebDavServlet; +import org.argeo.cms.jcr.internal.servlet.JcrHttpUtils; +import org.argeo.cms.osgi.DataModelNamespace; +import org.argeo.cms.security.CryptoKeyring; +import org.argeo.cms.security.Keyring; +import org.argeo.jcr.Jcr; +import org.argeo.jcr.JcrException; +import org.argeo.jcr.JcrUtils; +import org.argeo.util.LangUtils; +import org.argeo.util.naming.LdapAttrs; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; +import org.osgi.framework.wiring.BundleCapability; +import org.osgi.framework.wiring.BundleWire; +import org.osgi.framework.wiring.BundleWiring; +import org.osgi.service.cm.ManagedService; +import org.osgi.service.http.whiteboard.HttpWhiteboardConstants; +import org.osgi.util.tracker.ServiceTracker; + +/** Implementation of a CMS deployment. */ +public class CmsJcrDeployment { + private final CmsLog log = CmsLog.getLog(getClass()); + private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); + + private DataModels dataModels; + private String webDavConfig = JcrHttpUtils.WEBDAV_CONFIG; + + private boolean argeoDataModelExtensionsAvailable = false; + + // Readiness + private boolean nodeAvailable = false; + + CmsDeployment cmsDeployment; + + public CmsJcrDeployment() { +// initTrackers(); + } + + public void start() { + dataModels = new DataModels(bc); + + ServiceTracker repoContextSt = new RepositoryContextStc(); + repoContextSt.open(); + //KernelUtils.asyncOpen(repoContextSt); + +// nodeDeployment = CmsJcrActivator.getService(NodeDeployment.class); + + JcrInitUtils.addToDeployment(cmsDeployment); + + } + + public void stop() { +// if (nodeHttp != null) +// nodeHttp.destroy(); + + try { + for (ServiceReference sr : bc + .getServiceReferences(JackrabbitLocalRepository.class, null)) { + bc.getService(sr).destroy(); + } + } catch (InvalidSyntaxException e1) { + log.error("Cannot clean repositories", e1); + } + + } + + public void setCmsDeployment(CmsDeployment cmsDeployment) { + this.cmsDeployment = cmsDeployment; + } + + /** + * Checks whether the deployment is available according to expectations, and + * mark it as available. + */ +// private synchronized void checkReadiness() { +// if (isAvailable()) +// return; +// if (nodeAvailable && userAdminAvailable && (httpExpected ? httpAvailable : true)) { +// String data = KernelUtils.getFrameworkProp(KernelUtils.OSGI_INSTANCE_AREA); +// String state = KernelUtils.getFrameworkProp(KernelUtils.OSGI_CONFIGURATION_AREA); +// availableSince = System.currentTimeMillis(); +// long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime(); +// String jvmUptimeStr = " in " + (jvmUptime / 1000) + "." + (jvmUptime % 1000) + "s"; +// log.info("## ARGEO NODE AVAILABLE" + (log.isDebugEnabled() ? jvmUptimeStr : "") + " ##"); +// if (log.isDebugEnabled()) { +// log.debug("## state: " + state); +// if (data != null) +// log.debug("## data: " + data); +// } +// long begin = bc.getService(bc.getServiceReference(NodeState.class)).getAvailableSince(); +// long initDuration = System.currentTimeMillis() - begin; +// if (log.isTraceEnabled()) +// log.trace("Kernel initialization took " + initDuration + "ms"); +// tributeToFreeSoftware(initDuration); +// } +// } + + private void prepareNodeRepository(Repository deployedNodeRepository, List publishAsLocalRepo) { +// if (availableSince != null) { +// throw new IllegalStateException("Deployment is already available"); +// } + + // home + prepareDataModel(CmsConstants.NODE_REPOSITORY, deployedNodeRepository, publishAsLocalRepo); + + // init from backup +// if (deployConfig.isFirstInit()) { +// Path restorePath = Paths.get(System.getProperty("user.dir"), "restore"); +// if (Files.exists(restorePath)) { +// if (log.isDebugEnabled()) +// log.debug("Found backup " + restorePath + ", restoring it..."); +// LogicalRestore logicalRestore = new LogicalRestore(bc, deployedNodeRepository, restorePath); +// KernelUtils.doAsDataAdmin(logicalRestore); +// log.info("Restored backup from " + restorePath); +// } +// } + + // init from repository + Collection> initRepositorySr; + try { + initRepositorySr = bc.getServiceReferences(Repository.class, + "(" + CmsConstants.CN + "=" + CmsConstants.NODE_INIT + ")"); + } catch (InvalidSyntaxException e1) { + throw new IllegalArgumentException(e1); + } + Iterator> it = initRepositorySr.iterator(); + while (it.hasNext()) { + ServiceReference sr = it.next(); + Object labeledUri = sr.getProperties().get(LdapAttrs.labeledURI.name()); + Repository initRepository = bc.getService(sr); + if (log.isDebugEnabled()) + log.debug("Found init repository " + labeledUri + ", copying it..."); + initFromRepository(deployedNodeRepository, initRepository); + log.info("Node repository initialised from " + labeledUri); + } + } + + /** Init from a (typically remote) repository. */ + private void initFromRepository(Repository deployedNodeRepository, Repository initRepository) { + Session initSession = null; + try { + initSession = initRepository.login(); + workspaces: for (String workspaceName : initSession.getWorkspace().getAccessibleWorkspaceNames()) { + if ("security".equals(workspaceName)) + continue workspaces; + if (log.isDebugEnabled()) + log.debug("Copying workspace " + workspaceName + " from init repository..."); + long begin = System.currentTimeMillis(); + Session targetSession = null; + Session sourceSession = null; + try { + try { + targetSession = CmsJcrUtils.openDataAdminSession(deployedNodeRepository, workspaceName); + } catch (IllegalArgumentException e) {// no such workspace + Session adminSession = CmsJcrUtils.openDataAdminSession(deployedNodeRepository, null); + try { + adminSession.getWorkspace().createWorkspace(workspaceName); + } finally { + Jcr.logout(adminSession); + } + targetSession = CmsJcrUtils.openDataAdminSession(deployedNodeRepository, workspaceName); + } + sourceSession = initRepository.login(workspaceName); +// JcrUtils.copyWorkspaceXml(sourceSession, targetSession); + // TODO deal with referenceable nodes + JcrUtils.copy(sourceSession.getRootNode(), targetSession.getRootNode()); + targetSession.save(); + long duration = System.currentTimeMillis() - begin; + if (log.isDebugEnabled()) + log.debug("Copied workspace " + workspaceName + " from init repository in " + (duration / 1000) + + " s"); + } catch (Exception e) { + log.error("Cannot copy workspace " + workspaceName + " from init repository.", e); + } finally { + Jcr.logout(sourceSession); + Jcr.logout(targetSession); + } + } + } catch (RepositoryException e) { + throw new JcrException(e); + } finally { + Jcr.logout(initSession); + } + } + + private void prepareHomeRepository(RepositoryImpl deployedRepository) { + Session adminSession = KernelUtils.openAdminSession(deployedRepository); + try { + argeoDataModelExtensionsAvailable = Arrays + .asList(adminSession.getWorkspace().getNamespaceRegistry().getURIs()) + .contains(ArgeoNames.ARGEO_NAMESPACE); + } catch (RepositoryException e) { + log.warn("Cannot check whether Argeo namespace is registered assuming it isn't.", e); + argeoDataModelExtensionsAvailable = false; + } finally { + JcrUtils.logoutQuietly(adminSession); + } + + // Publish home with the highest service ranking + Hashtable regProps = new Hashtable<>(); + regProps.put(CmsConstants.CN, CmsConstants.EGO_REPOSITORY); + regProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE); + Repository egoRepository = new EgoRepository(deployedRepository, false); + bc.registerService(Repository.class, egoRepository, regProps); + registerRepositoryServlets(CmsConstants.EGO_REPOSITORY, egoRepository); + + // Keyring only if Argeo extensions are available + if (argeoDataModelExtensionsAvailable) { + new ServiceTracker(bc, CallbackHandler.class, null) { + + @Override + public CallbackHandler addingService(ServiceReference reference) { + NodeKeyRing nodeKeyring = new NodeKeyRing(egoRepository); + CallbackHandler callbackHandler = bc.getService(reference); + nodeKeyring.setDefaultCallbackHandler(callbackHandler); + bc.registerService(LangUtils.names(Keyring.class, CryptoKeyring.class, ManagedService.class), + nodeKeyring, LangUtils.dict(Constants.SERVICE_PID, CmsConstants.NODE_KEYRING_PID)); + return callbackHandler; + } + + }.open(); + } + } + + /** Session is logged out. */ + private void prepareDataModel(String cn, Repository repository, List publishAsLocalRepo) { + Session adminSession = KernelUtils.openAdminSession(repository); + try { + Set processed = new HashSet(); + bundles: for (Bundle bundle : bc.getBundles()) { + BundleWiring wiring = bundle.adapt(BundleWiring.class); + if (wiring == null) + continue bundles; + if (CmsConstants.NODE_REPOSITORY.equals(cn))// process all data models + processWiring(cn, adminSession, wiring, processed, false, publishAsLocalRepo); + else { + List capabilities = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE); + for (BundleCapability capability : capabilities) { + String dataModelName = (String) capability.getAttributes().get(DataModelNamespace.NAME); + if (dataModelName.equals(cn))// process only own data model + processWiring(cn, adminSession, wiring, processed, false, publishAsLocalRepo); + } + } + } + } finally { + JcrUtils.logoutQuietly(adminSession); + } + } + + private void processWiring(String cn, Session adminSession, BundleWiring wiring, Set processed, + boolean importListedAbstractModels, List publishAsLocalRepo) { + // recursively process requirements first + List requiredWires = wiring.getRequiredWires(CMS_DATA_MODEL_NAMESPACE); + for (BundleWire wire : requiredWires) { + processWiring(cn, adminSession, wire.getProviderWiring(), processed, true, publishAsLocalRepo); + } + + List capabilities = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE); + capabilities: for (BundleCapability capability : capabilities) { + if (!importListedAbstractModels + && KernelUtils.asBoolean((String) capability.getAttributes().get(DataModelNamespace.ABSTRACT))) { + continue capabilities; + } + boolean publish = registerDataModelCapability(cn, adminSession, capability, processed); + if (publish) + publishAsLocalRepo.add((String) capability.getAttributes().get(DataModelNamespace.NAME)); + } + } + + private boolean registerDataModelCapability(String cn, Session adminSession, BundleCapability capability, + Set processed) { + Map attrs = capability.getAttributes(); + String name = (String) attrs.get(DataModelNamespace.NAME); + if (processed.contains(name)) { + if (log.isTraceEnabled()) + log.trace("Data model " + name + " has already been processed"); + return false; + } + + // CND + String path = (String) attrs.get(DataModelNamespace.CND); + if (path != null) { + File dataModel = bc.getBundle().getDataFile("dataModels/" + path); + if (!dataModel.exists()) { + URL url = capability.getRevision().getBundle().getResource(path); + if (url == null) + throw new IllegalArgumentException("No data model '" + name + "' found under path " + path); + try (Reader reader = new InputStreamReader(url.openStream())) { + CndImporter.registerNodeTypes(reader, adminSession, true); + processed.add(name); + dataModel.getParentFile().mkdirs(); + dataModel.createNewFile(); + if (log.isDebugEnabled()) + log.debug("Registered CND " + url); + } catch (Exception e) { + log.error("Cannot import CND " + url, e); + } + } + } + + if (KernelUtils.asBoolean((String) attrs.get(DataModelNamespace.ABSTRACT))) + return false; + // Non abstract + boolean isStandalone = isStandalone(name); + boolean publishLocalRepo; + if (isStandalone && name.equals(cn))// includes the node itself + publishLocalRepo = true; + else if (!isStandalone && cn.equals(CmsConstants.NODE_REPOSITORY)) + publishLocalRepo = true; + else + publishLocalRepo = false; + + return publishLocalRepo; + } + + boolean isStandalone(String dataModelName) { + return cmsDeployment.getProps(CmsConstants.NODE_REPOS_FACTORY_PID, dataModelName) != null; + } + + private void publishLocalRepo(String dataModelName, Repository repository) { + Hashtable properties = new Hashtable<>(); + properties.put(CmsConstants.CN, dataModelName); + LocalRepository localRepository; + String[] classes; + if (repository instanceof RepositoryImpl) { + localRepository = new JackrabbitLocalRepository((RepositoryImpl) repository, dataModelName); + classes = new String[] { Repository.class.getName(), LocalRepository.class.getName(), + JackrabbitLocalRepository.class.getName() }; + } else { + localRepository = new LocalRepository(repository, dataModelName); + classes = new String[] { Repository.class.getName(), LocalRepository.class.getName() }; + } + bc.registerService(classes, localRepository, properties); + + // TODO make it configurable + registerRepositoryServlets(dataModelName, localRepository); + if (log.isTraceEnabled()) + log.trace("Published data model " + dataModelName); + } + +// @Override +// public synchronized Long getAvailableSince() { +// return availableSince; +// } +// +// public synchronized boolean isAvailable() { +// return availableSince != null; +// } + + protected void registerRepositoryServlets(String alias, Repository repository) { + // FIXME re-enable it with a proper class loader +// registerRemotingServlet(alias, repository); +// registerWebdavServlet(alias, repository); + } + + protected void registerWebdavServlet(String alias, Repository repository) { + CmsWebDavServlet webdavServlet = new CmsWebDavServlet(alias, repository); + Hashtable ip = new Hashtable<>(); + ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsWebDavServlet.INIT_PARAM_RESOURCE_CONFIG, webDavConfig); + ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsWebDavServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, + "/" + alias); + + ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, "/" + alias + "/*"); + ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT, + "(" + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_PATH + "=" + CmsConstants.PATH_DATA + ")"); + bc.registerService(Servlet.class, webdavServlet, ip); + } + + protected void registerRemotingServlet(String alias, Repository repository) { + CmsRemotingServlet remotingServlet = new CmsRemotingServlet(alias, repository); + Hashtable ip = new Hashtable<>(); + ip.put(CmsConstants.CN, alias); + // Properties ip = new Properties(); + ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, + "/" + alias); + ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_AUTHENTICATE_HEADER, + "Negotiate"); + + // Looks like a bug in Jackrabbit remoting init + Path tmpDir; + try { + tmpDir = Files.createTempDirectory("remoting_" + alias); + } catch (IOException e) { + throw new RuntimeException("Cannot create temp directory for remoting servlet", e); + } + ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_HOME, tmpDir.toString()); + ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_TMP_DIRECTORY, + "remoting_" + alias); + ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_PROTECTED_HANDLERS_CONFIG, + JcrHttpUtils.DEFAULT_PROTECTED_HANDLERS); + ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_CREATE_ABSOLUTE_URI, "false"); + + ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, "/" + alias + "/*"); + ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT, + "(" + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_PATH + "=" + CmsConstants.PATH_JCR + ")"); + bc.registerService(Servlet.class, remotingServlet, ip); + } + + private class RepositoryContextStc extends ServiceTracker { + + public RepositoryContextStc() { + super(bc, RepositoryContext.class, null); + } + + @Override + public RepositoryContext addingService(ServiceReference reference) { + RepositoryContext repoContext = bc.getService(reference); + String cn = (String) reference.getProperty(CmsConstants.CN); + if (cn != null) { + List publishAsLocalRepo = new ArrayList<>(); + if (cn.equals(CmsConstants.NODE_REPOSITORY)) { +// JackrabbitDataModelMigration.clearRepositoryCaches(repoContext.getRepositoryConfig()); + prepareNodeRepository(repoContext.getRepository(), publishAsLocalRepo); + // TODO separate home repository + prepareHomeRepository(repoContext.getRepository()); + registerRepositoryServlets(cn, repoContext.getRepository()); + nodeAvailable = true; +// checkReadiness(); + } else { + prepareDataModel(cn, repoContext.getRepository(), publishAsLocalRepo); + } + // Publish all at once, so that bundles with multiple CNDs are consistent + for (String dataModelName : publishAsLocalRepo) + publishLocalRepo(dataModelName, repoContext.getRepository()); + } + return repoContext; + } + + @Override + public void modifiedService(ServiceReference reference, RepositoryContext service) { + } + + @Override + public void removedService(ServiceReference reference, RepositoryContext service) { + } + + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrFsProvider.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrFsProvider.java new file mode 100644 index 000000000..0099b3bed --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrFsProvider.java @@ -0,0 +1,133 @@ +package org.argeo.cms.jcr.internal; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystemAlreadyExistsException; +import java.nio.file.Path; +import java.nio.file.spi.FileSystemProvider; +import java.util.HashMap; +import java.util.Map; + +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.RepositoryFactory; +import javax.jcr.Session; +import javax.jcr.nodetype.NodeType; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.cms.auth.CurrentUser; +import org.argeo.cms.jcr.CmsJcrUtils; +import org.argeo.jackrabbit.fs.AbstractJackrabbitFsProvider; +import org.argeo.jcr.fs.JcrFileSystem; +import org.argeo.jcr.fs.JcrFileSystemProvider; +import org.argeo.jcr.fs.JcrFsException; + +/** Implementation of an {@link FileSystemProvider} based on Jackrabbit. */ +public class CmsJcrFsProvider extends AbstractJackrabbitFsProvider { + private Map fileSystems = new HashMap<>(); + + private RepositoryFactory repositoryFactory; + private Repository repository; + + @Override + public String getScheme() { + return CmsConstants.SCHEME_NODE; + } + + @Override + public FileSystem newFileSystem(URI uri, Map env) throws IOException { +// BundleContext bc = FrameworkUtil.getBundle(CmsJcrFsProvider.class).getBundleContext(); + String username = CurrentUser.getUsername(); + if (username == null) { + // TODO deal with anonymous + return null; + } + if (fileSystems.containsKey(username)) + throw new FileSystemAlreadyExistsException("CMS file system already exists for user " + username); + + try { + String host = uri.getHost(); + if (host != null && !host.trim().equals("")) { + URI repoUri = new URI("http", uri.getUserInfo(), uri.getHost(), uri.getPort(), "/jcr/node", null, null); +// RepositoryFactory repositoryFactory = bc.getService(bc.getServiceReference(RepositoryFactory.class)); + Repository repository = CmsJcrUtils.getRepositoryByUri(repositoryFactory, repoUri.toString()); + CmsFileSystem fileSystem = new CmsFileSystem(this, repository); + fileSystems.put(username, fileSystem); + return fileSystem; + } else { +// Repository repository = bc.getService( +// bc.getServiceReferences(Repository.class, "(cn=" + CmsConstants.EGO_REPOSITORY + ")") +// .iterator().next()); + + // Session session = repository.login(); + CmsFileSystem fileSystem = new CmsFileSystem(this, repository); + fileSystems.put(username, fileSystem); + return fileSystem; + } + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Cannot open file system " + uri + " for user " + username, e); + } + } + + @Override + public FileSystem getFileSystem(URI uri) { + return currentUserFileSystem(); + } + + @Override + public Path getPath(URI uri) { + JcrFileSystem fileSystem = currentUserFileSystem(); + String path = uri.getPath(); + if (fileSystem == null) + try { + fileSystem = (JcrFileSystem) newFileSystem(uri, new HashMap()); + } catch (IOException e) { + throw new JcrFsException("Could not autocreate file system", e); + } + return fileSystem.getPath(path); + } + + protected JcrFileSystem currentUserFileSystem() { + String username = CurrentUser.getUsername(); + return fileSystems.get(username); + } + + public Node getUserHome(Repository repository) { + try { + Session session = repository.login(CmsConstants.HOME_WORKSPACE); + return CmsJcrUtils.getUserHome(session); + } catch (RepositoryException e) { + throw new IllegalStateException("Cannot get user home", e); + } + } + + public void setRepositoryFactory(RepositoryFactory repositoryFactory) { + this.repositoryFactory = repositoryFactory; + } + + public void setRepository(Repository repository) { + this.repository = repository; + } + + static class CmsFileSystem extends JcrFileSystem { + public CmsFileSystem(JcrFileSystemProvider provider, Repository repository) throws IOException { + super(provider, repository); + } + + public boolean skipNode(Node node) throws RepositoryException { +// if (node.isNodeType(NodeType.NT_HIERARCHY_NODE) || node.isNodeType(NodeTypes.NODE_USER_HOME) +// || node.isNodeType(NodeTypes.NODE_GROUP_HOME)) + if (node.isNodeType(NodeType.NT_HIERARCHY_NODE)) + return false; + // FIXME Better identifies home + if (node.hasProperty(Property.JCR_ID)) + return false; + return true; + } + + } +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsPaths.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsPaths.java new file mode 100644 index 000000000..e7f5a55af --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsPaths.java @@ -0,0 +1,18 @@ +package org.argeo.cms.jcr.internal; + +import java.nio.file.Path; + +/** Centralises access to the default node deployment directories. */ +public class CmsPaths { + public static Path getRepoDirPath(String cn) { + return KernelUtils.getOsgiInstancePath(KernelConstants.DIR_REPOS + '/' + cn); + } + + public static Path getRepoIndexesBase() { + return KernelUtils.getOsgiInstancePath(KernelConstants.DIR_INDEXES); + } + + /** Singleton. */ + private CmsPaths() { + } +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsWorkspaceIndexer.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsWorkspaceIndexer.java new file mode 100644 index 000000000..69b98dc3a --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsWorkspaceIndexer.java @@ -0,0 +1,342 @@ +package org.argeo.cms.jcr.internal; + +import java.util.GregorianCalendar; +import java.util.concurrent.LinkedBlockingDeque; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.Value; +import javax.jcr.nodetype.NodeType; +import javax.jcr.observation.Event; +import javax.jcr.observation.EventIterator; +import javax.jcr.observation.EventListener; +import javax.jcr.version.VersionManager; + +import org.apache.jackrabbit.api.JackrabbitValue; +import org.apache.jackrabbit.core.RepositoryImpl; +import org.argeo.api.cms.CmsLog; +import org.argeo.jcr.JcrUtils; + +/** Ensure consistency of files, folder and last modified nodes. */ +class CmsWorkspaceIndexer implements EventListener { + private final static CmsLog log = CmsLog.getLog(CmsWorkspaceIndexer.class); + +// private final static String MIX_ETAG = "mix:etag"; + private final static String JCR_ETAG = "jcr:etag"; +// private final static String JCR_LAST_MODIFIED = "jcr:lastModified"; +// private final static String JCR_LAST_MODIFIED_BY = "jcr:lastModifiedBy"; +// private final static String JCR_MIXIN_TYPES = "jcr:mixinTypes"; + private final static String JCR_DATA = "jcr:data"; + private final static String JCR_CONTENT = "jcr:data"; + + private String cn; + private String workspaceName; + private RepositoryImpl repositoryImpl; + private Session session; + private VersionManager versionManager; + + private LinkedBlockingDeque toProcess = new LinkedBlockingDeque<>(); + private IndexingThread indexingThread; + private AtomicBoolean stopping = new AtomicBoolean(false); + + public CmsWorkspaceIndexer(RepositoryImpl repositoryImpl, String cn, String workspaceName) + throws RepositoryException { + this.cn = cn; + this.workspaceName = workspaceName; + this.repositoryImpl = repositoryImpl; + } + + public void init() { + session = KernelUtils.openAdminSession(repositoryImpl, workspaceName); + try { + String[] nodeTypes = { NodeType.NT_FILE, NodeType.MIX_LAST_MODIFIED }; + session.getWorkspace().getObservationManager().addEventListener(this, + Event.NODE_ADDED | Event.PROPERTY_CHANGED, "/", true, null, nodeTypes, true); + versionManager = session.getWorkspace().getVersionManager(); + + indexingThread = new IndexingThread(); + indexingThread.start(); + } catch (RepositoryException e1) { + throw new IllegalStateException(e1); + } + } + + public void destroy() { + stopping.set(true); + indexingThread.interrupt(); + // TODO make it configurable + try { + indexingThread.join(10 * 60 * 1000); + } catch (InterruptedException e1) { + log.warn("Indexing thread interrupted. Will log out session."); + } + + try { + session.getWorkspace().getObservationManager().removeEventListener(this); + } catch (RepositoryException e) { + if (log.isTraceEnabled()) + log.warn("Cannot unregistered JCR event listener", e); + } finally { + JcrUtils.logoutQuietly(session); + } + } + + private synchronized void processEvents(EventIterator events) { + long begin = System.currentTimeMillis(); + long count = 0; + while (events.hasNext()) { + Event event = events.nextEvent(); + try { + toProcess.put(event); + } catch (InterruptedException e) { + e.printStackTrace(); + } +// processEvent(event); + count++; + } + long duration = System.currentTimeMillis() - begin; + if (log.isTraceEnabled()) + log.trace("Processed " + count + " events in " + duration + " ms"); + notifyAll(); + } + + protected synchronized void processEvent(Event event) { + try { + String eventPath = event.getPath(); + if (event.getType() == Event.NODE_ADDED) { + if (!versionManager.isCheckedOut(eventPath)) + return;// ignore checked-in nodes + if (log.isTraceEnabled()) + log.trace("NODE_ADDED " + eventPath); +// session.refresh(true); + session.refresh(false); + Node node = session.getNode(eventPath); + Node parentNode = node.getParent(); + if (parentNode.isNodeType(NodeType.NT_FILE)) { + if (node.isNodeType(NodeType.NT_UNSTRUCTURED)) { + if (!node.isNodeType(NodeType.MIX_LAST_MODIFIED)) + node.addMixin(NodeType.MIX_LAST_MODIFIED); + Property property = node.getProperty(Property.JCR_DATA); + String etag = toEtag(property.getValue()); + session.save(); + node.setProperty(JCR_ETAG, etag); + if (log.isTraceEnabled()) + log.trace("ETag and last modified added to new " + node); + } else if (node.isNodeType(NodeType.NT_RESOURCE)) { +// if (!node.isNodeType(MIX_ETAG)) +// node.addMixin(MIX_ETAG); +// session.save(); +// Property property = node.getProperty(Property.JCR_DATA); +// String etag = toEtag(property.getValue()); +// node.setProperty(JCR_ETAG, etag); +// session.save(); + } +// setLastModifiedRecursive(parentNode, event); +// session.save(); +// if (log.isTraceEnabled()) +// log.trace("ETag and last modified added to new " + node); + } + +// if (node.isNodeType(NodeType.NT_FOLDER)) { +// setLastModifiedRecursive(node, event); +// session.save(); +// if (log.isTraceEnabled()) +// log.trace("Last modified added to new " + node); +// } + } else if (event.getType() == Event.PROPERTY_CHANGED) { + String propertyName = extractItemName(eventPath); + // skip if last modified properties are explicitly set + if (!propertyName.equals(JCR_DATA)) + return; +// if (propertyName.equals(JCR_LAST_MODIFIED)) +// return; +// if (propertyName.equals(JCR_LAST_MODIFIED_BY)) +// return; +// if (propertyName.equals(JCR_MIXIN_TYPES)) +// return; +// if (propertyName.equals(JCR_ETAG)) +// return; + + if (log.isTraceEnabled()) + log.trace("PROPERTY_CHANGED " + eventPath); + + if (!session.propertyExists(eventPath)) + return; + session.refresh(false); + Property property = session.getProperty(eventPath); + Node node = property.getParent(); + if (property.getType() == PropertyType.BINARY && propertyName.equals(JCR_DATA) + && node.isNodeType(NodeType.NT_UNSTRUCTURED)) { + String etag = toEtag(property.getValue()); + node.setProperty(JCR_ETAG, etag); + Node parentNode = node.getParent(); + if (parentNode.isNodeType(NodeType.MIX_LAST_MODIFIED)) { + setLastModified(parentNode, event); + } + if (log.isTraceEnabled()) + log.trace("ETag and last modified updated for " + node); + } +// setLastModified(node, event); +// session.save(); +// if (log.isTraceEnabled()) +// log.trace("ETag and last modified updated for " + node); + } else if (event.getType() == Event.NODE_REMOVED) { + String removeNodePath = eventPath; + String nodeName = extractItemName(eventPath); + if (JCR_CONTENT.equals(nodeName)) // parent is a file, deleted anyhow + return; + if (log.isTraceEnabled()) + log.trace("NODE_REMOVED " + eventPath); +// String parentPath = JcrUtils.parentPath(removeNodePath); +// session.refresh(true); +// setLastModified(parentPath, event); +// session.save(); + if (log.isTraceEnabled()) + log.trace("Last modified updated for parents of removed " + removeNodePath); + } + } catch (Exception e) { + if (log.isTraceEnabled()) + log.warn("Cannot process event " + event, e); + } finally { +// try { +// session.refresh(true); +// if (session.hasPendingChanges()) +// session.save(); +//// session.refresh(false); +// } catch (RepositoryException e) { +// if (log.isTraceEnabled()) +// log.warn("Cannot refresh JCR session", e); +// } + } + + } + + private String extractItemName(String path) { + if (path == null || path.length() <= 1) + return null; + int lastIndex = path.lastIndexOf('/'); + if (lastIndex >= 0) { + return path.substring(lastIndex + 1); + } else { + return path; + } + } + + @Override + public void onEvent(EventIterator events) { + processEvents(events); +// Runnable toRun = new Runnable() { +// +// @Override +// public void run() { +// processEvents(events); +// } +// }; +// Future future = Activator.getInternalExecutorService().submit(toRun); +// try { +// // make the call synchronous +// future.get(60, TimeUnit.SECONDS); +// } catch (TimeoutException | ExecutionException | InterruptedException e) { +// // silent +// } + } + + static String toEtag(Value v) { + if (v instanceof JackrabbitValue) { + JackrabbitValue value = (JackrabbitValue) v; + return '\"' + value.getContentIdentity() + '\"'; + } else { + return null; + } + + } + + protected synchronized void setLastModified(Node node, Event event) throws RepositoryException { + GregorianCalendar calendar = new GregorianCalendar(); + calendar.setTimeInMillis(event.getDate()); + node.setProperty(Property.JCR_LAST_MODIFIED, calendar); + node.setProperty(Property.JCR_LAST_MODIFIED_BY, event.getUserID()); + if (log.isTraceEnabled()) + log.trace("Last modified set on " + node); + } + + /** Recursively set the last updated time on parents. */ + protected synchronized void setLastModifiedRecursive(Node node, Event event) throws RepositoryException { + if (versionManager.isCheckedOut(node.getPath())) { + if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) { + setLastModified(node, event); + } + if (node.isNodeType(NodeType.NT_FOLDER) && !node.isNodeType(NodeType.MIX_LAST_MODIFIED)) { + node.addMixin(NodeType.MIX_LAST_MODIFIED); + if (log.isTraceEnabled()) + log.trace("Last modified mix-in added to " + node); + } + + } + + // end condition + if (node.getDepth() == 0) { +// try { +// node.getSession().save(); +// } catch (RepositoryException e) { +// log.warn("Cannot index workspace", e); +// } + return; + } else { + Node parent = node.getParent(); + setLastModifiedRecursive(parent, event); + } + } + + /** + * Recursively set the last updated time on parents. Useful to use paths when + * dealing with deletions. + */ + protected synchronized void setLastModifiedRecursive(String path, Event event) throws RepositoryException { + // root node will always exist, so end condition is delegated to the other + // recursive setLastModified method + if (session.nodeExists(path)) { + setLastModifiedRecursive(session.getNode(path), event); + } else { + setLastModifiedRecursive(JcrUtils.parentPath(path), event); + } + } + + @Override + public String toString() { + return "Indexer for workspace " + workspaceName + " of repository " + cn; + } + + class IndexingThread extends Thread { + + public IndexingThread() { + super(CmsWorkspaceIndexer.this.toString()); + // TODO Auto-generated constructor stub + } + + @Override + public void run() { + life: while (session != null && session.isLive()) { + try { + Event nextEvent = toProcess.take(); + processEvent(nextEvent); + } catch (InterruptedException e) { + // silent + interrupted(); + } + + if (stopping.get() && toProcess.isEmpty()) { + break life; + } + } + if (log.isDebugEnabled()) + log.debug(CmsWorkspaceIndexer.this.toString() + " has shut down."); + } + + } + +} \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/DataModels.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/DataModels.java new file mode 100644 index 000000000..f2196bd41 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/DataModels.java @@ -0,0 +1,190 @@ +package org.argeo.cms.jcr.internal; + +import static org.argeo.cms.osgi.DataModelNamespace.CMS_DATA_MODEL_NAMESPACE; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.osgi.DataModelNamespace; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.BundleEvent; +import org.osgi.framework.BundleListener; +import org.osgi.framework.wiring.BundleCapability; +import org.osgi.framework.wiring.BundleWire; +import org.osgi.framework.wiring.BundleWiring; + +class DataModels implements BundleListener { + private final static CmsLog log = CmsLog.getLog(DataModels.class); + + private Map dataModels = new TreeMap<>(); + + public DataModels(BundleContext bc) { + for (Bundle bundle : bc.getBundles()) + processBundle(bundle, null); + bc.addBundleListener(this); + } + + public List getNonAbstractDataModels() { + List res = new ArrayList<>(); + for (String name : dataModels.keySet()) { + DataModel dataModel = dataModels.get(name); + if (!dataModel.isAbstract()) + res.add(dataModel); + } + // TODO reorder? + return res; + } + + @Override + public void bundleChanged(BundleEvent event) { + if (event.getType() == Bundle.RESOLVED) { + processBundle(event.getBundle(), null); + } else if (event.getType() == Bundle.UNINSTALLED) { + BundleWiring wiring = event.getBundle().adapt(BundleWiring.class); + List providedDataModels = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE); + if (providedDataModels.size() == 0) + return; + for (BundleCapability bundleCapability : providedDataModels) { + dataModels.remove(bundleCapability.getAttributes().get(DataModelNamespace.NAME)); + } + } + + } + + protected void processBundle(Bundle bundle, List scannedBundles) { + if (scannedBundles != null && scannedBundles.contains(bundle)) + throw new IllegalStateException("Cycle in CMS data model requirements for " + bundle); + BundleWiring wiring = bundle.adapt(BundleWiring.class); + if (wiring == null) { + int bundleState = bundle.getState(); + if (bundleState != Bundle.INSTALLED && bundleState != Bundle.UNINSTALLED) {// ignore unresolved bundles + log.warn("Bundle " + bundle.getSymbolicName() + " #" + bundle.getBundleId() + " (" + + bundle.getLocation() + ") cannot be adapted to a wiring"); + } else { + if (log.isTraceEnabled()) + log.warn("Bundle " + bundle.getSymbolicName() + " is not resolved."); + } + return; + } + List providedDataModels = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE); + if (providedDataModels.size() == 0) + return; + List requiredDataModels = wiring.getRequiredWires(CMS_DATA_MODEL_NAMESPACE); + // process requirements first + for (BundleWire bundleWire : requiredDataModels) { + List nextScannedBundles = new ArrayList<>(); + if (scannedBundles != null) + nextScannedBundles.addAll(scannedBundles); + nextScannedBundles.add(bundle); + Bundle providerBundle = bundleWire.getProvider().getBundle(); + processBundle(providerBundle, nextScannedBundles); + } + for (BundleCapability bundleCapability : providedDataModels) { + String name = (String) bundleCapability.getAttributes().get(DataModelNamespace.NAME); + assert name != null; + if (!dataModels.containsKey(name)) { + DataModel dataModel = new DataModel(name, bundleCapability, requiredDataModels); + dataModels.put(dataModel.getName(), dataModel); + } + } + } + + /** Return a negative depth if dataModel is required by ref, 0 otherwise. */ + static int required(DataModel ref, DataModel dataModel, int depth) { + for (DataModel dm : ref.getRequired()) { + if (dm.equals(dataModel))// found here + return depth - 1; + int d = required(dm, dataModel, depth - 1); + if (d != 0)// found deeper + return d; + } + return 0;// not found + } + + class DataModel { + private final String name; + private final boolean abstrct; + // private final boolean standalone; + private final String cnd; + private final List required; + + private DataModel(String name, BundleCapability bundleCapability, List requiredDataModels) { + assert CMS_DATA_MODEL_NAMESPACE.equals(bundleCapability.getNamespace()); + this.name = name; + Map attrs = bundleCapability.getAttributes(); + abstrct = KernelUtils.asBoolean((String) attrs.get(DataModelNamespace.ABSTRACT)); + // standalone = KernelUtils.asBoolean((String) + // attrs.get(DataModelNamespace.CAPABILITY_STANDALONE_ATTRIBUTE)); + cnd = (String) attrs.get(DataModelNamespace.CND); + List req = new ArrayList<>(); + for (BundleWire wire : requiredDataModels) { + String requiredDataModelName = (String) wire.getCapability().getAttributes() + .get(DataModelNamespace.NAME); + assert requiredDataModelName != null; + DataModel requiredDataModel = dataModels.get(requiredDataModelName); + if (requiredDataModel == null) + throw new IllegalStateException("No required data model " + requiredDataModelName); + req.add(requiredDataModel); + } + required = Collections.unmodifiableList(req); + } + + public String getName() { + return name; + } + + public boolean isAbstract() { + return abstrct; + } + + // public boolean isStandalone() { + // return !isAbstract(); + // } + + public String getCnd() { + return cnd; + } + + public List getRequired() { + return required; + } + + // @Override + // public int compareTo(DataModel o) { + // if (equals(o)) + // return 0; + // int res = required(this, o, 0); + // if (res != 0) + // return res; + // // the other way round + // res = required(o, this, 0); + // if (res != 0) + // return -res; + // return 0; + // } + + @Override + public int hashCode() { + return name.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof DataModel) + return ((DataModel) obj).name.equals(name); + return false; + } + + @Override + public String toString() { + return "Data model " + name; + } + + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/EgoRepository.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/EgoRepository.java new file mode 100644 index 000000000..298025096 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/EgoRepository.java @@ -0,0 +1,265 @@ +package org.argeo.cms.jcr.internal; + +import java.security.PrivilegedAction; +import java.text.SimpleDateFormat; +import java.util.HashSet; +import java.util.Set; + +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.nodetype.NodeType; +import javax.jcr.security.Privilege; +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; +import javax.security.auth.Subject; +import javax.security.auth.login.LoginContext; + +import org.argeo.api.cms.CmsAuth; +import org.argeo.api.cms.CmsConstants; +import org.argeo.cms.CmsException; +import org.argeo.cms.jcr.CmsJcrUtils; +import org.argeo.jcr.JcrException; +import org.argeo.jcr.JcrRepositoryWrapper; +import org.argeo.jcr.JcrUtils; + +/** + * Make sure each user has a home directory available. + */ +class EgoRepository extends JcrRepositoryWrapper implements KernelConstants { + + /** The home base path. */ +// private String homeBasePath = KernelConstants.DEFAULT_HOME_BASE_PATH; +// private String usersBasePath = KernelConstants.DEFAULT_USERS_BASE_PATH; +// private String groupsBasePath = KernelConstants.DEFAULT_GROUPS_BASE_PATH; + + private Set checkedUsers = new HashSet(); + + private SimpleDateFormat usersDatePath = new SimpleDateFormat("YYYY/MM"); + + private String defaultHomeWorkspace = CmsConstants.HOME_WORKSPACE; + private String defaultGroupsWorkspace = CmsConstants.SRV_WORKSPACE; +// private String defaultGuestsWorkspace = NodeConstants.GUESTS_WORKSPACE; + private final boolean remote; + + public EgoRepository(Repository repository, boolean remote) { + super(repository); + this.remote = remote; + putDescriptor(CmsConstants.CN, CmsConstants.EGO_REPOSITORY); + if (!remote) { + LoginContext lc; + try { + lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_DATA_ADMIN); + lc.login(); + } catch (javax.security.auth.login.LoginException e1) { + throw new IllegalStateException("Cannot login as system", e1); + } + Subject.doAs(lc.getSubject(), new PrivilegedAction() { + + @Override + public Void run() { + loginOrCreateWorkspace(defaultHomeWorkspace); + loginOrCreateWorkspace(defaultGroupsWorkspace); + return null; + } + + }); + } + } + + private void loginOrCreateWorkspace(String workspace) { + Session adminSession = null; + try { + adminSession = JcrUtils.loginOrCreateWorkspace(getRepository(workspace), workspace); +// JcrUtils.addPrivilege(adminSession, "/", NodeConstants.ROLE_USER, Privilege.JCR_READ); + +// initJcr(adminSession); + } catch (RepositoryException e) { + throw new JcrException("Cannot init JCR home", e); + } finally { + JcrUtils.logoutQuietly(adminSession); + } + } + +// @Override +// public Session login(Credentials credentials, String workspaceName) +// throws LoginException, NoSuchWorkspaceException, RepositoryException { +// if (workspaceName == null) { +// return super.login(credentials, getUserHomeWorkspace()); +// } else { +// return super.login(credentials, workspaceName); +// } +// } + + protected String getUserHomeWorkspace() { + // TODO base on JAAS Subject metadata + return defaultHomeWorkspace; + } + + protected String getGroupsWorkspace() { + // TODO base on JAAS Subject metadata + return defaultGroupsWorkspace; + } + +// protected String getGuestsWorkspace() { +// // TODO base on JAAS Subject metadata +// return defaultGuestsWorkspace; +// } + + @Override + protected void processNewSession(Session session, String workspaceName) { + String username = session.getUserID(); + if (username == null || username.toString().equals("")) + return; + if (session.getUserID().equals(CmsConstants.ROLE_ANONYMOUS)) + return; + + String userHomeWorkspace = getUserHomeWorkspace(); + if (workspaceName == null || !workspaceName.equals(userHomeWorkspace)) + return; + + if (checkedUsers.contains(username)) + return; + Session adminSession = KernelUtils.openAdminSession(getRepository(workspaceName), workspaceName); + try { + syncJcr(adminSession, username); + checkedUsers.add(username); + } finally { + JcrUtils.logoutQuietly(adminSession); + } + } + + /* + * JCR + */ + /** Session is logged out. */ + private void initJcr(Session adminSession) { + try { +// JcrUtils.mkdirs(adminSession, homeBasePath); +// JcrUtils.mkdirs(adminSession, groupsBasePath); + adminSession.save(); + +// JcrUtils.addPrivilege(adminSession, homeBasePath, NodeConstants.ROLE_USER_ADMIN, Privilege.JCR_READ); +// JcrUtils.addPrivilege(adminSession, groupsBasePath, NodeConstants.ROLE_USER_ADMIN, Privilege.JCR_READ); + adminSession.save(); + } catch (RepositoryException e) { + throw new CmsException("Cannot initialize home repository", e); + } finally { + JcrUtils.logoutQuietly(adminSession); + } + } + + protected synchronized void syncJcr(Session adminSession, String username) { + // only in the default workspace +// if (workspaceName != null) +// return; + // skip system users + if (username.endsWith(CmsConstants.ROLES_BASEDN)) + return; + + try { + Node userHome = CmsJcrUtils.getUserHome(adminSession, username); + if (userHome == null) { +// String homePath = generateUserPath(username); + String userId = extractUserId(username); +// if (adminSession.itemExists(homePath))// duplicate user id +// userHome = adminSession.getNode(homePath).getParent().addNode(JcrUtils.lastPathElement(homePath)); +// else +// userHome = JcrUtils.mkdirs(adminSession, homePath); + userHome = adminSession.getRootNode().addNode(userId); +// userHome.addMixin(NodeTypes.NODE_USER_HOME); + userHome.addMixin(NodeType.MIX_CREATED); + userHome.addMixin(NodeType.MIX_TITLE); + userHome.setProperty(Property.JCR_ID, username); + // TODO use display name + userHome.setProperty(Property.JCR_TITLE, userId); +// userHome.setProperty(NodeNames.LDAP_UID, username); + adminSession.save(); + + JcrUtils.clearAccessControList(adminSession, userHome.getPath(), username); + JcrUtils.addPrivilege(adminSession, userHome.getPath(), username, Privilege.JCR_ALL); +// JackrabbitSecurityUtils.denyPrivilege(adminSession, userHome.getPath(), NodeConstants.ROLE_USER, +// Privilege.JCR_READ); + } + if (adminSession.hasPendingChanges()) + adminSession.save(); + } catch (RepositoryException e) { + JcrUtils.discardQuietly(adminSession); + throw new JcrException("Cannot sync node security model for " + username, e); + } + } + + /** Generate path for a new user home */ + private String generateUserPath(String username) { + LdapName dn; + try { + dn = new LdapName(username); + } catch (InvalidNameException e) { + throw new CmsException("Invalid name " + username, e); + } + String userId = dn.getRdn(dn.size() - 1).getValue().toString(); + return '/' + userId; +// int atIndex = userId.indexOf('@'); +// if (atIndex < 0) { +// return homeBasePath+'/' + userId; +// } else { +// return usersBasePath + '/' + usersDatePath.format(new Date()) + '/' + userId; +// } + } + + private String extractUserId(String username) { + LdapName dn; + try { + dn = new LdapName(username); + } catch (InvalidNameException e) { + throw new CmsException("Invalid name " + username, e); + } + String userId = dn.getRdn(dn.size() - 1).getValue().toString(); + return userId; +// int atIndex = userId.indexOf('@'); +// if (atIndex < 0) { +// return homeBasePath+'/' + userId; +// } else { +// return usersBasePath + '/' + usersDatePath.format(new Date()) + '/' + userId; +// } + } + + public void createWorkgroup(LdapName dn) { + String groupsWorkspace = getGroupsWorkspace(); + Session adminSession = KernelUtils.openAdminSession(getRepository(groupsWorkspace), groupsWorkspace); + String cn = dn.getRdn(dn.size() - 1).getValue().toString(); + Node newWorkgroup = CmsJcrUtils.getGroupHome(adminSession, cn); + if (newWorkgroup != null) { + JcrUtils.logoutQuietly(adminSession); + throw new CmsException("Workgroup " + newWorkgroup + " already exists for " + dn); + } + try { + // TODO enhance transformation of cn to a valid node name + // String relPath = cn.replaceAll("[^a-zA-Z0-9]", "_"); + String relPath = JcrUtils.replaceInvalidChars(cn); + newWorkgroup = adminSession.getRootNode().addNode(relPath, NodeType.NT_UNSTRUCTURED); +// newWorkgroup = JcrUtils.mkdirs(adminSession.getNode(groupsBasePath), relPath, NodeType.NT_UNSTRUCTURED); +// newWorkgroup.addMixin(NodeTypes.NODE_GROUP_HOME); + newWorkgroup.addMixin(NodeType.MIX_CREATED); + newWorkgroup.addMixin(NodeType.MIX_TITLE); + newWorkgroup.setProperty(Property.JCR_ID, dn.toString()); + newWorkgroup.setProperty(Property.JCR_TITLE, cn); +// newWorkgroup.setProperty(NodeNames.LDAP_CN, cn); + adminSession.save(); + JcrUtils.addPrivilege(adminSession, newWorkgroup.getPath(), dn.toString(), Privilege.JCR_ALL); + adminSession.save(); + } catch (RepositoryException e) { + throw new CmsException("Cannot create workgroup", e); + } finally { + JcrUtils.logoutQuietly(adminSession); + } + + } + + public boolean isRemote() { + return remote; + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JackrabbitLocalRepository.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JackrabbitLocalRepository.java new file mode 100644 index 000000000..bad9fdfd5 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JackrabbitLocalRepository.java @@ -0,0 +1,71 @@ +package org.argeo.cms.jcr.internal; + +import java.util.Map; +import java.util.TreeMap; + +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.apache.jackrabbit.core.RepositoryImpl; +import org.argeo.api.cms.CmsConstants; +import org.argeo.api.cms.CmsLog; + +class JackrabbitLocalRepository extends LocalRepository { + private final static CmsLog log = CmsLog.getLog(JackrabbitLocalRepository.class); + final String SECURITY_WORKSPACE = "security"; + + private Map workspaceMonitors = new TreeMap<>(); + + public JackrabbitLocalRepository(RepositoryImpl repository, String cn) { + super(repository, cn); +// Session session = KernelUtils.openAdminSession(repository); +// try { +// if (NodeConstants.NODE.equals(cn)) +// for (String workspaceName : session.getWorkspace().getAccessibleWorkspaceNames()) { +// addMonitor(workspaceName); +// } +// } catch (RepositoryException e) { +// throw new IllegalStateException(e); +// } finally { +// JcrUtils.logoutQuietly(session); +// } + } + + protected RepositoryImpl getJackrabbitrepository(String workspaceName) { + return (RepositoryImpl) getRepository(workspaceName); + } + + @Override + protected synchronized void processNewSession(Session session, String workspaceName) { +// String realWorkspaceName = session.getWorkspace().getName(); +// addMonitor(realWorkspaceName); + } + + private void addMonitor(String realWorkspaceName) { + if (realWorkspaceName.equals(SECURITY_WORKSPACE)) + return; + if (!CmsConstants.NODE_REPOSITORY.equals(getCn())) + return; + + if (!workspaceMonitors.containsKey(realWorkspaceName)) { + try { + CmsWorkspaceIndexer workspaceMonitor = new CmsWorkspaceIndexer( + getJackrabbitrepository(realWorkspaceName), getCn(), realWorkspaceName); + workspaceMonitors.put(realWorkspaceName, workspaceMonitor); + workspaceMonitor.init(); + if (log.isDebugEnabled()) + log.debug("Registered " + workspaceMonitor); + } catch (RepositoryException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + + public void destroy() { + for (String workspaceName : workspaceMonitors.keySet()) { + workspaceMonitors.get(workspaceName).destroy(); + } + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JcrKeyring.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JcrKeyring.java new file mode 100644 index 000000000..17625f5d2 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JcrKeyring.java @@ -0,0 +1,397 @@ +package org.argeo.cms.jcr.internal; + +import java.io.ByteArrayInputStream; +import java.io.CharArrayReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.nio.charset.StandardCharsets; +import java.security.GeneralSecurityException; +import java.security.NoSuchAlgorithmException; +import java.security.Provider; +import java.security.SecureRandom; + +import javax.crypto.Cipher; +import javax.crypto.CipherInputStream; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.SecretKey; +import javax.crypto.spec.IvParameterSpec; +import javax.jcr.Binary; +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.Property; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.query.Query; + +import org.apache.commons.io.IOUtils; +import org.argeo.api.cms.CmsConstants; +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.ArgeoNames; +import org.argeo.cms.ArgeoTypes; +import org.argeo.cms.jcr.CmsJcrUtils; +import org.argeo.cms.security.AbstractKeyring; +import org.argeo.cms.security.PBEKeySpecCallback; +import org.argeo.jcr.JcrException; +import org.argeo.jcr.JcrUtils; + +/** JCR based implementation of a keyring */ +public class JcrKeyring extends AbstractKeyring implements ArgeoNames { + private final static CmsLog log = CmsLog.getLog(JcrKeyring.class); + /** + * Stronger with 256, but causes problem with Oracle JVM, force 128 in this case + */ + public final static Long DEFAULT_SECRETE_KEY_LENGTH = 256l; + public final static String DEFAULT_SECRETE_KEY_FACTORY = "PBKDF2WithHmacSHA1"; + public final static String DEFAULT_SECRETE_KEY_ENCRYPTION = "AES"; + public final static String DEFAULT_CIPHER_NAME = "AES/CBC/PKCS5Padding"; + + private Integer iterationCountFactor = 200; + private Long secretKeyLength = DEFAULT_SECRETE_KEY_LENGTH; + private String secretKeyFactoryName = DEFAULT_SECRETE_KEY_FACTORY; + private String secretKeyEncryption = DEFAULT_SECRETE_KEY_ENCRYPTION; + private String cipherName = DEFAULT_CIPHER_NAME; + + private final Repository repository; + // TODO remove thread local session ; open a session each time + private ThreadLocal sessionThreadLocal = new ThreadLocal() { + + @Override + protected Session initialValue() { + return login(); + } + + }; + + // FIXME is it really still needed? + /** + * When setup is called the session has not yet been saved and we don't want to + * save it since there maybe other data which would be inconsistent. So we keep + * a reference to this node which will then be used (an reset to null) when + * handling the PBE callback. We keep one per thread in case multiple users are + * accessing the same instance of a keyring. + */ + // private ThreadLocal notYetSavedKeyring = new ThreadLocal() { + // + // @Override + // protected Node initialValue() { + // return null; + // } + // }; + + public JcrKeyring(Repository repository) { + this.repository = repository; + } + + private Session session() { + Session session = this.sessionThreadLocal.get(); + if (!session.isLive()) { + session = login(); + sessionThreadLocal.set(session); + } + return session; + } + + private Session login() { + try { + return repository.login(CmsConstants.HOME_WORKSPACE); + } catch (RepositoryException e) { + throw new JcrException("Cannot login key ring session", e); + } + } + + @Override + protected synchronized Boolean isSetup() { + Session session = null; + try { + // if (notYetSavedKeyring.get() != null) + // return true; + session = session(); + session.refresh(true); + Node userHome = CmsJcrUtils.getUserHome(session); + return userHome.hasNode(ARGEO_KEYRING); + } catch (RepositoryException e) { + throw new JcrException("Cannot check whether keyring is setup", e); + } finally { + JcrUtils.logoutQuietly(session); + } + } + + @Override + protected synchronized void setup(char[] password) { + Binary binary = null; + // InputStream in = null; + try { + session().refresh(true); + Node userHome = CmsJcrUtils.getUserHome(session()); + Node keyring; + if (userHome.hasNode(ARGEO_KEYRING)) { + throw new IllegalArgumentException("Keyring already set up"); + } else { + keyring = userHome.addNode(ARGEO_KEYRING); + } + keyring.addMixin(ArgeoTypes.ARGEO_PBE_SPEC); + + // deterministic salt and iteration count based on username + String username = session().getUserID(); + byte[] salt = new byte[8]; + byte[] usernameBytes = username.getBytes(StandardCharsets.UTF_8); + for (int i = 0; i < salt.length; i++) { + if (i < usernameBytes.length) + salt[i] = usernameBytes[i]; + else + salt[i] = 0; + } + try (InputStream in = new ByteArrayInputStream(salt);) { + binary = session().getValueFactory().createBinary(in); + keyring.setProperty(ARGEO_SALT, binary); + } catch (IOException e) { + throw new RuntimeException("Cannot set keyring salt", e); + } + + Integer iterationCount = username.length() * iterationCountFactor; + keyring.setProperty(ARGEO_ITERATION_COUNT, iterationCount); + + // default algo + // TODO check if algo and key length are available, use DES if not + keyring.setProperty(ARGEO_SECRET_KEY_FACTORY, secretKeyFactoryName); + keyring.setProperty(ARGEO_KEY_LENGTH, secretKeyLength); + keyring.setProperty(ARGEO_SECRET_KEY_ENCRYPTION, secretKeyEncryption); + keyring.setProperty(ARGEO_CIPHER, cipherName); + + keyring.getSession().save(); + + // encrypted password hash + // IOUtils.closeQuietly(in); + // JcrUtils.closeQuietly(binary); + // byte[] btPass = hash(password, salt, iterationCount); + // in = new ByteArrayInputStream(btPass); + // binary = session().getValueFactory().createBinary(in); + // keyring.setProperty(ARGEO_PASSWORD, binary); + + // notYetSavedKeyring.set(keyring); + } catch (RepositoryException e) { + throw new JcrException("Cannot setup keyring", e); + } finally { + JcrUtils.closeQuietly(binary); + // IOUtils.closeQuietly(in); + // JcrUtils.discardQuietly(session()); + } + } + + @Override + protected synchronized void handleKeySpecCallback(PBEKeySpecCallback pbeCallback) { + Session session = null; + try { + session = session(); + session.refresh(true); + Node userHome = CmsJcrUtils.getUserHome(session); + Node keyring; + if (userHome.hasNode(ARGEO_KEYRING)) + keyring = userHome.getNode(ARGEO_KEYRING); + // else if (notYetSavedKeyring.get() != null) + // keyring = notYetSavedKeyring.get(); + else + throw new IllegalStateException("Keyring not setup"); + + pbeCallback.set(keyring.getProperty(ARGEO_SECRET_KEY_FACTORY).getString(), + JcrUtils.getBinaryAsBytes(keyring.getProperty(ARGEO_SALT)), + (int) keyring.getProperty(ARGEO_ITERATION_COUNT).getLong(), + (int) keyring.getProperty(ARGEO_KEY_LENGTH).getLong(), + keyring.getProperty(ARGEO_SECRET_KEY_ENCRYPTION).getString()); + + // if (notYetSavedKeyring.get() != null) + // notYetSavedKeyring.remove(); + } catch (RepositoryException e) { + throw new JcrException("Cannot handle key spec callback", e); + } finally { + JcrUtils.logoutQuietly(session); + } + } + + /** The parent node must already exist at this path. */ + @Override + protected synchronized void encrypt(String path, InputStream unencrypted) { + // should be called first for lazy initialization + SecretKey secretKey = getSecretKey(null); + Cipher cipher = createCipher(); + + // Binary binary = null; + // InputStream in = null; + try { + session().refresh(true); + Node node; + if (!session().nodeExists(path)) { + String parentPath = JcrUtils.parentPath(path); + if (!session().nodeExists(parentPath)) + throw new IllegalStateException("No parent node of " + path); + Node parentNode = session().getNode(parentPath); + node = parentNode.addNode(JcrUtils.nodeNameFromPath(path)); + } else { + node = session().getNode(path); + } + encrypt(secretKey, cipher, node, unencrypted); + // node.addMixin(ArgeoTypes.ARGEO_ENCRYPTED); + // SecureRandom random = new SecureRandom(); + // byte[] iv = new byte[16]; + // random.nextBytes(iv); + // cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv)); + // JcrUtils.setBinaryAsBytes(node, ARGEO_IV, iv); + // + // try (InputStream in = new CipherInputStream(unencrypted, cipher);) { + // binary = session().getValueFactory().createBinary(in); + // node.setProperty(Property.JCR_DATA, binary); + // session().save(); + // } + } catch (RepositoryException e) { + throw new JcrException("Cannot encrypt", e); + } finally { + try { + unencrypted.close(); + } catch (IOException e) { + // silent + } + // IOUtils.closeQuietly(unencrypted); + // IOUtils.closeQuietly(in); + // JcrUtils.closeQuietly(binary); + JcrUtils.logoutQuietly(session()); + } + } + + protected synchronized void encrypt(SecretKey secretKey, Cipher cipher, Node node, InputStream unencrypted) { + try { + node.addMixin(ArgeoTypes.ARGEO_ENCRYPTED); + SecureRandom random = new SecureRandom(); + byte[] iv = new byte[16]; + random.nextBytes(iv); + cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv)); + JcrUtils.setBinaryAsBytes(node, ARGEO_IV, iv); + + Binary binary = null; + try (InputStream in = new CipherInputStream(unencrypted, cipher);) { + binary = session().getValueFactory().createBinary(in); + node.setProperty(Property.JCR_DATA, binary); + session().save(); + } finally { + JcrUtils.closeQuietly(binary); + } + } catch (RepositoryException e) { + throw new JcrException("Cannot encrypt", e); + } catch (GeneralSecurityException | IOException e) { + throw new RuntimeException("Cannot encrypt", e); + } + } + + @Override + protected synchronized InputStream decrypt(String path) { + Binary binary = null; + try { + session().refresh(true); + if (!session().nodeExists(path)) { + char[] password = ask(); + Reader reader = new CharArrayReader(password); + return new ByteArrayInputStream(IOUtils.toByteArray(reader, StandardCharsets.UTF_8)); + } else { + // should be called first for lazy initialisation + SecretKey secretKey = getSecretKey(null); + Cipher cipher = createCipher(); + Node node = session().getNode(path); + return decrypt(secretKey, cipher, node); + } + } catch (RepositoryException e) { + throw new JcrException("Cannot decrypt", e); + } catch (GeneralSecurityException | IOException e) { + throw new RuntimeException("Cannot decrypt", e); + } finally { + JcrUtils.closeQuietly(binary); + JcrUtils.logoutQuietly(session()); + } + } + + protected synchronized InputStream decrypt(SecretKey secretKey, Cipher cipher, Node node) + throws RepositoryException, GeneralSecurityException { + if (node.hasProperty(ARGEO_IV)) { + byte[] iv = JcrUtils.getBinaryAsBytes(node.getProperty(ARGEO_IV)); + cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv)); + } else { + cipher.init(Cipher.DECRYPT_MODE, secretKey); + } + + Binary binary = node.getProperty(Property.JCR_DATA).getBinary(); + InputStream encrypted = binary.getStream(); + return new CipherInputStream(encrypted, cipher); + } + + protected Cipher createCipher() { + try { + Node userHome = CmsJcrUtils.getUserHome(session()); + if (!userHome.hasNode(ARGEO_KEYRING)) + throw new IllegalArgumentException("Keyring not setup"); + Node keyring = userHome.getNode(ARGEO_KEYRING); + String cipherName = keyring.getProperty(ARGEO_CIPHER).getString(); + Provider securityProvider = getSecurityProvider(); + Cipher cipher; + if (securityProvider == null)// TODO use BC? + cipher = Cipher.getInstance(cipherName); + else + cipher = Cipher.getInstance(cipherName, securityProvider); + return cipher; + } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { + throw new IllegalArgumentException("Cannot get cipher", e); + } catch (RepositoryException e) { + throw new JcrException("Cannot get cipher", e); + } finally { + + } + } + + public synchronized void changePassword(char[] oldPassword, char[] newPassword) { + // TODO make it XA compatible + SecretKey oldSecretKey = getSecretKey(oldPassword); + SecretKey newSecretKey = getSecretKey(newPassword); + Session session = session(); + try { + NodeIterator encryptedNodes = session.getWorkspace().getQueryManager() + .createQuery("select * from [argeo:encrypted]", Query.JCR_SQL2).execute().getNodes(); + while (encryptedNodes.hasNext()) { + Node node = encryptedNodes.nextNode(); + InputStream in = decrypt(oldSecretKey, createCipher(), node); + encrypt(newSecretKey, createCipher(), node, in); + if (log.isDebugEnabled()) + log.debug("Converted keyring encrypted value of " + node.getPath()); + } + } catch (GeneralSecurityException e) { + throw new RuntimeException("Cannot change JCR keyring password", e); + } catch (RepositoryException e) { + throw new JcrException("Cannot change JCR keyring password", e); + } finally { + JcrUtils.logoutQuietly(session); + } + } + + // public synchronized void setSession(Session session) { + // this.session = session; + // } + + public void setIterationCountFactor(Integer iterationCountFactor) { + this.iterationCountFactor = iterationCountFactor; + } + + public void setSecretKeyLength(Long keyLength) { + this.secretKeyLength = keyLength; + } + + public void setSecretKeyFactoryName(String secreteKeyFactoryName) { + this.secretKeyFactoryName = secreteKeyFactoryName; + } + + public void setSecretKeyEncryption(String secreteKeyEncryption) { + this.secretKeyEncryption = secreteKeyEncryption; + } + + public void setCipherName(String cipherName) { + this.cipherName = cipherName; + } + +} \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JcrRepositoryFactory.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JcrRepositoryFactory.java new file mode 100644 index 000000000..342c1add7 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JcrRepositoryFactory.java @@ -0,0 +1,191 @@ +package org.argeo.cms.jcr.internal; + +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.RepositoryFactory; + +import org.apache.jackrabbit.jcr2dav.Jcr2davRepositoryFactory; +import org.argeo.api.cms.CmsConstants; +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.internal.jcr.RepoConf; +import org.argeo.cms.jcr.internal.osgi.CmsJcrActivator; +import org.osgi.framework.BundleContext; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; + +/** + * OSGi-aware Jackrabbit repository factory which can retrieve/publish + * {@link Repository} as OSGi services. + */ +public class JcrRepositoryFactory implements RepositoryFactory { + private final CmsLog log = CmsLog.getLog(getClass()); +// private final BundleContext bundleContext = FrameworkUtil.getBundle(getClass()).getBundleContext(); + + // private Resource fileRepositoryConfiguration = new ClassPathResource( + // "/org/argeo/cms/internal/kernel/repository-localfs.xml"); + + protected Repository getRepositoryByAlias(String alias) { + BundleContext bundleContext = CmsJcrActivator.getBundleContext(); + if (bundleContext != null) { + try { + Collection> srs = bundleContext.getServiceReferences(Repository.class, + "(" + CmsConstants.CN + "=" + alias + ")"); + if (srs.size() == 0) + throw new IllegalArgumentException("No repository with alias " + alias + " found in OSGi registry"); + else if (srs.size() > 1) + throw new IllegalArgumentException( + srs.size() + " repositories with alias " + alias + " found in OSGi registry"); + return bundleContext.getService(srs.iterator().next()); + } catch (InvalidSyntaxException e) { + throw new IllegalArgumentException("Cannot find repository with alias " + alias, e); + } + } else { + // TODO ability to filter static services + return null; + } + } + + // private void publish(String alias, Repository repository, Properties + // properties) { + // if (bundleContext != null) { + // // do not modify reference + // Hashtable props = new Hashtable(); + // props.putAll(props); + // props.put(JCR_REPOSITORY_ALIAS, alias); + // bundleContext.registerService(Repository.class.getName(), repository, + // props); + // } + // } + + @SuppressWarnings({ "rawtypes" }) + public Repository getRepository(Map parameters) throws RepositoryException { + // // check if can be found by alias + // Repository repository = super.getRepository(parameters); + // if (repository != null) + // return repository; + + // check if remote + Repository repository; + String uri = null; + if (parameters.containsKey(RepoConf.labeledUri.name())) + uri = parameters.get(CmsConstants.LABELED_URI).toString(); + else if (parameters.containsKey(KernelConstants.JACKRABBIT_REPOSITORY_URI)) + uri = parameters.get(KernelConstants.JACKRABBIT_REPOSITORY_URI).toString(); + + if (uri != null) { + if (uri.startsWith("http")) {// http, https + Object defaultWorkspace = parameters.get(RepoConf.defaultWorkspace.name()); + repository = createRemoteRepository(uri, defaultWorkspace != null ? defaultWorkspace.toString() : null); + } else if (uri.startsWith("file"))// http, https + repository = createFileRepository(uri, parameters); + else if (uri.startsWith("vm")) { + // log.warn("URI " + uri + " should have been managed by generic + // JCR repository factory"); + repository = getRepositoryByAlias(getAliasFromURI(uri)); + } else + throw new IllegalArgumentException("Unrecognized URI format " + uri); + + } + + else if (parameters.containsKey(CmsConstants.CN)) { + // Properties properties = new Properties(); + // properties.putAll(parameters); + String alias = parameters.get(CmsConstants.CN).toString(); + // publish(alias, repository, properties); + // log.info("Registered JCR repository under alias '" + alias + "' + // with properties " + properties); + repository = getRepositoryByAlias(alias); + } else + throw new IllegalArgumentException("Not enough information in " + parameters); + + if (repository == null) + throw new IllegalArgumentException("Repository not found " + parameters); + + return repository; + } + + protected Repository createRemoteRepository(String uri, String defaultWorkspace) throws RepositoryException { + Map params = new HashMap(); + params.put(KernelConstants.JACKRABBIT_REPOSITORY_URI, uri); + if (defaultWorkspace != null) + params.put(KernelConstants.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, defaultWorkspace); + Repository repository = new Jcr2davRepositoryFactory().getRepository(params); + if (repository == null) + throw new IllegalArgumentException("Remote Davex repository " + uri + " not found"); + log.info("Initialized remote Jackrabbit repository from uri " + uri); + return repository; + } + + @SuppressWarnings({ "rawtypes" }) + protected Repository createFileRepository(final String uri, Map parameters) throws RepositoryException { + throw new UnsupportedOperationException(); + // InputStream configurationIn = null; + // try { + // Properties vars = new Properties(); + // vars.putAll(parameters); + // String dirPath = uri.substring("file:".length()); + // File homeDir = new File(dirPath); + // if (homeDir.exists() && !homeDir.isDirectory()) + // throw new ArgeoJcrException("Repository home " + dirPath + " is not a + // directory"); + // if (!homeDir.exists()) + // homeDir.mkdirs(); + // configurationIn = fileRepositoryConfiguration.getInputStream(); + // vars.put(RepositoryConfigurationParser.REPOSITORY_HOME_VARIABLE, + // homeDir.getCanonicalPath()); + // RepositoryConfig repositoryConfig = RepositoryConfig.create(new + // InputSource(configurationIn), vars); + // + // // TransientRepository repository = new + // // TransientRepository(repositoryConfig); + // final RepositoryImpl repository = + // RepositoryImpl.create(repositoryConfig); + // Session session = repository.login(); + // // FIXME make it generic + // org.argeo.jcr.JcrUtils.addPrivilege(session, "/", "ROLE_ADMIN", + // "jcr:all"); + // org.argeo.jcr.JcrUtils.logoutQuietly(session); + // Runtime.getRuntime().addShutdownHook(new Thread("Clean JCR repository + // " + uri) { + // public void run() { + // repository.shutdown(); + // log.info("Destroyed repository " + uri); + // } + // }); + // log.info("Initialized file Jackrabbit repository from uri " + uri); + // return repository; + // } catch (Exception e) { + // throw new ArgeoJcrException("Cannot create repository " + uri, e); + // } finally { + // IOUtils.closeQuietly(configurationIn); + // } + } + + protected String getAliasFromURI(String uri) { + try { + URI uriObj = new URI(uri); + String alias = uriObj.getPath(); + if (alias.charAt(0) == '/') + alias = alias.substring(1); + if (alias.charAt(alias.length() - 1) == '/') + alias = alias.substring(0, alias.length() - 1); + return alias; + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Cannot interpret URI " + uri, e); + } + } + + /** + * Called after the repository has been initialised. Does nothing by default. + */ + @SuppressWarnings("rawtypes") + protected void postInitialization(Repository repository, Map parameters) { + + } +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/KernelConstants.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/KernelConstants.java new file mode 100644 index 000000000..93f29fbe8 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/KernelConstants.java @@ -0,0 +1,53 @@ +package org.argeo.cms.jcr.internal; + +import org.argeo.api.cms.CmsConstants; + +/** Internal CMS constants. */ +@Deprecated +public interface KernelConstants { + // Directories + String DIR_NODE = "node"; + String DIR_REPOS = "repos"; + String DIR_INDEXES = "indexes"; + String DIR_TRANSACTIONS = "transactions"; + + // Files + String DEPLOY_CONFIG_PATH = DIR_NODE + '/' + CmsConstants.DEPLOY_BASEDN + ".ldif"; + String DEFAULT_KEYSTORE_PATH = DIR_NODE + '/' + CmsConstants.NODE + ".p12"; + String DEFAULT_PEM_KEY_PATH = DIR_NODE + '/' + CmsConstants.NODE + ".key"; + String DEFAULT_PEM_CERT_PATH = DIR_NODE + '/' + CmsConstants.NODE + ".crt"; + String NODE_KEY_TAB_PATH = DIR_NODE + "/krb5.keytab"; + + // Security + String JAAS_CONFIG = "/org/argeo/cms/internal/kernel/jaas.cfg"; + String JAAS_CONFIG_IPA = "/org/argeo/cms/internal/kernel/jaas-ipa.cfg"; + + // Java + String JAAS_CONFIG_PROP = "java.security.auth.login.config"; + + // DEFAULTS JCR PATH + String DEFAULT_HOME_BASE_PATH = "/home"; + String DEFAULT_USERS_BASE_PATH = "/users"; + String DEFAULT_GROUPS_BASE_PATH = "/groups"; + + // KERBEROS + String DEFAULT_KERBEROS_SERVICE = "HTTP"; + + // HTTP client + String COOKIE_POLICY_BROWSER_COMPATIBILITY = "compatibility"; + + // RWT / RAP + // String PATH_WORKBENCH = "/ui"; + // String PATH_WORKBENCH_PUBLIC = PATH_WORKBENCH + "/public"; + + String JETTY_FACTORY_PID = "org.eclipse.equinox.http.jetty.config"; + String WHITEBOARD_PATTERN_PROP = "osgi.http.whiteboard.servlet.pattern"; + // default Jetty server configured via JettyConfigurator + String DEFAULT_JETTY_SERVER = "default"; + String CMS_JETTY_CUSTOMIZER_CLASS = "org.argeo.equinox.jetty.CmsJettyCustomizer"; + + // avoid dependencies + String CONTEXT_NAME_PROP = "contextName"; + String JACKRABBIT_REPOSITORY_URI = "org.apache.jackrabbit.repository.uri"; + String JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = "org.apache.jackrabbit.spi2davex.WorkspaceNameDefault"; +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/KernelUtils.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/KernelUtils.java new file mode 100644 index 000000000..edfe87a03 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/KernelUtils.java @@ -0,0 +1,262 @@ +package org.argeo.cms.jcr.internal; + +import java.io.File; +import java.io.IOException; +import java.io.PrintStream; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.PrivilegedAction; +import java.security.URIParameter; +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.Properties; +import java.util.TreeMap; +import java.util.TreeSet; + +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.security.auth.Subject; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; + +import org.argeo.api.cms.CmsAuth; +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.jcr.internal.osgi.CmsJcrActivator; +import org.argeo.cms.osgi.DataModelNamespace; +import org.osgi.framework.BundleContext; +import org.osgi.util.tracker.ServiceTracker; + +/** Package utilities */ +class KernelUtils implements KernelConstants { + final static String OSGI_INSTANCE_AREA = "osgi.instance.area"; + final static String OSGI_CONFIGURATION_AREA = "osgi.configuration.area"; + + static void setJaasConfiguration(URL jaasConfigurationUrl) { + try { + URIParameter uriParameter = new URIParameter(jaasConfigurationUrl.toURI()); + javax.security.auth.login.Configuration jaasConfiguration = javax.security.auth.login.Configuration + .getInstance("JavaLoginConfig", uriParameter); + javax.security.auth.login.Configuration.setConfiguration(jaasConfiguration); + } catch (Exception e) { + throw new IllegalArgumentException("Cannot set configuration " + jaasConfigurationUrl, e); + } + } + + static Dictionary asDictionary(Properties props) { + Hashtable hashtable = new Hashtable(); + for (Object key : props.keySet()) { + hashtable.put(key.toString(), props.get(key)); + } + return hashtable; + } + + static Dictionary asDictionary(ClassLoader cl, String resource) { + Properties props = new Properties(); + try { + props.load(cl.getResourceAsStream(resource)); + } catch (IOException e) { + throw new IllegalArgumentException("Cannot load " + resource + " from classpath", e); + } + return asDictionary(props); + } + + static File getExecutionDir(String relativePath) { + File executionDir = new File(getFrameworkProp("user.dir")); + if (relativePath == null) + return executionDir; + try { + return new File(executionDir, relativePath).getCanonicalFile(); + } catch (IOException e) { + throw new IllegalArgumentException("Cannot get canonical file", e); + } + } + + static File getOsgiInstanceDir() { + return new File(getBundleContext().getProperty(OSGI_INSTANCE_AREA).substring("file:".length())) + .getAbsoluteFile(); + } + + static Path getOsgiInstancePath(String relativePath) { + return Paths.get(getOsgiInstanceUri(relativePath)); + } + + static URI getOsgiInstanceUri(String relativePath) { + String osgiInstanceBaseUri = getFrameworkProp(OSGI_INSTANCE_AREA); + if (osgiInstanceBaseUri != null) + return safeUri(osgiInstanceBaseUri + (relativePath != null ? relativePath : "")); + else + return Paths.get(System.getProperty("user.dir")).toUri(); + } + + static File getOsgiConfigurationFile(String relativePath) { + try { + return new File(new URI(getBundleContext().getProperty(OSGI_CONFIGURATION_AREA) + relativePath)) + .getCanonicalFile(); + } catch (Exception e) { + throw new IllegalArgumentException("Cannot get configuration file for " + relativePath, e); + } + } + + static String getFrameworkProp(String key, String def) { + BundleContext bundleContext = CmsJcrActivator.getBundleContext(); + String value; + if (bundleContext != null) + value = bundleContext.getProperty(key); + else + value = System.getProperty(key); + if (value == null) + return def; + return value; + } + + static String getFrameworkProp(String key) { + return getFrameworkProp(key, null); + } + + // Security + // static Subject anonymousLogin() { + // Subject subject = new Subject(); + // LoginContext lc; + // try { + // lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, subject); + // lc.login(); + // return subject; + // } catch (LoginException e) { + // throw new CmsException("Cannot login as anonymous", e); + // } + // } + + static void logFrameworkProperties(CmsLog log) { + BundleContext bc = getBundleContext(); + for (Object sysProp : new TreeSet(System.getProperties().keySet())) { + log.debug(sysProp + "=" + bc.getProperty(sysProp.toString())); + } + // String[] keys = { Constants.FRAMEWORK_STORAGE, + // Constants.FRAMEWORK_OS_NAME, Constants.FRAMEWORK_OS_VERSION, + // Constants.FRAMEWORK_PROCESSOR, Constants.FRAMEWORK_SECURITY, + // Constants.FRAMEWORK_TRUST_REPOSITORIES, + // Constants.FRAMEWORK_WINDOWSYSTEM, Constants.FRAMEWORK_VENDOR, + // Constants.FRAMEWORK_VERSION, Constants.FRAMEWORK_STORAGE_CLEAN, + // Constants.FRAMEWORK_LANGUAGE, Constants.FRAMEWORK_UUID }; + // for (String key : keys) + // log.debug(key + "=" + bc.getProperty(key)); + } + + static void printSystemProperties(PrintStream out) { + TreeMap display = new TreeMap<>(); + for (Object key : System.getProperties().keySet()) + display.put(key.toString(), System.getProperty(key.toString())); + for (String key : display.keySet()) + out.println(key + "=" + display.get(key)); + } + + static Session openAdminSession(Repository repository) { + return openAdminSession(repository, null); + } + + static Session openAdminSession(final Repository repository, final String workspaceName) { + LoginContext loginContext = loginAsDataAdmin(); + return Subject.doAs(loginContext.getSubject(), new PrivilegedAction() { + + @Override + public Session run() { + try { + return repository.login(workspaceName); + } catch (RepositoryException e) { + throw new IllegalStateException("Cannot open admin session", e); + } finally { + try { + loginContext.logout(); + } catch (LoginException e) { + throw new IllegalStateException(e); + } + } + } + + }); + } + + static LoginContext loginAsDataAdmin() { + ClassLoader currentCl = Thread.currentThread().getContextClassLoader(); + Thread.currentThread().setContextClassLoader(KernelUtils.class.getClassLoader()); + LoginContext loginContext; + try { + loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_DATA_ADMIN); + loginContext.login(); + } catch (LoginException e1) { + throw new IllegalStateException("Could not login as data admin", e1); + } finally { + Thread.currentThread().setContextClassLoader(currentCl); + } + return loginContext; + } + + static void doAsDataAdmin(Runnable action) { + LoginContext loginContext = loginAsDataAdmin(); + Subject.doAs(loginContext.getSubject(), new PrivilegedAction() { + + @Override + public Void run() { + try { + action.run(); + return null; + } finally { + try { + loginContext.logout(); + } catch (LoginException e) { + throw new IllegalStateException(e); + } + } + } + + }); + } + + static void asyncOpen(ServiceTracker st) { + Runnable run = new Runnable() { + + @Override + public void run() { + st.open(); + } + }; +// Activator.getInternalExecutorService().execute(run); + new Thread(run, "Open service tracker " + st).start(); + } + + static BundleContext getBundleContext() { + return CmsJcrActivator.getBundleContext(); + } + + static boolean asBoolean(String value) { + if (value == null) + return false; + switch (value) { + case "true": + return true; + case "false": + return false; + default: + throw new IllegalArgumentException( + "Unsupported value for attribute " + DataModelNamespace.ABSTRACT + ": " + value); + } + } + + private static URI safeUri(String uri) { + if (uri == null) + throw new IllegalArgumentException("URI cannot be null"); + try { + return new URI(uri); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Badly formatted URI " + uri, e); + } + } + + private KernelUtils() { + + } +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/LocalRepository.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/LocalRepository.java new file mode 100644 index 000000000..0bac94cc0 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/LocalRepository.java @@ -0,0 +1,23 @@ +package org.argeo.cms.jcr.internal; + +import javax.jcr.Repository; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.jcr.JcrRepositoryWrapper; + +class LocalRepository extends JcrRepositoryWrapper { + private final String cn; + + public LocalRepository(Repository repository, String cn) { + super(repository); + this.cn = cn; + // Map attrs = dataModelCapability.getAttributes(); + // cn = (String) attrs.get(DataModelNamespace.NAME); + putDescriptor(CmsConstants.CN, cn); + } + + String getCn() { + return cn; + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/NodeKeyRing.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/NodeKeyRing.java new file mode 100644 index 000000000..9cd1f7269 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/NodeKeyRing.java @@ -0,0 +1,19 @@ +package org.argeo.cms.jcr.internal; + +import java.util.Dictionary; + +import javax.jcr.Repository; + +import org.osgi.service.cm.ConfigurationException; +import org.osgi.service.cm.ManagedService; + +class NodeKeyRing extends JcrKeyring implements ManagedService{ + + public NodeKeyRing(Repository repository) { + super(repository); + } + + @Override + public void updated(Dictionary properties) throws ConfigurationException { + } +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/RepositoryContextsFactory.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/RepositoryContextsFactory.java new file mode 100644 index 000000000..e05a0023e --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/RepositoryContextsFactory.java @@ -0,0 +1,143 @@ +package org.argeo.cms.jcr.internal; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Dictionary; +import java.util.HashMap; +import java.util.Map; + +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.RepositoryFactory; + +import org.apache.jackrabbit.core.RepositoryContext; +import org.argeo.api.cms.CmsConstants; +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.internal.jcr.RepoConf; +import org.argeo.cms.internal.jcr.RepositoryBuilder; +import org.argeo.cms.jcr.internal.osgi.CmsJcrActivator; +import org.argeo.util.LangUtils; +import org.osgi.framework.Constants; +import org.osgi.service.cm.ConfigurationException; +import org.osgi.service.cm.ManagedServiceFactory; + +/** A {@link ManagedServiceFactory} creating or referencing JCR repositories. */ +public class RepositoryContextsFactory implements ManagedServiceFactory { + private final static CmsLog log = CmsLog.getLog(RepositoryContextsFactory.class); +// private final BundleContext bc = FrameworkUtil.getBundle(RepositoryServiceFactory.class).getBundleContext(); + + private Map repositories = new HashMap(); + private Map pidToCn = new HashMap(); + + public void init() { + + } + + public void destroy() { + for (String pid : repositories.keySet()) { + try { + RepositoryContext repositoryContext = repositories.get(pid); + // Must start in another thread otherwise shutdown is interrupted + // TODO use an executor? + new Thread(() -> { + repositoryContext.getRepository().shutdown(); + if (log.isDebugEnabled()) + log.debug("Shut down repository " + pid + + (pidToCn.containsKey(pid) ? " (" + pidToCn.get(pid) + ")" : "")); + }, "Shutdown JCR repository " + pid).start(); + } catch (Exception e) { + log.error("Error when shutting down Jackrabbit repository " + pid, e); + } + } + } + + @Override + public String getName() { + return "Jackrabbit repository service factory"; + } + + @Override + public void updated(String pid, Dictionary properties) throws ConfigurationException { + if (repositories.containsKey(pid)) + throw new IllegalArgumentException("Already a repository registered for " + pid); + + if (properties == null) + return; + + Object cn = properties.get(CmsConstants.CN); + if (cn != null) + for (String otherPid : pidToCn.keySet()) { + Object o = pidToCn.get(otherPid); + if (cn.equals(o)) { + RepositoryContext repositoryContext = repositories.remove(otherPid); + repositories.put(pid, repositoryContext); + if (log.isDebugEnabled()) + log.debug("Ignoring update of Jackrabbit repository " + cn); + // FIXME perform a proper update (also of the OSGi service) + return; + } + } + + try { + Object labeledUri = properties.get(RepoConf.labeledUri.name()); + if (labeledUri == null) { + RepositoryBuilder repositoryBuilder = new RepositoryBuilder(); + RepositoryContext repositoryContext = repositoryBuilder.createRepositoryContext(properties); + repositories.put(pid, repositoryContext); + Dictionary props = LangUtils.dict(Constants.SERVICE_PID, pid); + // props.put(ArgeoJcrConstants.JCR_REPOSITORY_URI, + // properties.get(RepoConf.labeledUri.name())); + if (cn != null) { + props.put(CmsConstants.CN, cn); + // props.put(NodeConstants.JCR_REPOSITORY_ALIAS, cn); + pidToCn.put(pid, cn); + } + CmsJcrActivator.registerService(RepositoryContext.class, repositoryContext, props); + } else { + Object defaultWorkspace = properties.get(RepoConf.defaultWorkspace.name()); + if (defaultWorkspace == null) + defaultWorkspace = RepoConf.defaultWorkspace.getDefault(); + URI uri = new URI(labeledUri.toString()); +// RepositoryFactory repositoryFactory = bc +// .getService(bc.getServiceReference(RepositoryFactory.class)); + RepositoryFactory repositoryFactory = CmsJcrActivator.getService(RepositoryFactory.class); + Map parameters = new HashMap(); + parameters.put(RepoConf.labeledUri.name(), uri.toString()); + parameters.put(RepoConf.defaultWorkspace.name(), defaultWorkspace.toString()); + Repository repository = repositoryFactory.getRepository(parameters); + // Repository repository = NodeUtils.getRepositoryByUri(repositoryFactory, + // uri.toString()); + Dictionary props = LangUtils.dict(Constants.SERVICE_PID, pid); + props.put(RepoConf.labeledUri.name(), + new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), uri.getPath(), null, null) + .toString()); + if (cn != null) { + props.put(CmsConstants.CN, cn); + // props.put(NodeConstants.JCR_REPOSITORY_ALIAS, cn); + pidToCn.put(pid, cn); + } + CmsJcrActivator.registerService(Repository.class, repository, props); + + // home + if (cn.equals(CmsConstants.NODE_REPOSITORY)) { + Dictionary homeProps = LangUtils.dict(CmsConstants.CN, CmsConstants.EGO_REPOSITORY); + EgoRepository homeRepository = new EgoRepository(repository, true); + CmsJcrActivator.registerService(Repository.class, homeRepository, homeProps); + } + } + } catch (RepositoryException | URISyntaxException | IOException e) { + throw new IllegalStateException("Cannot create Jackrabbit repository " + pid, e); + } + + } + + @Override + public void deleted(String pid) { + RepositoryContext repositoryContext = repositories.remove(pid); + repositoryContext.getRepository().shutdown(); + if (log.isDebugEnabled()) + log.debug("Deleted repository " + pid); + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/StatisticsThread.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/StatisticsThread.java new file mode 100644 index 000000000..5a2cd5b7b --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/StatisticsThread.java @@ -0,0 +1,123 @@ +package org.argeo.cms.jcr.internal; + +import java.io.File; +import java.lang.management.ManagementFactory; + +import org.apache.jackrabbit.api.stats.RepositoryStatistics; +import org.apache.jackrabbit.stats.RepositoryStatisticsImpl; +import org.argeo.api.cms.CmsLog; + +/** + * Background thread started by the kernel, which gather statistics and + * monitor/control other processes. + */ +public class StatisticsThread extends Thread { + private final static CmsLog log = CmsLog.getLog(StatisticsThread.class); + + private RepositoryStatisticsImpl repoStats; + + /** The smallest period of operation, in ms */ + private final long PERIOD = 60 * 1000l; + /** One ms in ns */ + private final static long m = 1000l * 1000l; + private final static long M = 1024l * 1024l; + + private boolean running = true; + + private CmsLog kernelStatsLog = CmsLog.getLog("argeo.stats.kernel"); + private CmsLog nodeStatsLog = CmsLog.getLog("argeo.stats.node"); + + @SuppressWarnings("unused") + private long cycle = 0l; + + public StatisticsThread(String name) { + super(name); + } + + private void doSmallestPeriod() { + // Clean expired sessions + // FIXME re-enable it in CMS + //CmsSessionImpl.closeInvalidSessions(); + + if (kernelStatsLog.isDebugEnabled()) { + StringBuilder line = new StringBuilder(64); + line.append("§\t"); + long freeMem = Runtime.getRuntime().freeMemory() / M; + long totalMem = Runtime.getRuntime().totalMemory() / M; + long maxMem = Runtime.getRuntime().maxMemory() / M; + double loadAvg = ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage(); + // in min + boolean min = true; + long uptime = ManagementFactory.getRuntimeMXBean().getUptime() / (1000 * 60); + if (uptime > 24 * 60) { + min = false; + uptime = uptime / 60; + } + line.append(uptime).append(min ? " min" : " h").append('\t'); + line.append(loadAvg).append('\t').append(maxMem).append('\t').append(totalMem).append('\t').append(freeMem) + .append('\t'); + kernelStatsLog.debug(line); + } + + if (nodeStatsLog.isDebugEnabled()) { + File dataDir = KernelUtils.getOsgiInstanceDir(); + long freeSpace = dataDir.getUsableSpace() / M; + // File currentRoot = null; + // for (File root : File.listRoots()) { + // String rootPath = root.getAbsolutePath(); + // if (dataDir.getAbsolutePath().startsWith(rootPath)) { + // if (currentRoot == null + // || (rootPath.length() > currentRoot.getPath() + // .length())) { + // currentRoot = root; + // } + // } + // } + // long totalSpace = currentRoot.getTotalSpace(); + StringBuilder line = new StringBuilder(128); + line.append("§\t").append(freeSpace).append(" MB left in " + dataDir); + line.append('\n'); + if (repoStats != null) + for (RepositoryStatistics.Type type : RepositoryStatistics.Type.values()) { + long[] vals = repoStats.getTimeSeries(type).getValuePerMinute(); + long val = vals[vals.length - 1]; + line.append(type.name()).append('\t').append(val).append('\n'); + } + nodeStatsLog.debug(line); + } + } + + @Override + public void run() { + if (log.isTraceEnabled()) + log.trace("Kernel thread started."); + final long periodNs = PERIOD * m; + while (running) { + long beginNs = System.nanoTime(); + doSmallestPeriod(); + + long waitNs = periodNs - (System.nanoTime() - beginNs); + if (waitNs < 0) + continue; + // wait + try { + sleep(waitNs / m, (int) (waitNs % m)); + } catch (InterruptedException e) { + // silent + } + cycle++; + } + } + + public synchronized void destroyAndJoin() { + running = false; + notifyAll(); +// interrupt(); +// try { +// join(PERIOD * 2); +// } catch (InterruptedException e) { +// // throw new CmsException("Kernel thread destruction was interrupted"); +// log.error("Kernel thread destruction was interrupted", e); +// } + } +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/osgi/CmsJcrActivator.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/osgi/CmsJcrActivator.java new file mode 100644 index 000000000..57860d84f --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/osgi/CmsJcrActivator.java @@ -0,0 +1,91 @@ +package org.argeo.cms.jcr.internal.osgi; + +import java.util.Dictionary; + +import org.argeo.cms.jcr.internal.StatisticsThread; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; + +public class CmsJcrActivator implements BundleActivator { + private static BundleContext bundleContext; + +// private List stopHooks = new ArrayList<>(); + private StatisticsThread kernelThread; + +// private JackrabbitRepositoryContextsFactory repositoryServiceFactory; +// private CmsJcrDeployment jcrDeployment; + + @Override + public void start(BundleContext context) throws Exception { + bundleContext = context; + + // kernel thread + kernelThread = new StatisticsThread("Kernel Thread"); + kernelThread.setContextClassLoader(getClass().getClassLoader()); + kernelThread.start(); + + // JCR +// repositoryServiceFactory = new JackrabbitRepositoryContextsFactory(); +//// stopHooks.add(() -> repositoryServiceFactory.shutdown()); +// registerService(ManagedServiceFactory.class, repositoryServiceFactory, +// LangUtils.dict(Constants.SERVICE_PID, CmsConstants.NODE_REPOS_FACTORY_PID)); + +// JcrRepositoryFactory repositoryFactory = new JcrRepositoryFactory(); +// registerService(RepositoryFactory.class, repositoryFactory, null); + + // File System +// CmsJcrFsProvider cmsFsProvider = new CmsJcrFsProvider(); +// ServiceLoader fspSl = ServiceLoader.load(FileSystemProvider.class); +// for (FileSystemProvider fsp : fspSl) { +// log.debug("FileSystemProvider " + fsp); +// if (fsp instanceof CmsFsProvider) { +// cmsFsProvider = (CmsFsProvider) fsp; +// } +// } +// for (FileSystemProvider fsp : FileSystemProvider.installedProviders()) { +// log.debug("Installed FileSystemProvider " + fsp); +// } +// registerService(FileSystemProvider.class, cmsFsProvider, +// LangUtils.dict(Constants.SERVICE_PID, CmsConstants.NODE_FS_PROVIDER_PID)); + +// jcrDeployment = new CmsJcrDeployment(); +// jcrDeployment.init(); + } + + @Override + public void stop(BundleContext context) throws Exception { +// if (jcrDeployment != null) +// jcrDeployment.destroy(); + +// if (repositoryServiceFactory != null) +// repositoryServiceFactory.shutdown(); + + if (kernelThread != null) + kernelThread.destroyAndJoin(); + + bundleContext = null; + } + + @Deprecated + public static void registerService(Class clss, T service, Dictionary properties) { + if (bundleContext != null) { + bundleContext.registerService(clss, service, properties); + } + + } + + @Deprecated + public static BundleContext getBundleContext() { + return bundleContext; + } + + @Deprecated + public static T getService(Class clss) { + if (bundleContext != null) { + return bundleContext.getService(bundleContext.getServiceReference(clss)); + } else { + return null; + } + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsRemotingServlet.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsRemotingServlet.java new file mode 100644 index 000000000..fa3f87f67 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsRemotingServlet.java @@ -0,0 +1,44 @@ +package org.argeo.cms.jcr.internal.servlet; + +import java.util.Map; + +import javax.jcr.Repository; + +import org.apache.jackrabbit.server.SessionProvider; +import org.apache.jackrabbit.server.remoting.davex.JcrRemotingServlet; +import org.argeo.api.cms.CmsConstants; + +/** A {@link JcrRemotingServlet} based on {@link CmsSessionProvider}. */ +public class CmsRemotingServlet extends JcrRemotingServlet { + private static final long serialVersionUID = 6459455509684213633L; + private Repository repository; + private SessionProvider sessionProvider; + + public CmsRemotingServlet() { + } + + public CmsRemotingServlet(String alias, Repository repository) { + this.repository = repository; + this.sessionProvider = new CmsSessionProvider(alias); + } + + @Override + public Repository getRepository() { + return repository; + } + + public void setRepository(Repository repository, Map properties) { + this.repository = repository; + String alias = properties.get(CmsConstants.CN); + if (alias != null) + sessionProvider = new CmsSessionProvider(alias); + else + throw new IllegalArgumentException("Only aliased repositories are supported"); + } + + @Override + protected SessionProvider getSessionProvider() { + return sessionProvider; + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsSessionProvider.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsSessionProvider.java new file mode 100644 index 000000000..0f27fd005 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsSessionProvider.java @@ -0,0 +1,174 @@ +package org.argeo.cms.jcr.internal.servlet; + +import java.io.Serializable; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.security.auth.Subject; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; + +import org.apache.jackrabbit.server.SessionProvider; +import org.argeo.api.cms.CmsSession; +import org.argeo.api.cms.CmsLog; +import org.argeo.api.cms.CmsConstants; +import org.argeo.jcr.JcrUtils; + +/** + * Implements an open session in view patter: a new JCR session is created for + * each request + */ +public class CmsSessionProvider implements SessionProvider, Serializable { + private static final long serialVersionUID = -1358136599534938466L; + + private final static CmsLog log = CmsLog.getLog(CmsSessionProvider.class); + + private final String alias; + + private LinkedHashMap cmsSessions = new LinkedHashMap<>(); + + public CmsSessionProvider(String alias) { + this.alias = alias; + } + + public Session getSession(HttpServletRequest request, Repository rep, String workspace) + throws javax.jcr.LoginException, ServletException, RepositoryException { + + // a client is scanning parent URLs. +// if (workspace == null) +// return null; + +// CmsSessionImpl cmsSession = WebCmsSessionImpl.getCmsSession(request); + // FIXME retrieve CMS session + CmsSession cmsSession = null; + if (log.isTraceEnabled()) { + log.trace("Get JCR session from " + cmsSession); + } + if (cmsSession == null) + throw new IllegalStateException("Cannot find a session for request " + request.getRequestURI()); + CmsDataSession cmsDataSession = new CmsDataSession(cmsSession); + Session session = cmsDataSession.getDataSession(alias, workspace, rep); + cmsSessions.put(session, cmsDataSession); + return session; + } + + public void releaseSession(Session session) { +// JcrUtils.logoutQuietly(session); + if (cmsSessions.containsKey(session)) { + CmsDataSession cmsDataSession = cmsSessions.get(session); + cmsDataSession.releaseDataSession(alias, session); + } else { + log.warn("JCR session " + session + " not found in CMS session list. Logging it out..."); + JcrUtils.logoutQuietly(session); + } + } + + static class CmsDataSession { + private CmsSession cmsSession; + + private Map dataSessions = new HashMap<>(); + private Set dataSessionsInUse = new HashSet<>(); + private Set additionalDataSessions = new HashSet<>(); + + private CmsDataSession(CmsSession cmsSession) { + this.cmsSession = cmsSession; + } + + public Session newDataSession(String cn, String workspace, Repository repository) { + checkValid(); + return login(repository, workspace); + } + + public synchronized Session getDataSession(String cn, String workspace, Repository repository) { + checkValid(); + // FIXME make it more robust + if (workspace == null) + workspace = CmsConstants.SYS_WORKSPACE; + String path = cn + '/' + workspace; + if (dataSessionsInUse.contains(path)) { + try { + wait(1000); + if (dataSessionsInUse.contains(path)) { + Session session = login(repository, workspace); + additionalDataSessions.add(session); + if (log.isTraceEnabled()) + log.trace("Additional data session " + path + " for " + cmsSession.getUserDn()); + return session; + } + } catch (InterruptedException e) { + // silent + } + } + + Session session = null; + if (dataSessions.containsKey(path)) { + session = dataSessions.get(path); + } else { + session = login(repository, workspace); + dataSessions.put(path, session); + if (log.isTraceEnabled()) + log.trace("New data session " + path + " for " + cmsSession.getUserDn()); + } + dataSessionsInUse.add(path); + return session; + } + + private Session login(Repository repository, String workspace) { + try { + return Subject.doAs(cmsSession.getSubject(), new PrivilegedExceptionAction() { + @Override + public Session run() throws Exception { + return repository.login(workspace); + } + }); + } catch (PrivilegedActionException e) { + throw new IllegalStateException("Cannot log in " + cmsSession.getUserDn() + " to JCR", e); + } + } + + public synchronized void releaseDataSession(String cn, Session session) { + if (additionalDataSessions.contains(session)) { + JcrUtils.logoutQuietly(session); + additionalDataSessions.remove(session); + if (log.isTraceEnabled()) + log.trace("Remove additional data session " + session); + return; + } + String path = cn + '/' + session.getWorkspace().getName(); + if (!dataSessionsInUse.contains(path)) + log.warn("Data session " + path + " was not in use for " + cmsSession.getUserDn()); + dataSessionsInUse.remove(path); + Session registeredSession = dataSessions.get(path); + if (session != registeredSession) + log.warn("Data session " + path + " not consistent for " + cmsSession.getUserDn()); + if (log.isTraceEnabled()) + log.trace("Released data session " + session + " for " + path); + notifyAll(); + } + + private void checkValid() { + if (!cmsSession.isValid()) + throw new IllegalStateException( + "CMS session " + cmsSession.getUuid() + " is not valid since " + cmsSession.getEnd()); + } + + private void close() { + // FIXME class this when CMS session is closed + synchronized (this) { + // TODO check data session in use ? + for (String path : dataSessions.keySet()) + JcrUtils.logoutQuietly(dataSessions.get(path)); + for (Session session : additionalDataSessions) + JcrUtils.logoutQuietly(session); + } + } + } +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsWebDavServlet.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsWebDavServlet.java new file mode 100644 index 000000000..0f0858f51 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsWebDavServlet.java @@ -0,0 +1,37 @@ +package org.argeo.cms.jcr.internal.servlet; + +import java.util.Map; + +import javax.jcr.Repository; + +import org.apache.jackrabbit.webdav.simple.SimpleWebdavServlet; +import org.argeo.api.cms.CmsConstants; + +/** A {@link SimpleWebdavServlet} based on {@link CmsSessionProvider}. */ +public class CmsWebDavServlet extends SimpleWebdavServlet { + private static final long serialVersionUID = 7485800288686328063L; + private Repository repository; + + public CmsWebDavServlet() { + } + + public CmsWebDavServlet(String alias, Repository repository) { + this.repository = repository; + setSessionProvider(new CmsSessionProvider(alias)); + } + + @Override + public Repository getRepository() { + return repository; + } + + public void setRepository(Repository repository, Map properties) { + this.repository = repository; + String alias = properties.get(CmsConstants.CN); + if (alias != null) + setSessionProvider(new CmsSessionProvider(alias)); + else + throw new IllegalArgumentException("Only aliased repositories are supported"); + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/DataServletContext.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/DataServletContext.java new file mode 100644 index 000000000..2f60e97d9 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/DataServletContext.java @@ -0,0 +1,8 @@ +package org.argeo.cms.jcr.internal.servlet; + +import org.argeo.cms.servlet.CmsServletContext; + +/** Internal subclass, so that config resources can be loaded from our bundle. */ +public class DataServletContext extends CmsServletContext { + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/JcrHttpUtils.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/JcrHttpUtils.java new file mode 100644 index 000000000..11e903db8 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/JcrHttpUtils.java @@ -0,0 +1,73 @@ +package org.argeo.cms.jcr.internal.servlet; + +import java.util.Enumeration; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.argeo.api.cms.CmsLog; + +public class JcrHttpUtils { + public final static String HEADER_AUTHORIZATION = "Authorization"; + public final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate"; + + public final static String DEFAULT_PROTECTED_HANDLERS = "/org/argeo/cms/jcr/internal/servlet/protectedHandlers.xml"; + public final static String WEBDAV_CONFIG = "/org/argeo/cms/jcr/internal/servlet/webdav-config.xml"; + + static boolean isBrowser(String userAgent) { + return userAgent.contains("webkit") || userAgent.contains("gecko") || userAgent.contains("firefox") + || userAgent.contains("msie") || userAgent.contains("chrome") || userAgent.contains("chromium") + || userAgent.contains("opera") || userAgent.contains("browser"); + } + + public static void logResponseHeaders(CmsLog log, HttpServletResponse response) { + if (!log.isDebugEnabled()) + return; + for (String headerName : response.getHeaderNames()) { + Object headerValue = response.getHeader(headerName); + log.debug(headerName + ": " + headerValue); + } + } + + public static void logRequestHeaders(CmsLog log, HttpServletRequest request) { + if (!log.isDebugEnabled()) + return; + for (Enumeration headerNames = request.getHeaderNames(); headerNames.hasMoreElements();) { + String headerName = headerNames.nextElement(); + Object headerValue = request.getHeader(headerName); + log.debug(headerName + ": " + headerValue); + } + log.debug(request.getRequestURI() + "\n"); + } + + public static void logRequest(CmsLog log, HttpServletRequest request) { + log.debug("contextPath=" + request.getContextPath()); + log.debug("servletPath=" + request.getServletPath()); + log.debug("requestURI=" + request.getRequestURI()); + log.debug("queryString=" + request.getQueryString()); + StringBuilder buf = new StringBuilder(); + // headers + Enumeration en = request.getHeaderNames(); + while (en.hasMoreElements()) { + String header = en.nextElement(); + Enumeration values = request.getHeaders(header); + while (values.hasMoreElements()) + buf.append(" " + header + ": " + values.nextElement()); + buf.append('\n'); + } + + // attributed + Enumeration an = request.getAttributeNames(); + while (an.hasMoreElements()) { + String attr = an.nextElement(); + Object value = request.getAttribute(attr); + buf.append(" " + attr + ": " + value); + buf.append('\n'); + } + log.debug("\n" + buf); + } + + private JcrHttpUtils() { + + } +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/JcrServletContext.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/JcrServletContext.java new file mode 100644 index 000000000..21046f34e --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/JcrServletContext.java @@ -0,0 +1,8 @@ +package org.argeo.cms.jcr.internal.servlet; + +import org.argeo.cms.servlet.PrivateWwwAuthServletContext; + +/** Internal subclass, so that config resources can be loaded from our bundle. */ +public class JcrServletContext extends PrivateWwwAuthServletContext { + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/LinkServlet.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/LinkServlet.java new file mode 100644 index 000000000..62cdc5f6b --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/LinkServlet.java @@ -0,0 +1,259 @@ +package org.argeo.cms.jcr.internal.servlet; + +import static javax.jcr.Property.JCR_DESCRIPTION; +import static javax.jcr.Property.JCR_LAST_MODIFIED; +import static javax.jcr.Property.JCR_TITLE; + +import java.io.IOException; +import java.io.PrintWriter; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.PrivilegedExceptionAction; +import java.util.Calendar; +import java.util.Collection; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.security.auth.Subject; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.argeo.api.cms.CmsAuth; +import org.argeo.api.cms.CmsConstants; +import org.argeo.cms.CmsException; +import org.argeo.cms.jcr.CmsJcrUtils; +import org.argeo.jcr.JcrUtils; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; + +public class LinkServlet extends HttpServlet { + private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); + + private static final long serialVersionUID = 3749990143146845708L; + + @Override + protected void service(HttpServletRequest request, HttpServletResponse response) + throws ServletException, IOException { + String path = request.getPathInfo(); + String userAgent = request.getHeader("User-Agent").toLowerCase(); + boolean isBot = false; + // boolean isCompatibleBrowser = false; + if (userAgent.contains("bot") || userAgent.contains("facebook") || userAgent.contains("twitter")) { + isBot = true; + } + // else if (userAgent.contains("webkit") || + // userAgent.contains("gecko") || userAgent.contains("firefox") + // || userAgent.contains("msie") || userAgent.contains("chrome") || + // userAgent.contains("chromium") + // || userAgent.contains("opera") || userAgent.contains("browser")) + // { + // isCompatibleBrowser = true; + // } + + if (isBot) { + // log.warn("# BOT " + request.getHeader("User-Agent")); + canonicalAnswer(request, response, path); + return; + } + + // if (isCompatibleBrowser && log.isTraceEnabled()) + // log.trace("# BWS " + request.getHeader("User-Agent")); + redirectTo(response, "/#" + path); + } + + private void redirectTo(HttpServletResponse response, String location) { + response.setHeader("Location", location); + response.setStatus(HttpServletResponse.SC_FOUND); + } + + // private boolean canonicalAnswerNeededBy(HttpServletRequest request) { + // String userAgent = request.getHeader("User-Agent").toLowerCase(); + // return userAgent.startsWith("facebookexternalhit/"); + // } + + /** For bots which don't understand RWT. */ + private void canonicalAnswer(HttpServletRequest request, HttpServletResponse response, String path) { + Session session = null; + try { + PrintWriter writer = response.getWriter(); + session = Subject.doAs(anonymousLogin(), new PrivilegedExceptionAction() { + + @Override + public Session run() throws Exception { + Collection> srs = bc.getServiceReferences(Repository.class, + "(" + CmsConstants.CN + "=" + CmsConstants.EGO_REPOSITORY + ")"); + Repository repository = bc.getService(srs.iterator().next()); + return repository.login(); + } + + }); + Node node = session.getNode(path); + String title = node.hasProperty(JCR_TITLE) ? node.getProperty(JCR_TITLE).getString() : node.getName(); + String desc = node.hasProperty(JCR_DESCRIPTION) ? node.getProperty(JCR_DESCRIPTION).getString() : null; + Calendar lastUpdate = node.hasProperty(JCR_LAST_MODIFIED) ? node.getProperty(JCR_LAST_MODIFIED).getDate() + : null; + String url = getCanonicalUrl(node, request); + String imgUrl = null; + // TODO support images +// loop: for (NodeIterator it = node.getNodes(); it.hasNext();) { +// // Takes the first found cms:image +// Node child = it.nextNode(); +// if (child.isNodeType(CMS_IMAGE)) { +// imgUrl = getDataUrl(child, request); +// break loop; +// } +// } + StringBuilder buf = new StringBuilder(); + buf.append(""); + buf.append(""); + writeMeta(buf, "og:title", escapeHTML(title)); + writeMeta(buf, "og:type", "website"); + buf.append(""); + buf.append(""); + writeMeta(buf, "og:url", url); + if (desc != null) + writeMeta(buf, "og:description", escapeHTML(desc)); + if (imgUrl != null) + writeMeta(buf, "og:image", imgUrl); + if (lastUpdate != null) + writeMeta(buf, "og:updated_time", Long.toString(lastUpdate.getTime().getTime())); + buf.append(""); + buf.append(""); + buf.append("

!! This page is meant for indexing robots, not for real people," + " visit ").append(escapeHTML(title)).append(" instead.

"); + writeCanonical(buf, node); + buf.append(""); + buf.append(""); + writer.print(buf.toString()); + + response.setHeader("Content-Type", "text/html"); + writer.flush(); + } catch (Exception e) { + throw new CmsException("Cannot write canonical answer", e); + } finally { + JcrUtils.logoutQuietly(session); + } + } + + /** + * From http://stackoverflow.com/questions/1265282/recommended-method-for- + * escaping-html-in-java (+ escaping '). TODO Use + * org.apache.commons.lang.StringEscapeUtils + */ + private String escapeHTML(String s) { + StringBuilder out = new StringBuilder(Math.max(16, s.length())); + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c > 127 || c == '\'' || c == '"' || c == '<' || c == '>' || c == '&') { + out.append("&#"); + out.append((int) c); + out.append(';'); + } else { + out.append(c); + } + } + return out.toString(); + } + + private void writeMeta(StringBuilder buf, String tag, String value) { + buf.append(""); + } + + private void writeCanonical(StringBuilder buf, Node node) throws RepositoryException { + buf.append("
"); + if (node.hasProperty(JCR_TITLE)) + buf.append("

").append(node.getProperty(JCR_TITLE).getString()).append("

"); + if (node.hasProperty(JCR_DESCRIPTION)) + buf.append("

").append(node.getProperty(JCR_DESCRIPTION).getString()).append("

"); + NodeIterator children = node.getNodes(); + while (children.hasNext()) { + writeCanonical(buf, children.nextNode()); + } + buf.append("
"); + } + + // DATA + private StringBuilder getServerBaseUrl(HttpServletRequest request) { + try { + URL url = new URL(request.getRequestURL().toString()); + StringBuilder buf = new StringBuilder(); + buf.append(url.getProtocol()).append("://").append(url.getHost()); + if (url.getPort() != -1) + buf.append(':').append(url.getPort()); + return buf; + } catch (MalformedURLException e) { + throw new CmsException("Cannot extract server base URL from " + request.getRequestURL(), e); + } + } + + private String getDataUrl(Node node, HttpServletRequest request) throws RepositoryException { + try { + StringBuilder buf = getServerBaseUrl(request); + buf.append(CmsJcrUtils.getDataPath(CmsConstants.EGO_REPOSITORY, node)); + return new URL(buf.toString()).toString(); + } catch (MalformedURLException e) { + throw new CmsException("Cannot build data URL for " + node, e); + } + } + + // public static String getDataPath(Node node) throws + // RepositoryException { + // assert node != null; + // String userId = node.getSession().getUserID(); + //// if (log.isTraceEnabled()) + //// log.trace(userId + " : " + node.getPath()); + // StringBuilder buf = new StringBuilder(); + // boolean isAnonymous = + // userId.equalsIgnoreCase(NodeConstants.ROLE_ANONYMOUS); + // if (isAnonymous) + // buf.append(WEBDAV_PUBLIC); + // else + // buf.append(WEBDAV_PRIVATE); + // Session session = node.getSession(); + // Repository repository = session.getRepository(); + // String cn; + // if (repository.isSingleValueDescriptor(NodeConstants.CN)) { + // cn = repository.getDescriptor(NodeConstants.CN); + // } else { + //// log.warn("No cn defined in repository, using " + + // NodeConstants.NODE); + // cn = NodeConstants.NODE; + // } + // return + // buf.append('/').append(cn).append('/').append(session.getWorkspace().getName()).append(node.getPath()) + // .toString(); + // } + + private String getCanonicalUrl(Node node, HttpServletRequest request) throws RepositoryException { + try { + StringBuilder buf = getServerBaseUrl(request); + buf.append('/').append('!').append(node.getPath()); + return new URL(buf.toString()).toString(); + } catch (MalformedURLException e) { + throw new CmsException("Cannot build data URL for " + node, e); + } + // return request.getRequestURL().append('!').append(node.getPath()) + // .toString(); + } + + private Subject anonymousLogin() { + Subject subject = new Subject(); + LoginContext lc; + try { + lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS, subject); + lc.login(); + return subject; + } catch (LoginException e) { + throw new CmsException("Cannot login as anonymous", e); + } + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/protectedHandlers.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/protectedHandlers.xml new file mode 100644 index 000000000..59f22cd5e --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/protectedHandlers.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/webdav-config.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/webdav-config.xml new file mode 100644 index 000000000..436389898 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/webdav-config.xml @@ -0,0 +1,207 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + nt:file + nt:resource + + + + + + + + + + + + + rep + jcr + + node + argeo + cms + slc + connect + activities + people + documents + tracker + + + + + + + diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/ldap.cnd b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/ldap.cnd new file mode 100644 index 000000000..a2306c60e --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/ldap.cnd @@ -0,0 +1 @@ + diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/node.cnd b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/node.cnd new file mode 100644 index 000000000..d8a26b64e --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/node.cnd @@ -0,0 +1,9 @@ + + +[node:userHome] +mixin +- ldap:uid (STRING) m + +[node:groupHome] +mixin +- ldap:cn (STRING) m diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/CsvTabularWriter.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/CsvTabularWriter.java new file mode 100644 index 000000000..ccd543f4d --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/CsvTabularWriter.java @@ -0,0 +1,23 @@ +package org.argeo.cms.jcr.tabular; + +import java.io.OutputStream; + +import org.argeo.cms.tabular.TabularWriter; +import org.argeo.util.CsvWriter; + +/** Write tabular content in a stream as CSV. Wraps a {@link CsvWriter}. */ +public class CsvTabularWriter implements TabularWriter { + private CsvWriter csvWriter; + + public CsvTabularWriter(OutputStream out) { + this.csvWriter = new CsvWriter(out); + } + + public void appendRow(Object[] row) { + csvWriter.writeLine(row); + } + + public void close() { + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/JcrTabularRowIterator.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/JcrTabularRowIterator.java new file mode 100644 index 000000000..d1d9b583b --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/JcrTabularRowIterator.java @@ -0,0 +1,170 @@ +package org.argeo.cms.jcr.tabular; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; + +import javax.jcr.Binary; +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.Property; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; + +import org.apache.commons.io.IOUtils; +import org.argeo.cms.ArgeoTypes; +import org.argeo.cms.tabular.ArrayTabularRow; +import org.argeo.cms.tabular.TabularColumn; +import org.argeo.cms.tabular.TabularRow; +import org.argeo.cms.tabular.TabularRowIterator; +import org.argeo.jcr.JcrException; +import org.argeo.util.CsvParser; + +/** Iterates over the rows of a {@link ArgeoTypes#ARGEO_TABLE} node. */ +public class JcrTabularRowIterator implements TabularRowIterator { + private Boolean hasNext = null; + private Boolean parsingCompleted = false; + + private Long currentRowNumber = 0l; + + private List header = new ArrayList(); + + /** referenced so that we can close it */ + private Binary binary; + private InputStream in; + + private CsvParser csvParser; + private ArrayBlockingQueue> textLines; + + public JcrTabularRowIterator(Node tableNode) { + try { + for (NodeIterator it = tableNode.getNodes(); it.hasNext();) { + Node node = it.nextNode(); + if (node.isNodeType(ArgeoTypes.ARGEO_COLUMN)) { + Integer type = PropertyType.valueFromName(node.getProperty( + Property.JCR_REQUIRED_TYPE).getString()); + TabularColumn tc = new TabularColumn(node.getProperty( + Property.JCR_TITLE).getString(), type); + header.add(tc); + } + } + Node contentNode = tableNode.getNode(Property.JCR_CONTENT); + if (contentNode.isNodeType(ArgeoTypes.ARGEO_CSV)) { + textLines = new ArrayBlockingQueue>(1000); + csvParser = new CsvParser() { + protected void processLine(Integer lineNumber, + List header, List tokens) { + try { + textLines.put(tokens); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + // textLines.add(tokens); + if (hasNext == null) { + hasNext = true; + synchronized (JcrTabularRowIterator.this) { + JcrTabularRowIterator.this.notifyAll(); + } + } + } + }; + csvParser.setNoHeader(true); + binary = contentNode.getProperty(Property.JCR_DATA).getBinary(); + in = binary.getStream(); + Thread thread = new Thread(contentNode.getPath() + " reader") { + public void run() { + try { + csvParser.parse(in); + } finally { + parsingCompleted = true; + IOUtils.closeQuietly(in); + } + } + }; + thread.start(); + } + } catch (RepositoryException e) { + throw new JcrException("Cannot read table " + tableNode, e); + } + } + + public synchronized boolean hasNext() { + // we don't know if there is anything available + // while (hasNext == null) + // try { + // wait(); + // } catch (InterruptedException e) { + // // silent + // // FIXME better deal with interruption + // Thread.currentThread().interrupt(); + // break; + // } + + // buffer not empty + if (!textLines.isEmpty()) + return true; + + // maybe the parsing is finished but the flag has not been set + while (!parsingCompleted && textLines.isEmpty()) + try { + wait(100); + } catch (InterruptedException e) { + // silent + // FIXME better deal with interruption + Thread.currentThread().interrupt(); + break; + } + + // buffer not empty + if (!textLines.isEmpty()) + return true; + + // (parsingCompleted && textLines.isEmpty()) + return false; + + // if (!hasNext && textLines.isEmpty()) { + // if (in != null) { + // IOUtils.closeQuietly(in); + // in = null; + // } + // if (binary != null) { + // JcrUtils.closeQuietly(binary); + // binary = null; + // } + // return false; + // } else + // return true; + } + + public synchronized TabularRow next() { + try { + List tokens = textLines.take(); + List objs = new ArrayList(tokens.size()); + for (String token : tokens) { + // TODO convert to other formats using header + objs.add(token); + } + currentRowNumber++; + return new ArrayTabularRow(objs); + } catch (InterruptedException e) { + // silent + // FIXME better deal with interruption + } + return null; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + public Long getCurrentRowNumber() { + return currentRowNumber; + } + + public List getHeader() { + return header; + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/JcrTabularWriter.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/JcrTabularWriter.java new file mode 100644 index 000000000..cc3e0d7a9 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/JcrTabularWriter.java @@ -0,0 +1,82 @@ +package org.argeo.cms.jcr.tabular; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.util.List; + +import javax.jcr.Binary; +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; + +import org.apache.commons.io.IOUtils; +import org.argeo.cms.ArgeoTypes; +import org.argeo.cms.tabular.TabularColumn; +import org.argeo.cms.tabular.TabularWriter; +import org.argeo.jcr.JcrException; +import org.argeo.jcr.JcrUtils; +import org.argeo.util.CsvWriter; + +/** Write / reference tabular content in a JCR repository. */ +public class JcrTabularWriter implements TabularWriter { + private Node contentNode; + private ByteArrayOutputStream out; + private CsvWriter csvWriter; + + @SuppressWarnings("unused") + private final List columns; + + /** Creates a table node */ + public JcrTabularWriter(Node tableNode, List columns, + String contentNodeType) { + try { + this.columns = columns; + for (TabularColumn column : columns) { + String normalized = JcrUtils.replaceInvalidChars(column + .getName()); + Node columnNode = tableNode.addNode(normalized, + ArgeoTypes.ARGEO_COLUMN); + columnNode.setProperty(Property.JCR_TITLE, column.getName()); + if (column.getType() != null) + columnNode.setProperty(Property.JCR_REQUIRED_TYPE, + PropertyType.nameFromValue(column.getType())); + else + columnNode.setProperty(Property.JCR_REQUIRED_TYPE, + PropertyType.TYPENAME_STRING); + } + contentNode = tableNode.addNode(Property.JCR_CONTENT, + contentNodeType); + if (contentNodeType.equals(ArgeoTypes.ARGEO_CSV)) { + contentNode.setProperty(Property.JCR_MIMETYPE, "text/csv"); + contentNode.setProperty(Property.JCR_ENCODING, "UTF-8"); + out = new ByteArrayOutputStream(); + csvWriter = new CsvWriter(out); + } + } catch (RepositoryException e) { + throw new JcrException("Cannot create table node " + tableNode, e); + } + } + + public void appendRow(Object[] row) { + csvWriter.writeLine(row); + } + + public void close() { + Binary binary = null; + InputStream in = null; + try { + // TODO parallelize with pipes and writing from another thread + in = new ByteArrayInputStream(out.toByteArray()); + binary = contentNode.getSession().getValueFactory() + .createBinary(in); + contentNode.setProperty(Property.JCR_DATA, binary); + } catch (RepositoryException e) { + throw new JcrException("Cannot store data in " + contentNode, e); + } finally { + IOUtils.closeQuietly(in); + JcrUtils.closeQuietly(binary); + } + } +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/package-info.java b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/package-info.java new file mode 100644 index 000000000..506a6ac98 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/package-info.java @@ -0,0 +1,2 @@ +/** Argeo CMS implementation of the Argeo Tabular API (CSV, JCR). */ +package org.argeo.cms.jcr.tabular; \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/JackrabbitAdminLoginModule.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/JackrabbitAdminLoginModule.java new file mode 100644 index 000000000..7396c87e7 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/JackrabbitAdminLoginModule.java @@ -0,0 +1,48 @@ +package org.argeo.jackrabbit; + +import java.util.Map; + +import javax.security.auth.Subject; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.login.LoginException; +import javax.security.auth.spi.LoginModule; + +import org.apache.jackrabbit.core.security.SecurityConstants; +import org.apache.jackrabbit.core.security.principal.AdminPrincipal; + +@Deprecated +public class JackrabbitAdminLoginModule implements LoginModule { + private Subject subject; + + @Override + public void initialize(Subject subject, CallbackHandler callbackHandler, + Map sharedState, Map options) { + this.subject = subject; + } + + @Override + public boolean login() throws LoginException { + // TODO check permission? + return true; + } + + @Override + public boolean commit() throws LoginException { + subject.getPrincipals().add( + new AdminPrincipal(SecurityConstants.ADMIN_ID)); + return true; + } + + @Override + public boolean abort() throws LoginException { + return true; + } + + @Override + public boolean logout() throws LoginException { + subject.getPrincipals().removeAll( + subject.getPrincipals(AdminPrincipal.class)); + return true; + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/JackrabbitDataModelMigration.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/JackrabbitDataModelMigration.java new file mode 100644 index 000000000..8c267e314 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/JackrabbitDataModelMigration.java @@ -0,0 +1,172 @@ +package org.argeo.jackrabbit; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.Reader; +import java.net.URL; + +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.apache.commons.io.IOUtils; +import org.apache.jackrabbit.commons.cnd.CndImporter; +import org.apache.jackrabbit.commons.cnd.ParseException; +import org.apache.jackrabbit.core.config.RepositoryConfig; +import org.apache.jackrabbit.core.fs.FileSystemException; +import org.argeo.api.cms.CmsLog; +import org.argeo.jcr.JcrCallback; +import org.argeo.jcr.JcrException; +import org.argeo.jcr.JcrUtils; + +/** Migrate the data in a Jackrabbit repository. */ +@Deprecated +public class JackrabbitDataModelMigration implements Comparable { + private final static CmsLog log = CmsLog.getLog(JackrabbitDataModelMigration.class); + + private String dataModelNodePath; + private String targetVersion; + private URL migrationCnd; + private JcrCallback dataModification; + + /** + * Expects an already started repository with the old data model to migrate. + * Expects to be run with admin rights (Repository.login() will be used). + * + * @return true if a migration was performed and the repository needs to be + * restarted and its caches cleared. + */ + public Boolean migrate(Session session) { + long begin = System.currentTimeMillis(); + Reader reader = null; + try { + // check if already migrated + if (!session.itemExists(dataModelNodePath)) { +// log.warn("Node " + dataModelNodePath + " does not exist: nothing to migrate."); + return false; + } +// Node dataModelNode = session.getNode(dataModelNodePath); +// if (dataModelNode.hasProperty(ArgeoNames.ARGEO_DATA_MODEL_VERSION)) { +// String currentVersion = dataModelNode.getProperty( +// ArgeoNames.ARGEO_DATA_MODEL_VERSION).getString(); +// if (compareVersions(currentVersion, targetVersion) >= 0) { +// log.info("Data model at version " + currentVersion +// + ", no need to migrate."); +// return false; +// } +// } + + // apply transitional CND + if (migrationCnd != null) { + reader = new InputStreamReader(migrationCnd.openStream()); + CndImporter.registerNodeTypes(reader, session, true); + session.save(); +// log.info("Registered migration node types from " + migrationCnd); + } + + // modify data + dataModification.execute(session); + + // apply changes + session.save(); + + long duration = System.currentTimeMillis() - begin; +// log.info("Migration of data model " + dataModelNodePath + " to " + targetVersion + " performed in " +// + duration + "ms"); + return true; + } catch (RepositoryException e) { + JcrUtils.discardQuietly(session); + throw new JcrException("Migration of data model " + dataModelNodePath + " to " + targetVersion + " failed.", + e); + } catch (ParseException | IOException e) { + JcrUtils.discardQuietly(session); + throw new RuntimeException( + "Migration of data model " + dataModelNodePath + " to " + targetVersion + " failed.", e); + } finally { + JcrUtils.logoutQuietly(session); + IOUtils.closeQuietly(reader); + } + } + + protected static int compareVersions(String version1, String version2) { + // TODO do a proper version analysis and comparison + return version1.compareTo(version2); + } + + /** To be called on a stopped repository. */ + public static void clearRepositoryCaches(RepositoryConfig repositoryConfig) { + try { + String customeNodeTypesPath = "/nodetypes/custom_nodetypes.xml"; + // FIXME causes weird error in Eclipse + repositoryConfig.getFileSystem().deleteFile(customeNodeTypesPath); + if (log.isDebugEnabled()) + log.debug("Cleared " + customeNodeTypesPath); + } catch (RuntimeException e) { + throw e; + } catch (RepositoryException e) { + throw new JcrException(e); + } catch (FileSystemException e) { + throw new RuntimeException("Cannot clear node types cache.",e); + } + + // File customNodeTypes = new File(home.getPath() + // + "/repository/nodetypes/custom_nodetypes.xml"); + // if (customNodeTypes.exists()) { + // customNodeTypes.delete(); + // if (log.isDebugEnabled()) + // log.debug("Cleared " + customNodeTypes); + // } else { + // log.warn("File " + customNodeTypes + " not found."); + // } + } + + /* + * FOR USE IN (SORTED) SETS + */ + + public int compareTo(JackrabbitDataModelMigration dataModelMigration) { + // TODO make ordering smarter + if (dataModelNodePath.equals(dataModelMigration.dataModelNodePath)) + return compareVersions(targetVersion, dataModelMigration.targetVersion); + else + return dataModelNodePath.compareTo(dataModelMigration.dataModelNodePath); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof JackrabbitDataModelMigration)) + return false; + JackrabbitDataModelMigration dataModelMigration = (JackrabbitDataModelMigration) obj; + return dataModelNodePath.equals(dataModelMigration.dataModelNodePath) + && targetVersion.equals(dataModelMigration.targetVersion); + } + + @Override + public int hashCode() { + return targetVersion.hashCode(); + } + + public void setDataModelNodePath(String dataModelNodePath) { + this.dataModelNodePath = dataModelNodePath; + } + + public void setTargetVersion(String targetVersion) { + this.targetVersion = targetVersion; + } + + public void setMigrationCnd(URL migrationCnd) { + this.migrationCnd = migrationCnd; + } + + public void setDataModification(JcrCallback dataModification) { + this.dataModification = dataModification; + } + + public String getDataModelNodePath() { + return dataModelNodePath; + } + + public String getTargetVersion() { + return targetVersion; + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryFactory.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryFactory.java new file mode 100644 index 000000000..77ad527e1 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryFactory.java @@ -0,0 +1,26 @@ +package org.argeo.jackrabbit.client; + +import java.util.Map; + +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.RepositoryFactory; + +import org.apache.jackrabbit.jcr2spi.Jcr2spiRepositoryFactory; +import org.apache.jackrabbit.jcr2spi.RepositoryImpl; +import org.apache.jackrabbit.spi.RepositoryServiceFactory; + +/** A customised {@link RepositoryFactory} access a remote DAVEX service. */ +public class ClientDavexRepositoryFactory implements RepositoryFactory { + public final static String JACKRABBIT_DAVEX_URI = ClientDavexRepositoryServiceFactory.PARAM_REPOSITORY_URI; + public final static String JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = ClientDavexRepositoryServiceFactory.PARAM_WORKSPACE_NAME_DEFAULT; + + @SuppressWarnings("rawtypes") + @Override + public Repository getRepository(Map parameters) throws RepositoryException { + RepositoryServiceFactory repositoryServiceFactory = new ClientDavexRepositoryServiceFactory(); + return RepositoryImpl + .create(new Jcr2spiRepositoryFactory.RepositoryConfigImpl(repositoryServiceFactory, parameters)); + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryService.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryService.java new file mode 100644 index 000000000..0f9db8772 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryService.java @@ -0,0 +1,40 @@ +package org.argeo.jackrabbit.client; + +import javax.jcr.RepositoryException; + +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.protocol.HttpContext; +import org.apache.jackrabbit.spi.SessionInfo; +import org.apache.jackrabbit.spi2davex.BatchReadConfig; +import org.apache.jackrabbit.spi2davex.RepositoryServiceImpl; + +/** + * Wrapper for {@link RepositoryServiceImpl} in order to access the underlying + * {@link HttpClientContext}. + */ +public class ClientDavexRepositoryService extends RepositoryServiceImpl { + + public ClientDavexRepositoryService(String jcrServerURI, BatchReadConfig batchReadConfig) + throws RepositoryException { + super(jcrServerURI, batchReadConfig); + } + + public ClientDavexRepositoryService(String jcrServerURI, String defaultWorkspaceName, + BatchReadConfig batchReadConfig, int itemInfoCacheSize, int maximumHttpConnections) + throws RepositoryException { + super(jcrServerURI, defaultWorkspaceName, batchReadConfig, itemInfoCacheSize, maximumHttpConnections); + } + + public ClientDavexRepositoryService(String jcrServerURI, String defaultWorkspaceName, + BatchReadConfig batchReadConfig, int itemInfoCacheSize) throws RepositoryException { + super(jcrServerURI, defaultWorkspaceName, batchReadConfig, itemInfoCacheSize); + } + + @Override + protected HttpContext getContext(SessionInfo sessionInfo) throws RepositoryException { + HttpClientContext result = HttpClientContext.create(); + result.setAuthCache(new NonSerialBasicAuthCache()); + return result; + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryServiceFactory.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryServiceFactory.java new file mode 100644 index 000000000..4b240f060 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryServiceFactory.java @@ -0,0 +1,82 @@ +package org.argeo.jackrabbit.client; + +import java.util.Map; + +import javax.jcr.RepositoryException; + +import org.apache.jackrabbit.spi.RepositoryService; +import org.apache.jackrabbit.spi.commons.ItemInfoCacheImpl; +import org.apache.jackrabbit.spi2davex.BatchReadConfig; +import org.apache.jackrabbit.spi2davex.Spi2davexRepositoryServiceFactory; + +/** + * Wrapper for {@link Spi2davexRepositoryServiceFactory} in order to create a + * {@link ClientDavexRepositoryService}. + */ +public class ClientDavexRepositoryServiceFactory extends Spi2davexRepositoryServiceFactory { + @Override + public RepositoryService createRepositoryService(Map parameters) throws RepositoryException { + // retrieve the repository uri + String uri; + if (parameters == null) { + uri = System.getProperty(PARAM_REPOSITORY_URI); + } else { + Object repoUri = parameters.get(PARAM_REPOSITORY_URI); + uri = (repoUri == null) ? null : repoUri.toString(); + } + if (uri == null) { + uri = DEFAULT_REPOSITORY_URI; + } + + // load other optional configuration parameters + BatchReadConfig brc = null; + int itemInfoCacheSize = ItemInfoCacheImpl.DEFAULT_CACHE_SIZE; + int maximumHttpConnections = 0; + + // since JCR-4120 the default workspace name is no longer set to 'default' + // note: if running with JCR Server < 1.5 a default workspace name must + // therefore be configured + String workspaceNameDefault = null; + + if (parameters != null) { + // batchRead config + Object param = parameters.get(PARAM_BATCHREAD_CONFIG); + if (param != null && param instanceof BatchReadConfig) { + brc = (BatchReadConfig) param; + } + + // itemCache size config + param = parameters.get(PARAM_ITEMINFO_CACHE_SIZE); + if (param != null) { + try { + itemInfoCacheSize = Integer.parseInt(param.toString()); + } catch (NumberFormatException e) { + // ignore, use default + } + } + + // max connections config + param = parameters.get(PARAM_MAX_CONNECTIONS); + if (param != null) { + try { + maximumHttpConnections = Integer.parseInt(param.toString()); + } catch (NumberFormatException e) { + // using default + } + } + + param = parameters.get(PARAM_WORKSPACE_NAME_DEFAULT); + if (param != null) { + workspaceNameDefault = param.toString(); + } + } + + if (maximumHttpConnections > 0) { + return new ClientDavexRepositoryService(uri, workspaceNameDefault, brc, itemInfoCacheSize, + maximumHttpConnections); + } else { + return new ClientDavexRepositoryService(uri, workspaceNameDefault, brc, itemInfoCacheSize); + } + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/JackrabbitClient.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/JackrabbitClient.java new file mode 100644 index 000000000..e08f4d6c7 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/JackrabbitClient.java @@ -0,0 +1,125 @@ +package org.argeo.jackrabbit.client; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.Map; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.RepositoryFactory; +import javax.jcr.Session; + +import org.apache.http.client.protocol.HttpClientContext; +import org.apache.http.protocol.HttpContext; +import org.apache.jackrabbit.jcr2dav.Jcr2davRepositoryFactory; +import org.apache.jackrabbit.jcr2spi.Jcr2spiRepositoryFactory; +import org.apache.jackrabbit.jcr2spi.RepositoryImpl; +import org.apache.jackrabbit.spi.RepositoryService; +import org.apache.jackrabbit.spi.RepositoryServiceFactory; +import org.apache.jackrabbit.spi.SessionInfo; +import org.apache.jackrabbit.spi.commons.ItemInfoCacheImpl; +import org.apache.jackrabbit.spi2davex.BatchReadConfig; +import org.apache.jackrabbit.spi2davex.RepositoryServiceImpl; +import org.apache.jackrabbit.spi2davex.Spi2davexRepositoryServiceFactory; +import org.argeo.jcr.JcrUtils; + +/** Minimal client to test JCR DAVEX connectivity. */ +public class JackrabbitClient { + final static String JACKRABBIT_REPOSITORY_URI = "org.apache.jackrabbit.repository.uri"; + final static String JACKRABBIT_DAVEX_URI = "org.apache.jackrabbit.spi2davex.uri"; + final static String JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = "org.apache.jackrabbit.spi2davex.WorkspaceNameDefault"; + + public static void main(String[] args) { + String repoUri = args.length == 0 ? "http://root:demo@localhost:7070/jcr/ego" : args[0]; + String workspace = args.length < 2 ? "home" : args[1]; + + Repository repository = null; + Session session = null; + + URI uri; + try { + uri = new URI(repoUri); + } catch (URISyntaxException e1) { + throw new IllegalArgumentException(e1); + } + + if (uri.getScheme().equals("http") || uri.getScheme().equals("https")) { + + RepositoryFactory repositoryFactory = new Jcr2davRepositoryFactory() { + @SuppressWarnings("rawtypes") + public Repository getRepository(Map parameters) throws RepositoryException { + RepositoryServiceFactory repositoryServiceFactory = new Spi2davexRepositoryServiceFactory() { + + @Override + public RepositoryService createRepositoryService(Map parameters) + throws RepositoryException { + Object uri = parameters.get(JACKRABBIT_DAVEX_URI); + Object defaultWorkspace = parameters.get(JACKRABBIT_REMOTE_DEFAULT_WORKSPACE); + BatchReadConfig brc = null; + return new RepositoryServiceImpl(uri.toString(), defaultWorkspace.toString(), brc, + ItemInfoCacheImpl.DEFAULT_CACHE_SIZE) { + + @Override + protected HttpContext getContext(SessionInfo sessionInfo) throws RepositoryException { + HttpClientContext result = HttpClientContext.create(); + result.setAuthCache(new NonSerialBasicAuthCache()); + return result; + } + + }; + } + }; + return RepositoryImpl.create( + new Jcr2spiRepositoryFactory.RepositoryConfigImpl(repositoryServiceFactory, parameters)); + } + }; + Map params = new HashMap(); + params.put(JACKRABBIT_DAVEX_URI, repoUri.toString()); + // FIXME make it configurable + params.put(JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, "sys"); + + try { + repository = repositoryFactory.getRepository(params); + if (repository != null) + session = repository.login(workspace); + else + throw new IllegalArgumentException("Repository " + repoUri + " not found"); + } catch (RepositoryException e) { + e.printStackTrace(); + } + + } else { + Path path = Paths.get(uri.getPath()); + } + + try { + Node rootNode = session.getRootNode(); + NodeIterator nit = rootNode.getNodes(); + while (nit.hasNext()) { + System.out.println(nit.nextNode().getPath()); + } + + Node newNode = JcrUtils.mkdirs(rootNode, "dir/subdir"); + System.out.println("Created folder " + newNode.getPath()); + Node newFile = JcrUtils.copyBytesAsFile(newNode, "test.txt", "TEST".getBytes()); + System.out.println("Created file " + newFile.getPath()); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(JcrUtils.getFileAsStream(newFile)))) { + System.out.println("Read " + reader.readLine()); + } catch (IOException e) { + e.printStackTrace(); + } + newNode.getParent().remove(); + System.out.println("Removed new nodes"); + } catch (RepositoryException e) { + e.printStackTrace(); + } + } +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/NonSerialBasicAuthCache.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/NonSerialBasicAuthCache.java new file mode 100644 index 000000000..3fb0db9a0 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/NonSerialBasicAuthCache.java @@ -0,0 +1,41 @@ +package org.argeo.jackrabbit.client; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.http.HttpHost; +import org.apache.http.auth.AuthScheme; +import org.apache.http.client.AuthCache; + +/** + * Implementation of {@link AuthCache} which doesn't use serialization, as it is + * not supported by GraalVM at this stage. + */ +public class NonSerialBasicAuthCache implements AuthCache { + private final Map cache; + + public NonSerialBasicAuthCache() { + cache = new ConcurrentHashMap(); + } + + @Override + public void put(HttpHost host, AuthScheme authScheme) { + cache.put(host, authScheme); + } + + @Override + public AuthScheme get(HttpHost host) { + return cache.get(host); + } + + @Override + public void remove(HttpHost host) { + cache.remove(host); + } + + @Override + public void clear() { + cache.clear(); + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/AbstractJackrabbitFsProvider.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/AbstractJackrabbitFsProvider.java new file mode 100644 index 000000000..a2eb98302 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/AbstractJackrabbitFsProvider.java @@ -0,0 +1,7 @@ +package org.argeo.jackrabbit.fs; + +import org.argeo.jcr.fs.JcrFileSystemProvider; + +public abstract class AbstractJackrabbitFsProvider extends JcrFileSystemProvider { + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/DavexFsProvider.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/DavexFsProvider.java new file mode 100644 index 000000000..1cae6e493 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/DavexFsProvider.java @@ -0,0 +1,149 @@ +package org.argeo.jackrabbit.fs; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.DirectoryStream; +import java.nio.file.FileSystem; +import java.nio.file.FileSystemAlreadyExistsException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; + +import javax.jcr.Repository; +import javax.jcr.RepositoryFactory; +import javax.jcr.Session; + +import org.argeo.jackrabbit.client.ClientDavexRepositoryFactory; +import org.argeo.jcr.fs.JcrFileSystem; +import org.argeo.jcr.fs.JcrFsException; + +/** + * A file system provider based on a JCR repository remotely accessed via the + * DAVEX protocol. + */ +public class DavexFsProvider extends AbstractJackrabbitFsProvider { + final static String DEFAULT_JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = "sys"; + + private Map fileSystems = new HashMap<>(); + + @Override + public String getScheme() { + return "davex"; + } + + @Override + public FileSystem newFileSystem(URI uri, Map env) throws IOException { + if (uri.getHost() == null) + throw new IllegalArgumentException("An host should be provided"); + try { + URI repoUri = new URI("http", uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath(), null, null); + String repoKey = repoUri.toString(); + if (fileSystems.containsKey(repoKey)) + throw new FileSystemAlreadyExistsException("CMS file system already exists for " + repoKey); + RepositoryFactory repositoryFactory = new ClientDavexRepositoryFactory(); + return tryGetRepo(repositoryFactory, repoUri, "home"); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Cannot open file system " + uri, e); + } + } + + private JcrFileSystem tryGetRepo(RepositoryFactory repositoryFactory, URI repoUri, String workspace) + throws IOException { + Map params = new HashMap(); + params.put(ClientDavexRepositoryFactory.JACKRABBIT_DAVEX_URI, repoUri.toString()); + // TODO better integrate with OSGi or other configuration than system + // properties. + String remoteDefaultWorkspace = System.getProperty( + ClientDavexRepositoryFactory.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, + DEFAULT_JACKRABBIT_REMOTE_DEFAULT_WORKSPACE); + params.put(ClientDavexRepositoryFactory.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, remoteDefaultWorkspace); + Repository repository = null; + Session session = null; + try { + repository = repositoryFactory.getRepository(params); + if (repository != null) + session = repository.login(workspace); + } catch (Exception e) { + // silent + } + + if (session == null) { + if (repoUri.getPath() == null || repoUri.getPath().equals("/")) + return null; + String repoUriStr = repoUri.toString(); + if (repoUriStr.endsWith("/")) + repoUriStr = repoUriStr.substring(0, repoUriStr.length() - 1); + String nextRepoUriStr = repoUriStr.substring(0, repoUriStr.lastIndexOf('/')); + String nextWorkspace = repoUriStr.substring(repoUriStr.lastIndexOf('/') + 1); + URI nextUri; + try { + nextUri = new URI(nextRepoUriStr); + } catch (URISyntaxException e) { + throw new IllegalArgumentException("Badly formatted URI", e); + } + return tryGetRepo(repositoryFactory, nextUri, nextWorkspace); + } else { + JcrFileSystem fileSystem = new JcrFileSystem(this, repository); + fileSystems.put(repoUri.toString() + "/" + workspace, fileSystem); + return fileSystem; + } + } + + @Override + public FileSystem getFileSystem(URI uri) { + return currentUserFileSystem(uri); + } + + @Override + public Path getPath(URI uri) { + JcrFileSystem fileSystem = currentUserFileSystem(uri); + if (fileSystem == null) + try { + fileSystem = (JcrFileSystem) newFileSystem(uri, new HashMap()); + if (fileSystem == null) + throw new IllegalArgumentException("No file system found for " + uri); + } catch (IOException e) { + throw new JcrFsException("Could not autocreate file system", e); + } + URI repoUri = null; + try { + repoUri = new URI("http", uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath(), null, null); + } catch (URISyntaxException e) { + throw new IllegalArgumentException(e); + } + String uriStr = repoUri.toString(); + String localPath = null; + for (String key : fileSystems.keySet()) { + if (uriStr.startsWith(key)) { + localPath = uriStr.toString().substring(key.length()); + } + } + if ("".equals(localPath)) + localPath = "/"; + return fileSystem.getPath(localPath); + } + + private JcrFileSystem currentUserFileSystem(URI uri) { + for (String key : fileSystems.keySet()) { + if (uri.toString().startsWith(key)) + return fileSystems.get(key); + } + return null; + } + + public static void main(String args[]) { + try { + DavexFsProvider fsProvider = new DavexFsProvider(); + Path path = fsProvider.getPath(new URI("davex://root:demo@localhost:7070/jcr/ego/")); + System.out.println(path); + DirectoryStream ds = Files.newDirectoryStream(path); + for (Path p : ds) { + System.out.println("- " + p); + } + } catch (Exception e) { + e.printStackTrace(); + } + } +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/JackrabbitMemoryFsProvider.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/JackrabbitMemoryFsProvider.java new file mode 100644 index 000000000..e3a70d084 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/JackrabbitMemoryFsProvider.java @@ -0,0 +1,87 @@ +package org.argeo.jackrabbit.fs; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.Map; + +import javax.jcr.Credentials; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.SimpleCredentials; + +import org.apache.jackrabbit.core.RepositoryImpl; +import org.apache.jackrabbit.core.config.RepositoryConfig; +import org.argeo.jcr.fs.JcrFileSystem; +import org.argeo.jcr.fs.JcrFsException; + +public class JackrabbitMemoryFsProvider extends AbstractJackrabbitFsProvider { + private RepositoryImpl repository; + private JcrFileSystem fileSystem; + + private Credentials credentials; + + public JackrabbitMemoryFsProvider() { + String username = System.getProperty("user.name"); + credentials = new SimpleCredentials(username, username.toCharArray()); + } + + @Override + public String getScheme() { + return "jcr+memory"; + } + + @Override + public FileSystem newFileSystem(URI uri, Map env) throws IOException { + try { + Path tempDir = Files.createTempDirectory("fs-memory"); + URL confUrl = JackrabbitMemoryFsProvider.class.getResource("fs-memory.xml"); + RepositoryConfig repositoryConfig = RepositoryConfig.create(confUrl.toURI(), tempDir.toString()); + repository = RepositoryImpl.create(repositoryConfig); + postRepositoryCreation(repository); + fileSystem = new JcrFileSystem(this, repository, credentials); + return fileSystem; + } catch (RepositoryException | URISyntaxException e) { + throw new IOException("Cannot login to repository", e); + } + } + + @Override + public FileSystem getFileSystem(URI uri) { + return fileSystem; + } + + @Override + public Path getPath(URI uri) { + String path = uri.getPath(); + if (fileSystem == null) + try { + newFileSystem(uri, new HashMap()); + } catch (IOException e) { + throw new JcrFsException("Could not autocreate file system", e); + } + return fileSystem.getPath(path); + } + + public Repository getRepository() { + return repository; + } + + public Session login() throws RepositoryException { + return getRepository().login(credentials); + } + + /** + * Called after the repository has been created and before the file system is + * created. + */ + protected void postRepositoryCreation(RepositoryImpl repositoryImpl) throws RepositoryException { + + } +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/fs-memory.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/fs-memory.xml new file mode 100644 index 000000000..f2541fb4e --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/fs-memory.xml @@ -0,0 +1,57 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/package-info.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/package-info.java new file mode 100644 index 000000000..c9ec2c3b9 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/package-info.java @@ -0,0 +1,2 @@ +/** Java NIO file system implementation based on Jackrabbit. */ +package org.argeo.jackrabbit.fs; \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/package-info.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/package-info.java new file mode 100644 index 000000000..17497d62c --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/package-info.java @@ -0,0 +1,2 @@ +/** Generic Jackrabbit utilities. */ +package org.argeo.jackrabbit; \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-h2.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-h2.xml new file mode 100644 index 000000000..05267621f --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-h2.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-localfs.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-localfs.xml new file mode 100644 index 000000000..3d2470863 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-localfs.xml @@ -0,0 +1,60 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-memory.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-memory.xml new file mode 100644 index 000000000..ecee5bdad --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-memory.xml @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-postgresql-ds.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-postgresql-ds.xml new file mode 100644 index 000000000..07a0d0428 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-postgresql-ds.xml @@ -0,0 +1,82 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-postgresql.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-postgresql.xml new file mode 100644 index 000000000..967782820 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-postgresql.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/security/JackrabbitSecurityUtils.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/security/JackrabbitSecurityUtils.java new file mode 100644 index 000000000..f98cf9947 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/security/JackrabbitSecurityUtils.java @@ -0,0 +1,79 @@ +package org.argeo.jackrabbit.security; + +import java.security.Principal; +import java.util.ArrayList; +import java.util.List; + +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.security.Privilege; + +import org.apache.jackrabbit.api.security.JackrabbitAccessControlList; +import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager; +import org.argeo.api.cms.CmsLog; +import org.argeo.jcr.JcrUtils; + +/** Utilities around Jackrabbit security extensions. */ +public class JackrabbitSecurityUtils { + private final static CmsLog log = CmsLog.getLog(JackrabbitSecurityUtils.class); + + /** + * Convenience method for denying a single privilege to a principal (user or + * role), typically jcr:all + */ + public synchronized static void denyPrivilege(Session session, String path, String principal, String privilege) + throws RepositoryException { + List privileges = new ArrayList(); + privileges.add(session.getAccessControlManager().privilegeFromName(privilege)); + denyPrivileges(session, path, () -> principal, privileges); + } + + /** + * Deny privileges on a path to a {@link Principal}. The path must already + * exist. Session is saved. Synchronized to prevent concurrent modifications of + * the same node. + */ + public synchronized static Boolean denyPrivileges(Session session, String path, Principal principal, + List privs) throws RepositoryException { + // make sure the session is in line with the persisted state + session.refresh(false); + JackrabbitAccessControlManager acm = (JackrabbitAccessControlManager) session.getAccessControlManager(); + JackrabbitAccessControlList acl = (JackrabbitAccessControlList) JcrUtils.getAccessControlList(acm, path); + +// accessControlEntries: for (AccessControlEntry ace : acl.getAccessControlEntries()) { +// Principal currentPrincipal = ace.getPrincipal(); +// if (currentPrincipal.getName().equals(principal.getName())) { +// Privilege[] currentPrivileges = ace.getPrivileges(); +// if (currentPrivileges.length != privs.size()) +// break accessControlEntries; +// for (int i = 0; i < currentPrivileges.length; i++) { +// Privilege currP = currentPrivileges[i]; +// Privilege p = privs.get(i); +// if (!currP.getName().equals(p.getName())) { +// break accessControlEntries; +// } +// } +// return false; +// } +// } + + Privilege[] privileges = privs.toArray(new Privilege[privs.size()]); + acl.addEntry(principal, privileges, false); + acm.setPolicy(path, acl); + if (log.isDebugEnabled()) { + StringBuffer privBuf = new StringBuffer(); + for (Privilege priv : privs) + privBuf.append(priv.getName()); + log.debug("Denied privileges " + privBuf + " to " + principal.getName() + " on " + path + " in '" + + session.getWorkspace().getName() + "'"); + } + session.refresh(true); + session.save(); + return true; + } + + /** Singleton. */ + private JackrabbitSecurityUtils() { + + } +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/security/package-info.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/security/package-info.java new file mode 100644 index 000000000..f3a282c4e --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/security/package-info.java @@ -0,0 +1,2 @@ +/** Generic Jackrabbit security utilities. */ +package org.argeo.jackrabbit.security; \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/AbstractJackrabbitTestCase.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/AbstractJackrabbitTestCase.java new file mode 100644 index 000000000..f65432eb7 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/AbstractJackrabbitTestCase.java @@ -0,0 +1,51 @@ +package org.argeo.jackrabbit.unit; + +import java.net.URL; + +import javax.jcr.Repository; + +import org.apache.commons.io.FileUtils; +import org.apache.jackrabbit.core.RepositoryImpl; +import org.apache.jackrabbit.core.config.RepositoryConfig; +import org.argeo.jcr.unit.AbstractJcrTestCase; + +/** Factorizes configuration of an in memory transient repository */ +public abstract class AbstractJackrabbitTestCase extends AbstractJcrTestCase { + protected RepositoryImpl repositoryImpl; + + // protected File getRepositoryFile() throws Exception { + // Resource res = new ClassPathResource( + // "org/argeo/jackrabbit/unit/repository-memory.xml"); + // return res.getFile(); + // } + + public AbstractJackrabbitTestCase() { + URL url = AbstractJackrabbitTestCase.class.getResource("jaas.config"); + assert url != null; + System.setProperty("java.security.auth.login.config", url.toString()); + } + + protected Repository createRepository() throws Exception { + // Repository repository = new TransientRepository(getRepositoryFile(), + // getHomeDir()); + RepositoryConfig repositoryConfig = RepositoryConfig.create( + AbstractJackrabbitTestCase.class + .getResourceAsStream(getRepositoryConfigResource()), + getHomeDir().getAbsolutePath()); + RepositoryImpl repositoryImpl = RepositoryImpl.create(repositoryConfig); + return repositoryImpl; + } + + protected String getRepositoryConfigResource() { + return "repository-memory.xml"; + } + + @Override + protected void clearRepository(Repository repository) throws Exception { + RepositoryImpl repositoryImpl = (RepositoryImpl) repository; + if (repositoryImpl != null) + repositoryImpl.shutdown(); + FileUtils.deleteDirectory(getHomeDir()); + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/jaas.config b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/jaas.config new file mode 100644 index 000000000..0313f91e5 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/jaas.config @@ -0,0 +1,7 @@ +TEST_JACKRABBIT_ADMIN { + org.argeo.cms.auth.DataAdminLoginModule requisite; +}; + +Jackrabbit { + org.argeo.security.jackrabbit.SystemJackrabbitLoginModule requisite; +}; diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/package-info.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/package-info.java new file mode 100644 index 000000000..3b6143b34 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/package-info.java @@ -0,0 +1,2 @@ +/** Helpers for unit tests with Jackrabbit repositories. */ +package org.argeo.jackrabbit.unit; \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/repository-h2.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/repository-h2.xml new file mode 100644 index 000000000..348dc288b --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/repository-h2.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/repository-memory.xml b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/repository-memory.xml new file mode 100644 index 000000000..839542417 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/repository-memory.xml @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/Bin.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/Bin.java new file mode 100644 index 000000000..0418810ed --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/Bin.java @@ -0,0 +1,60 @@ +package org.argeo.jcr; + +import java.io.IOException; +import java.io.InputStream; + +import javax.jcr.Binary; +import javax.jcr.Property; +import javax.jcr.RepositoryException; + +/** + * A {@link Binary} wrapper implementing {@link AutoCloseable} for ease of use + * in try/catch blocks. + */ +public class Bin implements Binary, AutoCloseable { + private final Binary wrappedBinary; + + public Bin(Property property) throws RepositoryException { + this(property.getBinary()); + } + + public Bin(Binary wrappedBinary) { + if (wrappedBinary == null) + throw new IllegalArgumentException("Wrapped binary cannot be null"); + this.wrappedBinary = wrappedBinary; + } + + // private static Binary getBinary(Property property) throws IOException { + // try { + // return property.getBinary(); + // } catch (RepositoryException e) { + // throw new IOException("Cannot get binary from property " + property, e); + // } + // } + + @Override + public void close() { + dispose(); + } + + @Override + public InputStream getStream() throws RepositoryException { + return wrappedBinary.getStream(); + } + + @Override + public int read(byte[] b, long position) throws IOException, RepositoryException { + return wrappedBinary.read(b, position); + } + + @Override + public long getSize() throws RepositoryException { + return wrappedBinary.getSize(); + } + + @Override + public void dispose() { + wrappedBinary.dispose(); + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/CollectionNodeIterator.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/CollectionNodeIterator.java new file mode 100644 index 000000000..b4124eea5 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/CollectionNodeIterator.java @@ -0,0 +1,61 @@ +package org.argeo.jcr; + +import java.util.Collection; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; + +/** Wraps a collection of nodes in order to read it as a {@link NodeIterator} */ +public class CollectionNodeIterator implements NodeIterator { + private final Long collectionSize; + private final Iterator iterator; + private Integer position = 0; + + public CollectionNodeIterator(Collection nodes) { + super(); + this.collectionSize = (long) nodes.size(); + this.iterator = nodes.iterator(); + } + + public void skip(long skipNum) { + if (skipNum < 0) + throw new IllegalArgumentException( + "Skip count has to be positive: " + skipNum); + + for (long i = 0; i < skipNum; i++) { + if (!hasNext()) + throw new NoSuchElementException("Last element past (position=" + + getPosition() + ")"); + nextNode(); + } + } + + public long getSize() { + return collectionSize; + } + + public long getPosition() { + return position; + } + + public boolean hasNext() { + return iterator.hasNext(); + } + + public Object next() { + return nextNode(); + } + + public void remove() { + iterator.remove(); + } + + public Node nextNode() { + Node node = iterator.next(); + position++; + return node; + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/DefaultJcrListener.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/DefaultJcrListener.java new file mode 100644 index 000000000..d873ef652 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/DefaultJcrListener.java @@ -0,0 +1,77 @@ +package org.argeo.jcr; + +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.observation.ObservationManager; + +import org.argeo.api.cms.CmsLog; + +/** To be overridden */ +public class DefaultJcrListener implements EventListener { + private final static CmsLog log = CmsLog.getLog(DefaultJcrListener.class); + private Session session; + private String path = "/"; + private Boolean deep = true; + + public void start() { + try { + addEventListener(session().getWorkspace().getObservationManager()); + if (log.isDebugEnabled()) + log.debug("Registered JCR event listener on " + path); + } catch (RepositoryException e) { + throw new JcrException("Cannot register event listener", e); + } + } + + public void stop() { + try { + session().getWorkspace().getObservationManager() + .removeEventListener(this); + if (log.isDebugEnabled()) + log.debug("Unregistered JCR event listener on " + path); + } catch (RepositoryException e) { + throw new JcrException("Cannot unregister event listener", e); + } + } + + /** Default is listen to all events */ + protected Integer getEvents() { + return Event.NODE_ADDED | Event.NODE_REMOVED | Event.PROPERTY_ADDED + | Event.PROPERTY_CHANGED | Event.PROPERTY_REMOVED; + } + + /** To be overidden */ + public void onEvent(EventIterator events) { + while (events.hasNext()) { + Event event = events.nextEvent(); + log.debug(event); + } + } + + /** To be overidden */ + protected void addEventListener(ObservationManager observationManager) + throws RepositoryException { + observationManager.addEventListener(this, getEvents(), path, deep, + null, null, false); + } + + private Session session() { + return session; + } + + public void setPath(String path) { + this.path = path; + } + + public void setDeep(Boolean deep) { + this.deep = deep; + } + + public void setSession(Session session) { + this.session = session; + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/Jcr.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/Jcr.java new file mode 100644 index 000000000..bf5de1260 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/Jcr.java @@ -0,0 +1,985 @@ +package org.argeo.jcr; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; +import java.text.MessageFormat; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Iterator; +import java.util.List; + +import javax.jcr.Binary; +import javax.jcr.ItemNotFoundException; +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.Property; +import javax.jcr.PropertyType; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.Value; +import javax.jcr.Workspace; +import javax.jcr.nodetype.NodeType; +import javax.jcr.query.Query; +import javax.jcr.query.QueryManager; +import javax.jcr.security.Privilege; +import javax.jcr.version.Version; +import javax.jcr.version.VersionHistory; +import javax.jcr.version.VersionIterator; +import javax.jcr.version.VersionManager; + +import org.apache.commons.io.IOUtils; + +/** + * Utility class whose purpose is to make using JCR less verbose by + * systematically using unchecked exceptions and returning null + * when something is not found. This is especially useful when writing user + * interfaces (such as with SWT) where listeners and callbacks expect unchecked + * exceptions. Loosely inspired by Java's Files singleton. + */ +public class Jcr { + /** + * The name of a node which will be serialized as XML text, as per section 7.3.1 + * of the JCR 2.0 specifications. + */ + public final static String JCR_XMLTEXT = "jcr:xmltext"; + /** + * The name of a property which will be serialized as XML text, as per section + * 7.3.1 of the JCR 2.0 specifications. + */ + public final static String JCR_XMLCHARACTERS = "jcr:xmlcharacters"; + /** + * jcr:name, when used in another context than + * {@link Property#JCR_NAME}, typically to name a node rather than a property. + */ + public final static String JCR_NAME = "jcr:name"; + /** + * jcr:path, when used in another context than + * {@link Property#JCR_PATH}, typically to name a node rather than a property. + */ + public final static String JCR_PATH = "jcr:path"; + /** + * jcr:primaryType with prefix instead of namespace (as in + * {@link Property#JCR_PRIMARY_TYPE}. + */ + public final static String JCR_PRIMARY_TYPE = "jcr:primaryType"; + /** + * jcr:mixinTypes with prefix instead of namespace (as in + * {@link Property#JCR_MIXIN_TYPES}. + */ + public final static String JCR_MIXIN_TYPES = "jcr:mixinTypes"; + /** + * jcr:uuid with prefix instead of namespace (as in + * {@link Property#JCR_UUID}. + */ + public final static String JCR_UUID = "jcr:uuid"; + /** + * jcr:created with prefix instead of namespace (as in + * {@link Property#JCR_CREATED}. + */ + public final static String JCR_CREATED = "jcr:created"; + /** + * jcr:createdBy with prefix instead of namespace (as in + * {@link Property#JCR_CREATED_BY}. + */ + public final static String JCR_CREATED_BY = "jcr:createdBy"; + /** + * jcr:lastModified with prefix instead of namespace (as in + * {@link Property#JCR_LAST_MODIFIED}. + */ + public final static String JCR_LAST_MODIFIED = "jcr:lastModified"; + /** + * jcr:lastModifiedBy with prefix instead of namespace (as in + * {@link Property#JCR_LAST_MODIFIED_BY}. + */ + public final static String JCR_LAST_MODIFIED_BY = "jcr:lastModifiedBy"; + + /** + * @see Node#isNodeType(String) + * @throws JcrException caused by {@link RepositoryException} + */ + public static boolean isNodeType(Node node, String nodeTypeName) { + try { + return node.isNodeType(nodeTypeName); + } catch (RepositoryException e) { + throw new JcrException("Cannot get whether " + node + " is of type " + nodeTypeName, e); + } + } + + /** + * @see Node#hasNodes() + * @throws JcrException caused by {@link RepositoryException} + */ + public static boolean hasNodes(Node node) { + try { + return node.hasNodes(); + } catch (RepositoryException e) { + throw new JcrException("Cannot get whether " + node + " has children.", e); + } + } + + /** + * @see Node#getParent() + * @throws JcrException caused by {@link RepositoryException} + */ + public static Node getParent(Node node) { + try { + return isRoot(node) ? null : node.getParent(); + } catch (RepositoryException e) { + throw new JcrException("Cannot get parent of " + node, e); + } + } + + /** + * @see Node#getParent() + * @throws JcrException caused by {@link RepositoryException} + */ + public static String getParentPath(Node node) { + return getPath(getParent(node)); + } + + /** + * Whether this node is the root node. + * + * @throws JcrException caused by {@link RepositoryException} + */ + public static boolean isRoot(Node node) { + try { + return node.getDepth() == 0; + } catch (RepositoryException e) { + throw new JcrException("Cannot get depth of " + node, e); + } + } + + /** + * @see Node#getPath() + * @throws JcrException caused by {@link RepositoryException} + */ + public static String getPath(Node node) { + try { + return node.getPath(); + } catch (RepositoryException e) { + throw new JcrException("Cannot get path of " + node, e); + } + } + + /** + * @see Node#getSession() + * @see Session#getWorkspace() + * @see Workspace#getName() + */ + public static String getWorkspaceName(Node node) { + return session(node).getWorkspace().getName(); + } + + /** + * @see Node#getIdentifier() + * @throws JcrException caused by {@link RepositoryException} + */ + public static String getIdentifier(Node node) { + try { + return node.getIdentifier(); + } catch (RepositoryException e) { + throw new JcrException("Cannot get identifier of " + node, e); + } + } + + /** + * @see Node#getName() + * @throws JcrException caused by {@link RepositoryException} + */ + public static String getName(Node node) { + try { + return node.getName(); + } catch (RepositoryException e) { + throw new JcrException("Cannot get name of " + node, e); + } + } + + /** + * Returns the node name with its current index (useful for re-ordering). + * + * @see Node#getName() + * @see Node#getIndex() + * @throws JcrException caused by {@link RepositoryException} + */ + public static String getIndexedName(Node node) { + try { + return node.getName() + "[" + node.getIndex() + "]"; + } catch (RepositoryException e) { + throw new JcrException("Cannot get name of " + node, e); + } + } + + /** + * @see Node#getProperty(String) + * @throws JcrException caused by {@link RepositoryException} + */ + public static Property getProperty(Node node, String property) { + try { + if (node.hasProperty(property)) + return node.getProperty(property); + else + return null; + } catch (RepositoryException e) { + throw new JcrException("Cannot get property " + property + " of " + node, e); + } + } + + /** + * @see Node#getIndex() + * @throws JcrException caused by {@link RepositoryException} + */ + public static int getIndex(Node node) { + try { + return node.getIndex(); + } catch (RepositoryException e) { + throw new JcrException("Cannot get index of " + node, e); + } + } + + /** + * If node has mixin {@link NodeType#MIX_TITLE}, return + * {@link Property#JCR_TITLE}, otherwise return {@link #getName(Node)}. + */ + public static String getTitle(Node node) { + if (Jcr.isNodeType(node, NodeType.MIX_TITLE)) + return get(node, Property.JCR_TITLE); + else + return Jcr.getName(node); + } + + /** Accesses a {@link NodeIterator} as an {@link Iterable}. */ + @SuppressWarnings("unchecked") + public static Iterable iterate(NodeIterator nodeIterator) { + return new Iterable() { + + @Override + public Iterator iterator() { + return nodeIterator; + } + }; + } + + /** + * @return the children as an {@link Iterable} for use in for-each llops. + * @see Node#getNodes() + * @throws JcrException caused by {@link RepositoryException} + */ + public static Iterable nodes(Node node) { + try { + return iterate(node.getNodes()); + } catch (RepositoryException e) { + throw new JcrException("Cannot get children of " + node, e); + } + } + + /** + * @return the children as a (possibly empty) {@link List}. + * @see Node#getNodes() + * @throws JcrException caused by {@link RepositoryException} + */ + public static List getNodes(Node node) { + List nodes = new ArrayList<>(); + try { + if (node.hasNodes()) { + NodeIterator nit = node.getNodes(); + while (nit.hasNext()) + nodes.add(nit.nextNode()); + return nodes; + } else + return nodes; + } catch (RepositoryException e) { + throw new JcrException("Cannot get children of " + node, e); + } + } + + /** + * @return the child or null if not found + * @see Node#getNode(String) + * @throws JcrException caused by {@link RepositoryException} + */ + public static Node getNode(Node node, String child) { + try { + if (node.hasNode(child)) + return node.getNode(child); + else + return null; + } catch (RepositoryException e) { + throw new JcrException("Cannot get child of " + node, e); + } + } + + /** + * @return the node at this path or null if not found + * @see Session#getNode(String) + * @throws JcrException caused by {@link RepositoryException} + */ + public static Node getNode(Session session, String path) { + try { + if (session.nodeExists(path)) + return session.getNode(path); + else + return null; + } catch (RepositoryException e) { + throw new JcrException("Cannot get node " + path, e); + } + } + + /** + * Add a node to this parent, setting its primary type and its mixins. + * + * @param parent the parent node + * @param name the name of the node, if null, the primary + * type will be used (typically for XML structures) + * @param primaryType the primary type, if null + * {@link NodeType#NT_UNSTRUCTURED} will be used. + * @param mixins the mixins + * @return the created node + * @see Node#addNode(String, String) + * @see Node#addMixin(String) + */ + public static Node addNode(Node parent, String name, String primaryType, String... mixins) { + if (name == null && primaryType == null) + throw new IllegalArgumentException("Both node name and primary type cannot be null"); + try { + Node newNode = parent.addNode(name == null ? primaryType : name, + primaryType == null ? NodeType.NT_UNSTRUCTURED : primaryType); + for (String mixin : mixins) { + newNode.addMixin(mixin); + } + return newNode; + } catch (RepositoryException e) { + throw new JcrException("Cannot add node " + name + " to " + parent, e); + } + } + + /** + * Add an {@link NodeType#NT_BASE} node to this parent. + * + * @param parent the parent node + * @param name the name of the node, cannot be null + * @return the created node + * + * @see Node#addNode(String) + */ + public static Node addNode(Node parent, String name) { + if (name == null) + throw new IllegalArgumentException("Node name cannot be null"); + try { + Node newNode = parent.addNode(name); + return newNode; + } catch (RepositoryException e) { + throw new JcrException("Cannot add node " + name + " to " + parent, e); + } + } + + /** + * Add mixins to a node. + * + * @param node the node + * @param mixins the mixins + * @return the created node + * @see Node#addMixin(String) + */ + public static void addMixin(Node node, String... mixins) { + try { + for (String mixin : mixins) { + node.addMixin(mixin); + } + } catch (RepositoryException e) { + throw new JcrException("Cannot add mixins " + Arrays.asList(mixins) + " to " + node, e); + } + } + + /** + * Removes this node. + * + * @see Node#remove() + */ + public static void remove(Node node) { + try { + node.remove(); + } catch (RepositoryException e) { + throw new JcrException("Cannot remove node " + node, e); + } + } + + /** + * @return the node with htis id or null if not found + * @see Session#getNodeByIdentifier(String) + * @throws JcrException caused by {@link RepositoryException} + */ + public static Node getNodeById(Session session, String id) { + try { + return session.getNodeByIdentifier(id); + } catch (ItemNotFoundException e) { + return null; + } catch (RepositoryException e) { + throw new JcrException("Cannot get node with id " + id, e); + } + } + + /** + * Set a property to the given value, or remove it if the value is + * null. + * + * @throws JcrException caused by {@link RepositoryException} + */ + public static void set(Node node, String property, Object value) { + try { + if (!node.hasProperty(property)) { + if (value != null) { + if (value instanceof List) {// multiple + List lst = (List) value; + String[] values = new String[lst.size()]; + for (int i = 0; i < lst.size(); i++) { + values[i] = lst.get(i).toString(); + } + node.setProperty(property, values); + } else { + node.setProperty(property, value.toString()); + } + } + return; + } + Property prop = node.getProperty(property); + if (value == null) { + prop.remove(); + return; + } + + // multiple + if (value instanceof List) { + List lst = (List) value; + String[] values = new String[lst.size()]; + // TODO better cast? + for (int i = 0; i < lst.size(); i++) { + values[i] = lst.get(i).toString(); + } + if (!prop.isMultiple()) + prop.remove(); + node.setProperty(property, values); + return; + } + + // single + if (prop.isMultiple()) { + prop.remove(); + node.setProperty(property, value.toString()); + return; + } + + if (value instanceof String) + prop.setValue((String) value); + else if (value instanceof Long) + prop.setValue((Long) value); + else if (value instanceof Integer) + prop.setValue(((Integer) value).longValue()); + else if (value instanceof Double) + prop.setValue((Double) value); + else if (value instanceof Float) + prop.setValue(((Float) value).doubleValue()); + else if (value instanceof Calendar) + prop.setValue((Calendar) value); + else if (value instanceof BigDecimal) + prop.setValue((BigDecimal) value); + else if (value instanceof Boolean) + prop.setValue((Boolean) value); + else if (value instanceof byte[]) + JcrUtils.setBinaryAsBytes(prop, (byte[]) value); + else if (value instanceof Instant) { + Instant instant = (Instant) value; + GregorianCalendar calendar = new GregorianCalendar(); + calendar.setTime(Date.from(instant)); + prop.setValue(calendar); + } else // try with toString() + prop.setValue(value.toString()); + } catch (RepositoryException e) { + throw new JcrException("Cannot set property " + property + " of " + node + " to " + value, e); + } + } + + /** + * Get property as {@link String}. + * + * @return the value of + * {@link Node#getProperty(String)}.{@link Property#getString()} or + * null if the property does not exist. + * @throws JcrException caused by {@link RepositoryException} + */ + public static String get(Node node, String property) { + return get(node, property, null); + } + + /** + * Get property as a {@link String}. If the property is multiple it returns the + * first value. + * + * @return the value of + * {@link Node#getProperty(String)}.{@link Property#getString()} or + * defaultValue if the property does not exist. + * @throws JcrException caused by {@link RepositoryException} + */ + public static String get(Node node, String property, String defaultValue) { + try { + if (node.hasProperty(property)) { + Property p = node.getProperty(property); + if (!p.isMultiple()) + return p.getString(); + else { + Value[] values = p.getValues(); + if (values.length == 0) + return defaultValue; + else + return values[0].getString(); + } + } else + return defaultValue; + } catch (RepositoryException e) { + throw new JcrException("Cannot retrieve property " + property + " from " + node, e); + } + } + + /** + * Get property as a {@link Value}. + * + * @return {@link Node#getProperty(String)} or null if the property + * does not exist. + * @throws JcrException caused by {@link RepositoryException} + */ + public static Value getValue(Node node, String property) { + try { + if (node.hasProperty(property)) + return node.getProperty(property).getValue(); + else + return null; + } catch (RepositoryException e) { + throw new JcrException("Cannot retrieve property " + property + " from " + node, e); + } + } + + /** + * Get property doing a best effort to cast it as the target object. + * + * @return the value of {@link Node#getProperty(String)} or + * defaultValue if the property does not exist. + * @throws IllegalArgumentException if the value could not be cast + * @throws JcrException in case of unexpected + * {@link RepositoryException} + */ + @SuppressWarnings("unchecked") + public static T getAs(Node node, String property, T defaultValue) { + try { + // TODO deal with multiple + if (node.hasProperty(property)) { + Property p = node.getProperty(property); + try { + if (p.isMultiple()) { + throw new UnsupportedOperationException("Multiple values properties are not supported"); + } + Value value = p.getValue(); + return (T) get(value); + } catch (ClassCastException e) { + throw new IllegalArgumentException( + "Cannot cast property of type " + PropertyType.nameFromValue(p.getType()), e); + } + } else { + return defaultValue; + } + } catch (RepositoryException e) { + throw new JcrException("Cannot retrieve property " + property + " from " + node, e); + } + } + + public static T getAs(Node node, String property, Class clss) { + if(String.class.isAssignableFrom(clss)) { + return (T)get(node,property); + } else if(Long.class.isAssignableFrom(clss)) { + return (T)get(node,property); + }else { + throw new IllegalArgumentException("Unsupported format "+clss); + } + } + + /** + * Get a multiple property as a list, doing a best effort to cast it as the + * target list. + * + * @return the value of {@link Node#getProperty(String)}. + * @throws IllegalArgumentException if the value could not be cast + * @throws JcrException in case of unexpected + * {@link RepositoryException} + */ + public static List getMultiple(Node node, String property) { + try { + if (node.hasProperty(property)) { + Property p = node.getProperty(property); + return getMultiple(p); + } else { + return null; + } + } catch (RepositoryException e) { + throw new JcrException("Cannot retrieve multiple values property " + property + " from " + node, e); + } + } + + /** + * Get a multiple property as a list, doing a best effort to cast it as the + * target list. + */ + @SuppressWarnings("unchecked") + public static List getMultiple(Property p) { + try { + List res = new ArrayList<>(); + if (!p.isMultiple()) { + res.add((T) get(p.getValue())); + return res; + } + Value[] values = p.getValues(); + for (Value value : values) { + res.add((T) get(value)); + } + return res; + } catch (ClassCastException | RepositoryException e) { + throw new IllegalArgumentException("Cannot get property " + p, e); + } + } + + /** Cast a {@link Value} to a standard Java object. */ + public static Object get(Value value) { + Binary binary = null; + try { + switch (value.getType()) { + case PropertyType.STRING: + return value.getString(); + case PropertyType.DOUBLE: + return (Double) value.getDouble(); + case PropertyType.LONG: + return (Long) value.getLong(); + case PropertyType.BOOLEAN: + return (Boolean) value.getBoolean(); + case PropertyType.DATE: + return value.getDate(); + case PropertyType.BINARY: + binary = value.getBinary(); + byte[] arr = null; + try (InputStream in = binary.getStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();) { + IOUtils.copy(in, out); + arr = out.toByteArray(); + } catch (IOException e) { + throw new RuntimeException("Cannot read binary from " + value, e); + } + return arr; + default: + return value.getString(); + } + } catch (RepositoryException e) { + throw new JcrException("Cannot cast value from " + value, e); + } finally { + if (binary != null) + binary.dispose(); + } + } + + /** + * Retrieves the {@link Session} related to this node. + * + * @deprecated Use {@link #getSession(Node)} instead. + */ + @Deprecated + public static Session session(Node node) { + return getSession(node); + } + + /** Retrieves the {@link Session} related to this node. */ + public static Session getSession(Node node) { + try { + return node.getSession(); + } catch (RepositoryException e) { + throw new JcrException("Cannot retrieve session related to " + node, e); + } + } + + /** Retrieves the root node related to this session. */ + public static Node getRootNode(Session session) { + try { + return session.getRootNode(); + } catch (RepositoryException e) { + throw new JcrException("Cannot get root node for " + session, e); + } + } + + /** Whether this item exists. */ + public static boolean itemExists(Session session, String path) { + try { + return session.itemExists(path); + } catch (RepositoryException e) { + throw new JcrException("Cannot check whether " + path + " exists", e); + } + } + + /** + * Saves the {@link Session} related to this node. Note that all other unrelated + * modifications in this session will also be saved. + */ + public static void save(Node node) { + try { + Session session = node.getSession(); +// if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) { +// set(node, Property.JCR_LAST_MODIFIED, Instant.now()); +// set(node, Property.JCR_LAST_MODIFIED_BY, session.getUserID()); +// } + if (session.hasPendingChanges()) + session.save(); + } catch (RepositoryException e) { + throw new JcrException("Cannot save session related to " + node + " in workspace " + + session(node).getWorkspace().getName(), e); + } + } + + /** Login to a JCR repository. */ + public static Session login(Repository repository, String workspace) { + try { + return repository.login(workspace); + } catch (RepositoryException e) { + throw new IllegalArgumentException("Cannot login to repository", e); + } + } + + /** Safely and silently logs out a session. */ + public static void logout(Session session) { + try { + if (session != null) + if (session.isLive()) + session.logout(); + } catch (Exception e) { + // silent + } + } + + /** Safely and silently logs out the underlying session. */ + public static void logout(Node node) { + Jcr.logout(session(node)); + } + + /* + * SECURITY + */ + /** + * Add a single privilege to a node. + * + * @see Privilege + */ + public static void addPrivilege(Node node, String principal, String privilege) { + try { + Session session = node.getSession(); + JcrUtils.addPrivilege(session, node.getPath(), principal, privilege); + } catch (RepositoryException e) { + throw new JcrException("Cannot add privilege " + privilege + " to " + node, e); + } + } + + /* + * VERSIONING + */ + /** Get checked out status. */ + public static boolean isCheckedOut(Node node) { + try { + return node.isCheckedOut(); + } catch (RepositoryException e) { + throw new JcrException("Cannot retrieve checked out status of " + node, e); + } + } + + /** @see VersionManager#checkpoint(String) */ + public static void checkpoint(Node node) { + try { + versionManager(node).checkpoint(node.getPath()); + } catch (RepositoryException e) { + throw new JcrException("Cannot check in " + node, e); + } + } + + /** @see VersionManager#checkin(String) */ + public static void checkin(Node node) { + try { + versionManager(node).checkin(node.getPath()); + } catch (RepositoryException e) { + throw new JcrException("Cannot check in " + node, e); + } + } + + /** @see VersionManager#checkout(String) */ + public static void checkout(Node node) { + try { + versionManager(node).checkout(node.getPath()); + } catch (RepositoryException e) { + throw new JcrException("Cannot check out " + node, e); + } + } + + /** Get the {@link VersionManager} related to this node. */ + public static VersionManager versionManager(Node node) { + try { + return node.getSession().getWorkspace().getVersionManager(); + } catch (RepositoryException e) { + throw new JcrException("Cannot get version manager from " + node, e); + } + } + + /** Get the {@link VersionHistory} related to this node. */ + public static VersionHistory getVersionHistory(Node node) { + try { + return versionManager(node).getVersionHistory(node.getPath()); + } catch (RepositoryException e) { + throw new JcrException("Cannot get version history from " + node, e); + } + } + + /** + * The linear versions of this version history in reverse order and without the + * root version. + */ + public static List getLinearVersions(VersionHistory versionHistory) { + try { + List lst = new ArrayList<>(); + VersionIterator vit = versionHistory.getAllLinearVersions(); + while (vit.hasNext()) + lst.add(vit.nextVersion()); + lst.remove(0); + Collections.reverse(lst); + return lst; + } catch (RepositoryException e) { + throw new JcrException("Cannot get linear versions from " + versionHistory, e); + } + } + + /** The frozen node related to this {@link Version}. */ + public static Node getFrozenNode(Version version) { + try { + return version.getFrozenNode(); + } catch (RepositoryException e) { + throw new JcrException("Cannot get frozen node from " + version, e); + } + } + + /** Get the base {@link Version} related to this node. */ + public static Version getBaseVersion(Node node) { + try { + return versionManager(node).getBaseVersion(node.getPath()); + } catch (RepositoryException e) { + throw new JcrException("Cannot get base version from " + node, e); + } + } + + /* + * FILES + */ + /** + * Returns the size of this file. + * + * @see NodeType#NT_FILE + */ + public static long getFileSize(Node fileNode) { + try { + if (!fileNode.isNodeType(NodeType.NT_FILE)) + throw new IllegalArgumentException(fileNode + " must be a file."); + return getBinarySize(fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary()); + } catch (RepositoryException e) { + throw new JcrException("Cannot get file size of " + fileNode, e); + } + } + + /** Returns the size of this {@link Binary}. */ + public static long getBinarySize(Binary binaryArg) { + try { + try (Bin binary = new Bin(binaryArg)) { + return binary.getSize(); + } + } catch (RepositoryException e) { + throw new JcrException("Cannot get file size of binary " + binaryArg, e); + } + } + + // QUERY + /** Creates a JCR-SQL2 query using {@link MessageFormat}. */ + public static Query createQuery(QueryManager qm, String sql, Object... args) { + // fix single quotes + sql = sql.replaceAll("'", "''"); + String query = MessageFormat.format(sql, args); + try { + return qm.createQuery(query, Query.JCR_SQL2); + } catch (RepositoryException e) { + throw new JcrException("Cannot create JCR-SQL2 query from " + query, e); + } + } + + /** Executes a JCR-SQL2 query using {@link MessageFormat}. */ + public static NodeIterator executeQuery(QueryManager qm, String sql, Object... args) { + Query query = createQuery(qm, sql, args); + try { + return query.execute().getNodes(); + } catch (RepositoryException e) { + throw new JcrException("Cannot execute query " + sql + " with arguments " + Arrays.asList(args), e); + } + } + + /** Executes a JCR-SQL2 query using {@link MessageFormat}. */ + public static NodeIterator executeQuery(Session session, String sql, Object... args) { + QueryManager queryManager; + try { + queryManager = session.getWorkspace().getQueryManager(); + } catch (RepositoryException e) { + throw new JcrException("Cannot get query manager from session " + session, e); + } + return executeQuery(queryManager, sql, args); + } + + /** + * Executes a JCR-SQL2 query using {@link MessageFormat}, which must return a + * single node at most. + * + * @return the node or null if not found. + */ + public static Node getNode(QueryManager qm, String sql, Object... args) { + NodeIterator nit = executeQuery(qm, sql, args); + if (nit.hasNext()) { + Node node = nit.nextNode(); + if (nit.hasNext()) + throw new IllegalStateException( + "Query " + sql + " with arguments " + Arrays.asList(args) + " returned more than one node."); + return node; + } else { + return null; + } + } + + /** + * Executes a JCR-SQL2 query using {@link MessageFormat}, which must return a + * single node at most. + * + * @return the node or null if not found. + */ + public static Node getNode(Session session, String sql, Object... args) { + QueryManager queryManager; + try { + queryManager = session.getWorkspace().getQueryManager(); + } catch (RepositoryException e) { + throw new JcrException("Cannot get query manager from session " + session, e); + } + return getNode(queryManager, sql, args); + } + + /** Singleton. */ + private Jcr() { + + } +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrAuthorizations.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrAuthorizations.java new file mode 100644 index 000000000..351929f8d --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrAuthorizations.java @@ -0,0 +1,207 @@ +package org.argeo.jcr; + +import java.security.Principal; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.security.AccessControlManager; +import javax.jcr.security.Privilege; +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; + +/** Apply authorizations to a JCR repository. */ +public class JcrAuthorizations implements Runnable { + // private final static Log log = + // LogFactory.getLog(JcrAuthorizations.class); + + private Repository repository; + private String workspace = null; + + private String securityWorkspace = "security"; + + /** + * key := privilege1,privilege2/path/to/node
+ * value := group1,group2,user1 + */ + private Map principalPrivileges = new HashMap(); + + public void run() { + String currentWorkspace = workspace; + Session session = null; + try { + if (workspace != null && workspace.equals("*")) { + session = repository.login(); + String[] workspaces = session.getWorkspace().getAccessibleWorkspaceNames(); + JcrUtils.logoutQuietly(session); + for (String wksp : workspaces) { + currentWorkspace = wksp; + if (currentWorkspace.equals(securityWorkspace)) + continue; + session = repository.login(currentWorkspace); + initAuthorizations(session); + JcrUtils.logoutQuietly(session); + } + } else { + session = repository.login(workspace); + initAuthorizations(session); + } + } catch (RepositoryException e) { + JcrUtils.discardQuietly(session); + throw new JcrException( + "Cannot set authorizations " + principalPrivileges + " on workspace " + currentWorkspace, e); + } finally { + JcrUtils.logoutQuietly(session); + } + } + + protected void processWorkspace(String workspace) { + Session session = null; + try { + session = repository.login(workspace); + initAuthorizations(session); + } catch (RepositoryException e) { + JcrUtils.discardQuietly(session); + throw new JcrException( + "Cannot set authorizations " + principalPrivileges + " on repository " + repository, e); + } finally { + JcrUtils.logoutQuietly(session); + } + } + + /** @deprecated call {@link #run()} instead. */ + @Deprecated + public void init() { + run(); + } + + protected void initAuthorizations(Session session) throws RepositoryException { + AccessControlManager acm = session.getAccessControlManager(); + + for (String privileges : principalPrivileges.keySet()) { + String path = null; + int slashIndex = privileges.indexOf('/'); + if (slashIndex == 0) { + throw new IllegalArgumentException("Privilege " + privileges + " badly formatted it starts with /"); + } else if (slashIndex > 0) { + path = privileges.substring(slashIndex); + privileges = privileges.substring(0, slashIndex); + } + + if (path == null) + path = "/"; + + List privs = new ArrayList(); + for (String priv : privileges.split(",")) { + privs.add(acm.privilegeFromName(priv)); + } + + String principalNames = principalPrivileges.get(privileges); + try { + new LdapName(principalNames); + // TODO differentiate groups and users ? + Principal principal = getOrCreatePrincipal(session, principalNames); + JcrUtils.addPrivileges(session, path, principal, privs); + } catch (InvalidNameException e) { + for (String principalName : principalNames.split(",")) { + Principal principal = getOrCreatePrincipal(session, principalName); + JcrUtils.addPrivileges(session, path, principal, privs); + // if (log.isDebugEnabled()) { + // StringBuffer privBuf = new StringBuffer(); + // for (Privilege priv : privs) + // privBuf.append(priv.getName()); + // log.debug("Added privileges " + privBuf + " to " + // + principal.getName() + " on " + path + " in '" + // + session.getWorkspace().getName() + "'"); + // } + } + } + } + + // if (log.isDebugEnabled()) + // log.debug("JCR authorizations applied on '" + // + session.getWorkspace().getName() + "'"); + } + + /** + * Returns a {@link SimplePrincipal}, does not check whether it exists since + * such capabilities is not provided by the standard JCR API. Can be + * overridden to provide smarter handling + */ + protected Principal getOrCreatePrincipal(Session session, String principalName) throws RepositoryException { + return new SimplePrincipal(principalName); + } + + // public static void addPrivileges(Session session, Principal principal, + // String path, List privs) throws RepositoryException { + // AccessControlManager acm = session.getAccessControlManager(); + // // search for an access control list + // AccessControlList acl = null; + // AccessControlPolicyIterator policyIterator = acm + // .getApplicablePolicies(path); + // if (policyIterator.hasNext()) { + // while (policyIterator.hasNext()) { + // AccessControlPolicy acp = policyIterator + // .nextAccessControlPolicy(); + // if (acp instanceof AccessControlList) + // acl = ((AccessControlList) acp); + // } + // } else { + // AccessControlPolicy[] existingPolicies = acm.getPolicies(path); + // for (AccessControlPolicy acp : existingPolicies) { + // if (acp instanceof AccessControlList) + // acl = ((AccessControlList) acp); + // } + // } + // + // if (acl != null) { + // acl.addAccessControlEntry(principal, + // privs.toArray(new Privilege[privs.size()])); + // acm.setPolicy(path, acl); + // session.save(); + // if (log.isDebugEnabled()) { + // StringBuffer buf = new StringBuffer(""); + // for (int i = 0; i < privs.size(); i++) { + // if (i != 0) + // buf.append(','); + // buf.append(privs.get(i).getName()); + // } + // log.debug("Added privilege(s) '" + buf + "' to '" + // + principal.getName() + "' on " + path + // + " from workspace '" + // + session.getWorkspace().getName() + "'"); + // } + // } else { + // throw new ArgeoJcrException("Don't know how to apply privileges " + // + privs + " to " + principal + " on " + path + // + " from workspace '" + session.getWorkspace().getName() + // + "'"); + // } + // } + + @Deprecated + public void setGroupPrivileges(Map groupPrivileges) { + this.principalPrivileges = groupPrivileges; + } + + public void setPrincipalPrivileges(Map principalPrivileges) { + this.principalPrivileges = principalPrivileges; + } + + public void setRepository(Repository repository) { + this.repository = repository; + } + + public void setWorkspace(String workspace) { + this.workspace = workspace; + } + + public void setSecurityWorkspace(String securityWorkspace) { + this.securityWorkspace = securityWorkspace; + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrCallback.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrCallback.java new file mode 100644 index 000000000..efbaabe82 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrCallback.java @@ -0,0 +1,15 @@ +package org.argeo.jcr; + +import java.util.function.Function; + +import javax.jcr.Session; + +/** An arbitrary execution on a JCR session, optionally returning a result. */ +@FunctionalInterface +public interface JcrCallback extends Function { + /** @deprecated Use {@link #apply(Session)} instead. */ + @Deprecated + public default Object execute(Session session) { + return apply(session); + } +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrException.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrException.java new file mode 100644 index 000000000..c77874376 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrException.java @@ -0,0 +1,22 @@ +package org.argeo.jcr; + +import javax.jcr.RepositoryException; + +/** + * Wraps a {@link RepositoryException} in a {@link RuntimeException}. + */ +public class JcrException extends IllegalStateException { + private static final long serialVersionUID = -4530350094877964989L; + + public JcrException(String message, RepositoryException e) { + super(message, e); + } + + public JcrException(RepositoryException e) { + super(e); + } + + public RepositoryException getRepositoryCause() { + return (RepositoryException) getCause(); + } +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrMonitor.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrMonitor.java new file mode 100644 index 000000000..71cf961e0 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrMonitor.java @@ -0,0 +1,87 @@ +package org.argeo.jcr; + + +/** + * Simple monitor abstraction. Inspired by Eclipse IProgressMOnitor, but without + * dependency to it. + */ +public interface JcrMonitor { + /** + * Constant indicating an unknown amount of work. + */ + public final static int UNKNOWN = -1; + + /** + * Notifies that the main task is beginning. This must only be called once + * on a given progress monitor instance. + * + * @param name + * the name (or description) of the main task + * @param totalWork + * the total number of work units into which the main task is + * been subdivided. If the value is UNKNOWN the + * implementation is free to indicate progress in a way which + * doesn't require the total number of work units in advance. + */ + public void beginTask(String name, int totalWork); + + /** + * Notifies that the work is done; that is, either the main task is + * completed or the user canceled it. This method may be called more than + * once (implementations should be prepared to handle this case). + */ + public void done(); + + /** + * Returns whether cancelation of current operation has been requested. + * Long-running operations should poll to see if cancelation has been + * requested. + * + * @return true if cancellation has been requested, and + * false otherwise + * @see #setCanceled(boolean) + */ + public boolean isCanceled(); + + /** + * Sets the cancel state to the given value. + * + * @param value + * true indicates that cancelation has been + * requested (but not necessarily acknowledged); + * false clears this flag + * @see #isCanceled() + */ + public void setCanceled(boolean value); + + /** + * Sets the task name to the given value. This method is used to restore the + * task label after a nested operation was executed. Normally there is no + * need for clients to call this method. + * + * @param name + * the name (or description) of the main task + * @see #beginTask(java.lang.String, int) + */ + public void setTaskName(String name); + + /** + * Notifies that a subtask of the main task is beginning. Subtasks are + * optional; the main task might not have subtasks. + * + * @param name + * the name (or description) of the subtask + */ + public void subTask(String name); + + /** + * Notifies that a given number of work unit of the main task has been + * completed. Note that this amount represents an installment, as opposed to + * a cumulative amount of work done to date. + * + * @param work + * a non-negative number of work units just completed + */ + public void worked(int work); + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrRepositoryWrapper.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrRepositoryWrapper.java new file mode 100644 index 000000000..3228eee74 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrRepositoryWrapper.java @@ -0,0 +1,244 @@ +package org.argeo.jcr; + +import java.io.InputStream; +import java.math.BigDecimal; +import java.util.Arrays; +import java.util.Calendar; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.jcr.Binary; +import javax.jcr.Credentials; +import javax.jcr.LoginException; +import javax.jcr.NoSuchWorkspaceException; +import javax.jcr.PropertyType; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.Value; +import javax.jcr.ValueFormatException; + +/** + * Wrapper around a JCR repository which allows to simplify configuration and + * intercept some actions. It exposes itself as a {@link Repository}. + */ +public abstract class JcrRepositoryWrapper implements Repository { + // private final static Log log = LogFactory + // .getLog(JcrRepositoryWrapper.class); + + // wrapped repository + private Repository repository; + + private Map additionalDescriptors = new HashMap<>(); + + private Boolean autocreateWorkspaces = false; + + public JcrRepositoryWrapper(Repository repository) { + setRepository(repository); + } + + /** + * Empty constructor + */ + public JcrRepositoryWrapper() { + } + + // /** Initializes */ + // public void init() { + // } + // + // /** Shutdown the repository */ + // public void destroy() throws Exception { + // } + + protected void putDescriptor(String key, String value) { + if (Arrays.asList(getRepository().getDescriptorKeys()).contains(key)) + throw new IllegalArgumentException("Descriptor key " + key + " is already defined in wrapped repository"); + if (value == null) + additionalDescriptors.remove(key); + else + additionalDescriptors.put(key, value); + } + + /* + * DELEGATED JCR REPOSITORY METHODS + */ + + public String getDescriptor(String key) { + if (additionalDescriptors.containsKey(key)) + return additionalDescriptors.get(key); + return getRepository().getDescriptor(key); + } + + public String[] getDescriptorKeys() { + if (additionalDescriptors.size() == 0) + return getRepository().getDescriptorKeys(); + List keys = Arrays.asList(getRepository().getDescriptorKeys()); + keys.addAll(additionalDescriptors.keySet()); + return keys.toArray(new String[keys.size()]); + } + + /** Central login method */ + public Session login(Credentials credentials, String workspaceName) + throws LoginException, NoSuchWorkspaceException, RepositoryException { + Session session; + try { + session = getRepository(workspaceName).login(credentials, workspaceName); + } catch (NoSuchWorkspaceException e) { + if (autocreateWorkspaces && workspaceName != null) + session = createWorkspaceAndLogsIn(credentials, workspaceName); + else + throw e; + } + processNewSession(session, workspaceName); + return session; + } + + public Session login() throws LoginException, RepositoryException { + return login(null, null); + } + + public Session login(Credentials credentials) throws LoginException, RepositoryException { + return login(credentials, null); + } + + public Session login(String workspaceName) throws LoginException, NoSuchWorkspaceException, RepositoryException { + return login(null, workspaceName); + } + + /** Called after a session has been created, does nothing by default. */ + protected void processNewSession(Session session, String workspaceName) { + } + + /** + * Wraps access to the repository, making sure it is available. + * + * @deprecated Use {@link #getDefaultRepository()} instead. + */ + @Deprecated + protected synchronized Repository getRepository() { + return getDefaultRepository(); + } + + protected synchronized Repository getDefaultRepository() { + return repository; + } + + protected synchronized Repository getRepository(String workspaceName) { + return getDefaultRepository(); + } + + /** + * Logs in to the default workspace, creates the required workspace, logs out, + * logs in to the required workspace. + */ + protected Session createWorkspaceAndLogsIn(Credentials credentials, String workspaceName) + throws RepositoryException { + if (workspaceName == null) + throw new IllegalArgumentException("No workspace specified."); + Session session = getRepository(workspaceName).login(credentials); + session.getWorkspace().createWorkspace(workspaceName); + session.logout(); + return getRepository(workspaceName).login(credentials, workspaceName); + } + + public boolean isStandardDescriptor(String key) { + return getRepository().isStandardDescriptor(key); + } + + public boolean isSingleValueDescriptor(String key) { + if (additionalDescriptors.containsKey(key)) + return true; + return getRepository().isSingleValueDescriptor(key); + } + + public Value getDescriptorValue(String key) { + if (additionalDescriptors.containsKey(key)) + return new StrValue(additionalDescriptors.get(key)); + return getRepository().getDescriptorValue(key); + } + + public Value[] getDescriptorValues(String key) { + return getRepository().getDescriptorValues(key); + } + + public synchronized void setRepository(Repository repository) { + this.repository = repository; + } + + public void setAutocreateWorkspaces(Boolean autocreateWorkspaces) { + this.autocreateWorkspaces = autocreateWorkspaces; + } + + protected static class StrValue implements Value { + private final String str; + + public StrValue(String str) { + this.str = str; + } + + @Override + public String getString() throws ValueFormatException, IllegalStateException, RepositoryException { + return str; + } + + @Override + public InputStream getStream() throws RepositoryException { + throw new UnsupportedOperationException(); + } + + @Override + public Binary getBinary() throws RepositoryException { + throw new UnsupportedOperationException(); + } + + @Override + public long getLong() throws ValueFormatException, RepositoryException { + try { + return Long.parseLong(str); + } catch (NumberFormatException e) { + throw new ValueFormatException("Cannot convert", e); + } + } + + @Override + public double getDouble() throws ValueFormatException, RepositoryException { + try { + return Double.parseDouble(str); + } catch (NumberFormatException e) { + throw new ValueFormatException("Cannot convert", e); + } + } + + @Override + public BigDecimal getDecimal() throws ValueFormatException, RepositoryException { + try { + return new BigDecimal(str); + } catch (NumberFormatException e) { + throw new ValueFormatException("Cannot convert", e); + } + } + + @Override + public Calendar getDate() throws ValueFormatException, RepositoryException { + throw new UnsupportedOperationException(); + } + + @Override + public boolean getBoolean() throws ValueFormatException, RepositoryException { + try { + return Boolean.parseBoolean(str); + } catch (NumberFormatException e) { + throw new ValueFormatException("Cannot convert", e); + } + } + + @Override + public int getType() { + return PropertyType.STRING; + } + + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrUrlStreamHandler.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrUrlStreamHandler.java new file mode 100644 index 000000000..82a65e7f1 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrUrlStreamHandler.java @@ -0,0 +1,70 @@ +package org.argeo.jcr; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; + +import javax.jcr.Item; +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.nodetype.NodeType; + +/** URL stream handler able to deal with nt:file node and properties. NOT FINISHED */ +public class JcrUrlStreamHandler extends URLStreamHandler { + private final Session session; + + public JcrUrlStreamHandler(Session session) { + this.session = session; + } + + @Override + protected URLConnection openConnection(final URL u) throws IOException { + // TODO Auto-generated method stub + return new URLConnection(u) { + + @Override + public void connect() throws IOException { + String itemPath = u.getPath(); + try { + if (!session.itemExists(itemPath)) + throw new IOException("No item under " + itemPath); + + Item item = session.getItem(u.getPath()); + if (item.isNode()) { + // this should be a nt:file node + Node node = (Node) item; + if (!node.getPrimaryNodeType().isNodeType( + NodeType.NT_FILE)) + throw new IOException("Node " + node + " is not a " + + NodeType.NT_FILE); + + } else { + Property property = (Property) item; + if(property.getType()==PropertyType.BINARY){ + //Binary binary = property.getBinary(); + + } + } + } catch (RepositoryException e) { + IOException ioe = new IOException( + "Unexpected JCR exception"); + ioe.initCause(e); + throw ioe; + } + } + + @Override + public InputStream getInputStream() throws IOException { + // TODO Auto-generated method stub + return super.getInputStream(); + } + + }; + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrUtils.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrUtils.java new file mode 100644 index 000000000..3be8be184 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrUtils.java @@ -0,0 +1,1778 @@ +package org.argeo.jcr; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.PipedInputStream; +import java.io.PipedOutputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.nio.file.Files; +import java.nio.file.Path; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.Principal; +import java.text.DateFormat; +import java.text.ParseException; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import javax.jcr.Binary; +import javax.jcr.Credentials; +import javax.jcr.ImportUUIDBehavior; +import javax.jcr.NamespaceRegistry; +import javax.jcr.NoSuchWorkspaceException; +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.Property; +import javax.jcr.PropertyIterator; +import javax.jcr.PropertyType; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.Value; +import javax.jcr.Workspace; +import javax.jcr.nodetype.NoSuchNodeTypeException; +import javax.jcr.nodetype.NodeType; +import javax.jcr.observation.EventListener; +import javax.jcr.query.Query; +import javax.jcr.query.QueryResult; +import javax.jcr.security.AccessControlEntry; +import javax.jcr.security.AccessControlList; +import javax.jcr.security.AccessControlManager; +import javax.jcr.security.AccessControlPolicy; +import javax.jcr.security.AccessControlPolicyIterator; +import javax.jcr.security.Privilege; + +import org.apache.commons.io.IOUtils; + +/** Utility methods to simplify common JCR operations. */ +public class JcrUtils { + +// final private static Log log = LogFactory.getLog(JcrUtils.class); + + /** + * Not complete yet. See + * http://www.day.com/specs/jcr/2.0/3_Repository_Model.html#3.2.2%20Local + * %20Names + */ + public final static char[] INVALID_NAME_CHARACTERS = { '/', ':', '[', ']', '|', '*', /* invalid for XML: */ '<', + '>', '&' }; + + /** Prevents instantiation */ + private JcrUtils() { + } + + /** + * Queries one single node. + * + * @return one single node or null if none was found + * @throws JcrException if more than one node was found + */ + public static Node querySingleNode(Query query) { + NodeIterator nodeIterator; + try { + QueryResult queryResult = query.execute(); + nodeIterator = queryResult.getNodes(); + } catch (RepositoryException e) { + throw new JcrException("Cannot execute query " + query, e); + } + Node node; + if (nodeIterator.hasNext()) + node = nodeIterator.nextNode(); + else + return null; + + if (nodeIterator.hasNext()) + throw new IllegalArgumentException("Query returned more than one node."); + return node; + } + + /** Retrieves the node name from the provided path */ + public static String nodeNameFromPath(String path) { + if (path.equals("/")) + return ""; + if (path.charAt(0) != '/') + throw new IllegalArgumentException("Path " + path + " must start with a '/'"); + String pathT = path; + if (pathT.charAt(pathT.length() - 1) == '/') + pathT = pathT.substring(0, pathT.length() - 2); + + int index = pathT.lastIndexOf('/'); + return pathT.substring(index + 1); + } + + /** Retrieves the parent path of the provided path */ + public static String parentPath(String path) { + if (path.equals("/")) + throw new IllegalArgumentException("Root path '/' has no parent path"); + if (path.charAt(0) != '/') + throw new IllegalArgumentException("Path " + path + " must start with a '/'"); + String pathT = path; + if (pathT.charAt(pathT.length() - 1) == '/') + pathT = pathT.substring(0, pathT.length() - 2); + + int index = pathT.lastIndexOf('/'); + return pathT.substring(0, index); + } + + /** The provided data as a path ('/' at the end, not the beginning) */ + public static String dateAsPath(Calendar cal) { + return dateAsPath(cal, false); + } + + /** + * Creates a deep path based on a URL: + * http://subdomain.example.com/to/content?args becomes + * com/example/subdomain/to/content + */ + public static String urlAsPath(String url) { + try { + URL u = new URL(url); + StringBuffer path = new StringBuffer(url.length()); + // invert host + path.append(hostAsPath(u.getHost())); + // we don't put port since it may not always be there and may change + path.append(u.getPath()); + return path.toString(); + } catch (MalformedURLException e) { + throw new IllegalArgumentException("Cannot generate URL path for " + url, e); + } + } + + /** Set the {@link NodeType#NT_ADDRESS} properties based on this URL. */ + public static void urlToAddressProperties(Node node, String url) { + try { + URL u = new URL(url); + node.setProperty(Property.JCR_PROTOCOL, u.getProtocol()); + node.setProperty(Property.JCR_HOST, u.getHost()); + node.setProperty(Property.JCR_PORT, Integer.toString(u.getPort())); + node.setProperty(Property.JCR_PATH, normalizePath(u.getPath())); + } catch (RepositoryException e) { + throw new JcrException("Cannot set URL " + url + " as nt:address properties", e); + } catch (MalformedURLException e) { + throw new IllegalArgumentException("Cannot set URL " + url + " as nt:address properties", e); + } + } + + /** Build URL based on the {@link NodeType#NT_ADDRESS} properties. */ + public static String urlFromAddressProperties(Node node) { + try { + URL u = new URL(node.getProperty(Property.JCR_PROTOCOL).getString(), + node.getProperty(Property.JCR_HOST).getString(), + (int) node.getProperty(Property.JCR_PORT).getLong(), + node.getProperty(Property.JCR_PATH).getString()); + return u.toString(); + } catch (RepositoryException e) { + throw new JcrException("Cannot get URL from nt:address properties of " + node, e); + } catch (MalformedURLException e) { + throw new IllegalArgumentException("Cannot get URL from nt:address properties of " + node, e); + } + } + + /* + * PATH UTILITIES + */ + + /** + * Make sure that: starts with '/', do not end with '/', do not have '//' + */ + public static String normalizePath(String path) { + List tokens = tokenize(path); + StringBuffer buf = new StringBuffer(path.length()); + for (String token : tokens) { + buf.append('/'); + buf.append(token); + } + return buf.toString(); + } + + /** + * Creates a path from a FQDN, inverting the order of the component: + * www.argeo.org becomes org.argeo.www + */ + public static String hostAsPath(String host) { + StringBuffer path = new StringBuffer(host.length()); + String[] hostTokens = host.split("\\."); + for (int i = hostTokens.length - 1; i >= 0; i--) { + path.append(hostTokens[i]); + if (i != 0) + path.append('/'); + } + return path.toString(); + } + + /** + * Creates a path from a UUID (e.g. 6ebda899-217d-4bf1-abe4-2839085c8f3c becomes + * 6ebda899-217d/4bf1/abe4/2839085c8f3c/). '/' at the end, not the beginning + */ + public static String uuidAsPath(String uuid) { + StringBuffer path = new StringBuffer(uuid.length()); + String[] tokens = uuid.split("-"); + for (int i = 0; i < tokens.length; i++) { + path.append(tokens[i]); + if (i != 0) + path.append('/'); + } + return path.toString(); + } + + /** + * The provided data as a path ('/' at the end, not the beginning) + * + * @param cal the date + * @param addHour whether to add hour as well + */ + public static String dateAsPath(Calendar cal, Boolean addHour) { + StringBuffer buf = new StringBuffer(14); + buf.append('Y'); + buf.append(cal.get(Calendar.YEAR)); + buf.append('/'); + + int month = cal.get(Calendar.MONTH) + 1; + buf.append('M'); + if (month < 10) + buf.append(0); + buf.append(month); + buf.append('/'); + + int day = cal.get(Calendar.DAY_OF_MONTH); + buf.append('D'); + if (day < 10) + buf.append(0); + buf.append(day); + buf.append('/'); + + if (addHour) { + int hour = cal.get(Calendar.HOUR_OF_DAY); + buf.append('H'); + if (hour < 10) + buf.append(0); + buf.append(hour); + buf.append('/'); + } + return buf.toString(); + + } + + /** Converts in one call a string into a gregorian calendar. */ + public static Calendar parseCalendar(DateFormat dateFormat, String value) { + try { + Date date = dateFormat.parse(value); + Calendar calendar = new GregorianCalendar(); + calendar.setTime(date); + return calendar; + } catch (ParseException e) { + throw new IllegalArgumentException("Cannot parse " + value + " with date format " + dateFormat, e); + } + + } + + /** The last element of a path. */ + public static String lastPathElement(String path) { + if (path.charAt(path.length() - 1) == '/') + throw new IllegalArgumentException("Path " + path + " cannot end with '/'"); + int index = path.lastIndexOf('/'); + if (index < 0) + return path; + return path.substring(index + 1); + } + + /** + * Call {@link Node#getName()} without exceptions (useful in super + * constructors). + */ + public static String getNameQuietly(Node node) { + try { + return node.getName(); + } catch (RepositoryException e) { + throw new JcrException("Cannot get name from " + node, e); + } + } + + /** + * Call {@link Node#getProperty(String)} without exceptions (useful in super + * constructors). + */ + public static String getStringPropertyQuietly(Node node, String propertyName) { + try { + return node.getProperty(propertyName).getString(); + } catch (RepositoryException e) { + throw new JcrException("Cannot get name from " + node, e); + } + } + +// /** +// * Routine that get the child with this name, adding it if it does not already +// * exist +// */ +// public static Node getOrAdd(Node parent, String name, String primaryNodeType) throws RepositoryException { +// return parent.hasNode(name) ? parent.getNode(name) : parent.addNode(name, primaryNodeType); +// } + + /** + * Routine that get the child with this name, adding it if it does not already + * exist + */ + public static Node getOrAdd(Node parent, String name, String primaryNodeType, String... mixinNodeTypes) + throws RepositoryException { + Node node; + if (parent.hasNode(name)) { + node = parent.getNode(name); + if (primaryNodeType != null && !node.isNodeType(primaryNodeType)) + throw new IllegalArgumentException("Node " + node + " exists but is of primary node type " + + node.getPrimaryNodeType().getName() + ", not " + primaryNodeType); + for (String mixin : mixinNodeTypes) { + if (!node.isNodeType(mixin)) + node.addMixin(mixin); + } + return node; + } else { + node = primaryNodeType != null ? parent.addNode(name, primaryNodeType) : parent.addNode(name); + for (String mixin : mixinNodeTypes) { + node.addMixin(mixin); + } + return node; + } + } + + /** + * Routine that get the child with this name, adding it if it does not already + * exist + */ + public static Node getOrAdd(Node parent, String name) throws RepositoryException { + return parent.hasNode(name) ? parent.getNode(name) : parent.addNode(name); + } + + /** Convert a {@link NodeIterator} to a list of {@link Node} */ + public static List nodeIteratorToList(NodeIterator nodeIterator) { + List nodes = new ArrayList(); + while (nodeIterator.hasNext()) { + nodes.add(nodeIterator.nextNode()); + } + return nodes; + } + + /* + * PROPERTIES + */ + + /** + * Concisely get the string value of a property or null if this node doesn't + * have this property + */ + public static String get(Node node, String propertyName) { + try { + if (!node.hasProperty(propertyName)) + return null; + return node.getProperty(propertyName).getString(); + } catch (RepositoryException e) { + throw new JcrException("Cannot get property " + propertyName + " of " + node, e); + } + } + + /** Concisely get the path of the given node. */ + public static String getPath(Node node) { + try { + return node.getPath(); + } catch (RepositoryException e) { + throw new JcrException("Cannot get path of " + node, e); + } + } + + /** Concisely get the boolean value of a property */ + public static Boolean check(Node node, String propertyName) { + try { + return node.getProperty(propertyName).getBoolean(); + } catch (RepositoryException e) { + throw new JcrException("Cannot get property " + propertyName + " of " + node, e); + } + } + + /** Concisely get the bytes array value of a property */ + public static byte[] getBytes(Node node, String propertyName) { + try { + return getBinaryAsBytes(node.getProperty(propertyName)); + } catch (RepositoryException e) { + throw new JcrException("Cannot get property " + propertyName + " of " + node, e); + } + } + + /* + * MKDIRS + */ + + /** + * Create sub nodes relative to a parent node + */ + public static Node mkdirs(Node parentNode, String relativePath) { + return mkdirs(parentNode, relativePath, null, null); + } + + /** + * Create sub nodes relative to a parent node + * + * @param nodeType the type of the leaf node + */ + public static Node mkdirs(Node parentNode, String relativePath, String nodeType) { + return mkdirs(parentNode, relativePath, nodeType, null); + } + + /** + * Create sub nodes relative to a parent node + * + * @param nodeType the type of the leaf node + */ + public static Node mkdirs(Node parentNode, String relativePath, String nodeType, String intermediaryNodeType) { + List tokens = tokenize(relativePath); + Node currParent = parentNode; + try { + for (int i = 0; i < tokens.size(); i++) { + String name = tokens.get(i); + if (currParent.hasNode(name)) { + currParent = currParent.getNode(name); + } else { + if (i != (tokens.size() - 1)) {// intermediary + currParent = currParent.addNode(name, intermediaryNodeType); + } else {// leaf + currParent = currParent.addNode(name, nodeType); + } + } + } + return currParent; + } catch (RepositoryException e) { + throw new JcrException("Cannot mkdirs relative path " + relativePath + " from " + parentNode, e); + } + } + + /** + * Synchronized and save is performed, to avoid race conditions in initializers + * leading to duplicate nodes. + */ + public synchronized static Node mkdirsSafe(Session session, String path, String type) { + try { + if (session.hasPendingChanges()) + throw new IllegalStateException("Session has pending changes, save them first."); + Node node = mkdirs(session, path, type); + session.save(); + return node; + } catch (RepositoryException e) { + discardQuietly(session); + throw new JcrException("Cannot safely make directories", e); + } + } + + public synchronized static Node mkdirsSafe(Session session, String path) { + return mkdirsSafe(session, path, null); + } + + /** Creates the nodes making path, if they don't exist. */ + public static Node mkdirs(Session session, String path) { + return mkdirs(session, path, null, null, false); + } + + /** + * @param type the type of the leaf node + */ + public static Node mkdirs(Session session, String path, String type) { + return mkdirs(session, path, type, null, false); + } + + /** + * Creates the nodes making path, if they don't exist. This is up to the caller + * to save the session. Use with caution since it can create duplicate nodes if + * used concurrently. Requires read access to the root node of the workspace. + */ + public static Node mkdirs(Session session, String path, String type, String intermediaryNodeType, + Boolean versioning) { + try { + if (path.equals("/")) + return session.getRootNode(); + + if (session.itemExists(path)) { + Node node = session.getNode(path); + // check type + if (type != null && !node.isNodeType(type) && !node.getPath().equals("/")) + throw new IllegalArgumentException("Node " + node + " exists but is of type " + + node.getPrimaryNodeType().getName() + " not of type " + type); + // TODO: check versioning + return node; + } + + // StringBuffer current = new StringBuffer("/"); + // Node currentNode = session.getRootNode(); + + Node currentNode = findClosestExistingParent(session, path); + String closestExistingParentPath = currentNode.getPath(); + StringBuffer current = new StringBuffer(closestExistingParentPath); + if (!closestExistingParentPath.endsWith("/")) + current.append('/'); + Iterator it = tokenize(path.substring(closestExistingParentPath.length())).iterator(); + while (it.hasNext()) { + String part = it.next(); + current.append(part).append('/'); + if (!session.itemExists(current.toString())) { + if (!it.hasNext() && type != null) + currentNode = currentNode.addNode(part, type); + else if (it.hasNext() && intermediaryNodeType != null) + currentNode = currentNode.addNode(part, intermediaryNodeType); + else + currentNode = currentNode.addNode(part); + if (versioning) + currentNode.addMixin(NodeType.MIX_VERSIONABLE); +// if (log.isTraceEnabled()) +// log.debug("Added folder " + part + " as " + current); + } else { + currentNode = (Node) session.getItem(current.toString()); + } + } + return currentNode; + } catch (RepositoryException e) { + discardQuietly(session); + throw new JcrException("Cannot mkdirs " + path, e); + } finally { + } + } + + private static Node findClosestExistingParent(Session session, String path) throws RepositoryException { + int idx = path.lastIndexOf('/'); + if (idx == 0) + return session.getRootNode(); + String parentPath = path.substring(0, idx); + if (session.itemExists(parentPath)) + return session.getNode(parentPath); + else + return findClosestExistingParent(session, parentPath); + } + + /** Convert a path to the list of its tokens */ + public static List tokenize(String path) { + List tokens = new ArrayList(); + boolean optimized = false; + if (!optimized) { + String[] rawTokens = path.split("/"); + for (String token : rawTokens) { + if (!token.equals("")) + tokens.add(token); + } + } else { + StringBuffer curr = new StringBuffer(); + char[] arr = path.toCharArray(); + chars: for (int i = 0; i < arr.length; i++) { + char c = arr[i]; + if (c == '/') { + if (i == 0 || (i == arr.length - 1)) + continue chars; + if (curr.length() > 0) { + tokens.add(curr.toString()); + curr = new StringBuffer(); + } + } else + curr.append(c); + } + if (curr.length() > 0) { + tokens.add(curr.toString()); + curr = new StringBuffer(); + } + } + return Collections.unmodifiableList(tokens); + } + + // /** + // * use {@link #mkdirs(Session, String, String, String, Boolean)} instead. + // * + // * @deprecated + // */ + // @Deprecated + // public static Node mkdirs(Session session, String path, String type, + // Boolean versioning) { + // return mkdirs(session, path, type, type, false); + // } + + /** + * Safe and repository implementation independent registration of a namespace. + */ + public static void registerNamespaceSafely(Session session, String prefix, String uri) { + try { + registerNamespaceSafely(session.getWorkspace().getNamespaceRegistry(), prefix, uri); + } catch (RepositoryException e) { + throw new JcrException("Cannot find namespace registry", e); + } + } + + /** + * Safe and repository implementation independent registration of a namespace. + */ + public static void registerNamespaceSafely(NamespaceRegistry nr, String prefix, String uri) { + try { + String[] prefixes = nr.getPrefixes(); + for (String pref : prefixes) + if (pref.equals(prefix)) { + String registeredUri = nr.getURI(pref); + if (!registeredUri.equals(uri)) + throw new IllegalArgumentException("Prefix " + pref + " already registered for URI " + + registeredUri + " which is different from provided URI " + uri); + else + return;// skip + } + nr.registerNamespace(prefix, uri); + } catch (RepositoryException e) { + throw new JcrException("Cannot register namespace " + uri + " under prefix " + prefix, e); + } + } + +// /** Recursively outputs the contents of the given node. */ +// public static void debug(Node node) { +// debug(node, log); +// } +// +// /** Recursively outputs the contents of the given node. */ +// public static void debug(Node node, Log log) { +// try { +// // First output the node path +// log.debug(node.getPath()); +// // Skip the virtual (and large!) jcr:system subtree +// if (node.getName().equals("jcr:system")) { +// return; +// } +// +// // Then the children nodes (recursive) +// NodeIterator it = node.getNodes(); +// while (it.hasNext()) { +// Node childNode = it.nextNode(); +// debug(childNode, log); +// } +// +// // Then output the properties +// PropertyIterator properties = node.getProperties(); +// // log.debug("Property are : "); +// +// properties: while (properties.hasNext()) { +// Property property = properties.nextProperty(); +// if (property.getType() == PropertyType.BINARY) +// continue properties;// skip +// if (property.getDefinition().isMultiple()) { +// // A multi-valued property, print all values +// Value[] values = property.getValues(); +// for (int i = 0; i < values.length; i++) { +// log.debug(property.getPath() + "=" + values[i].getString()); +// } +// } else { +// // A single-valued property +// log.debug(property.getPath() + "=" + property.getString()); +// } +// } +// } catch (Exception e) { +// log.error("Could not debug " + node, e); +// } +// +// } + +// /** Logs the effective access control policies */ +// public static void logEffectiveAccessPolicies(Node node) { +// try { +// logEffectiveAccessPolicies(node.getSession(), node.getPath()); +// } catch (RepositoryException e) { +// log.error("Cannot log effective access policies of " + node, e); +// } +// } +// +// /** Logs the effective access control policies */ +// public static void logEffectiveAccessPolicies(Session session, String path) { +// if (!log.isDebugEnabled()) +// return; +// +// try { +// AccessControlPolicy[] effectivePolicies = session.getAccessControlManager().getEffectivePolicies(path); +// if (effectivePolicies.length > 0) { +// for (AccessControlPolicy policy : effectivePolicies) { +// if (policy instanceof AccessControlList) { +// AccessControlList acl = (AccessControlList) policy; +// log.debug("Access control list for " + path + "\n" + accessControlListSummary(acl)); +// } +// } +// } else { +// log.debug("No effective access control policy for " + path); +// } +// } catch (RepositoryException e) { +// log.error("Cannot log effective access policies of " + path, e); +// } +// } + + /** Returns a human-readable summary of this access control list. */ + public static String accessControlListSummary(AccessControlList acl) { + StringBuffer buf = new StringBuffer(""); + try { + for (AccessControlEntry ace : acl.getAccessControlEntries()) { + buf.append('\t').append(ace.getPrincipal().getName()).append('\n'); + for (Privilege priv : ace.getPrivileges()) + buf.append("\t\t").append(priv.getName()).append('\n'); + } + return buf.toString(); + } catch (RepositoryException e) { + throw new JcrException("Cannot write summary of " + acl, e); + } + } + + /** Copy the whole workspace via a system view XML. */ + public static void copyWorkspaceXml(Session fromSession, Session toSession) { + Workspace fromWorkspace = fromSession.getWorkspace(); + Workspace toWorkspace = toSession.getWorkspace(); + String errorMsg = "Cannot copy workspace " + fromWorkspace + " to " + toWorkspace + " via XML."; + + try (PipedInputStream in = new PipedInputStream(1024 * 1024);) { + new Thread(() -> { + try (PipedOutputStream out = new PipedOutputStream(in)) { + fromSession.exportSystemView("/", out, false, false); + out.flush(); + } catch (IOException e) { + throw new RuntimeException(errorMsg, e); + } catch (RepositoryException e) { + throw new JcrException(errorMsg, e); + } + }, "Copy workspace" + fromWorkspace + " to " + toWorkspace).start(); + + toSession.importXML("/", in, ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING); + toSession.save(); + } catch (IOException e) { + throw new RuntimeException(errorMsg, e); + } catch (RepositoryException e) { + throw new JcrException(errorMsg, e); + } + } + + /** + * Copies recursively the content of a node to another one. Do NOT copy the + * property values of {@link NodeType#MIX_CREATED} and + * {@link NodeType#MIX_LAST_MODIFIED}, but update the + * {@link Property#JCR_LAST_MODIFIED} and {@link Property#JCR_LAST_MODIFIED_BY} + * properties if the target node has the {@link NodeType#MIX_LAST_MODIFIED} + * mixin. + */ + public static void copy(Node fromNode, Node toNode) { + try { + if (toNode.getDefinition().isProtected()) + return; + + // add mixins + for (NodeType mixinType : fromNode.getMixinNodeTypes()) { + try { + toNode.addMixin(mixinType.getName()); + } catch (NoSuchNodeTypeException e) { + // ignore unknown mixins + // TODO log it + } + } + + // process properties + PropertyIterator pit = fromNode.getProperties(); + properties: while (pit.hasNext()) { + Property fromProperty = pit.nextProperty(); + String propertyName = fromProperty.getName(); + if (toNode.hasProperty(propertyName) && toNode.getProperty(propertyName).getDefinition().isProtected()) + continue properties; + + if (fromProperty.getDefinition().isProtected()) + continue properties; + + if (propertyName.equals("jcr:created") || propertyName.equals("jcr:createdBy") + || propertyName.equals("jcr:lastModified") || propertyName.equals("jcr:lastModifiedBy")) + continue properties; + + if (fromProperty.isMultiple()) { + toNode.setProperty(propertyName, fromProperty.getValues()); + } else { + toNode.setProperty(propertyName, fromProperty.getValue()); + } + } + + // update jcr:lastModified and jcr:lastModifiedBy in toNode in case + // they existed, before adding the mixins + updateLastModified(toNode, true); + + // process children nodes + NodeIterator nit = fromNode.getNodes(); + while (nit.hasNext()) { + Node fromChild = nit.nextNode(); + Integer index = fromChild.getIndex(); + String nodeRelPath = fromChild.getName() + "[" + index + "]"; + Node toChild; + if (toNode.hasNode(nodeRelPath)) + toChild = toNode.getNode(nodeRelPath); + else { + try { + toChild = toNode.addNode(fromChild.getName(), fromChild.getPrimaryNodeType().getName()); + } catch (NoSuchNodeTypeException e) { + // ignore unknown primary types + // TODO log it + return; + } + } + copy(fromChild, toChild); + } + } catch (RepositoryException e) { + throw new JcrException("Cannot copy " + fromNode + " to " + toNode, e); + } + } + + /** + * Check whether all first-level properties (except jcr:* properties) are equal. + * Skip jcr:* properties + */ + public static Boolean allPropertiesEquals(Node reference, Node observed, Boolean onlyCommonProperties) { + try { + PropertyIterator pit = reference.getProperties(); + props: while (pit.hasNext()) { + Property propReference = pit.nextProperty(); + String propName = propReference.getName(); + if (propName.startsWith("jcr:")) + continue props; + + if (!observed.hasProperty(propName)) + if (onlyCommonProperties) + continue props; + else + return false; + // TODO: deal with multiple property values? + if (!observed.getProperty(propName).getValue().equals(propReference.getValue())) + return false; + } + return true; + } catch (RepositoryException e) { + throw new JcrException("Cannot check all properties equals of " + reference + " and " + observed, e); + } + } + + public static Map diffProperties(Node reference, Node observed) { + Map diffs = new TreeMap(); + diffPropertiesLevel(diffs, null, reference, observed); + return diffs; + } + + /** + * Compare the properties of two nodes. Recursivity to child nodes is not yet + * supported. Skip jcr:* properties. + */ + static void diffPropertiesLevel(Map diffs, String baseRelPath, Node reference, + Node observed) { + try { + // check removed and modified + PropertyIterator pit = reference.getProperties(); + props: while (pit.hasNext()) { + Property p = pit.nextProperty(); + String name = p.getName(); + if (name.startsWith("jcr:")) + continue props; + + if (!observed.hasProperty(name)) { + String relPath = propertyRelPath(baseRelPath, name); + PropertyDiff pDiff = new PropertyDiff(PropertyDiff.REMOVED, relPath, p.getValue(), null); + diffs.put(relPath, pDiff); + } else { + if (p.isMultiple()) { + // FIXME implement multiple + } else { + Value referenceValue = p.getValue(); + Value newValue = observed.getProperty(name).getValue(); + if (!referenceValue.equals(newValue)) { + String relPath = propertyRelPath(baseRelPath, name); + PropertyDiff pDiff = new PropertyDiff(PropertyDiff.MODIFIED, relPath, referenceValue, + newValue); + diffs.put(relPath, pDiff); + } + } + } + } + // check added + pit = observed.getProperties(); + props: while (pit.hasNext()) { + Property p = pit.nextProperty(); + String name = p.getName(); + if (name.startsWith("jcr:")) + continue props; + if (!reference.hasProperty(name)) { + if (p.isMultiple()) { + // FIXME implement multiple + } else { + String relPath = propertyRelPath(baseRelPath, name); + PropertyDiff pDiff = new PropertyDiff(PropertyDiff.ADDED, relPath, null, p.getValue()); + diffs.put(relPath, pDiff); + } + } + } + } catch (RepositoryException e) { + throw new JcrException("Cannot diff " + reference + " and " + observed, e); + } + } + + /** + * Compare only a restricted list of properties of two nodes. No recursivity. + * + */ + public static Map diffProperties(Node reference, Node observed, List properties) { + Map diffs = new TreeMap(); + try { + Iterator pit = properties.iterator(); + + props: while (pit.hasNext()) { + String name = pit.next(); + if (!reference.hasProperty(name)) { + if (!observed.hasProperty(name)) + continue props; + Value val = observed.getProperty(name).getValue(); + try { + // empty String but not null + if ("".equals(val.getString())) + continue props; + } catch (Exception e) { + // not parseable as String, silent + } + PropertyDiff pDiff = new PropertyDiff(PropertyDiff.ADDED, name, null, val); + diffs.put(name, pDiff); + } else if (!observed.hasProperty(name)) { + PropertyDiff pDiff = new PropertyDiff(PropertyDiff.REMOVED, name, + reference.getProperty(name).getValue(), null); + diffs.put(name, pDiff); + } else { + Value referenceValue = reference.getProperty(name).getValue(); + Value newValue = observed.getProperty(name).getValue(); + if (!referenceValue.equals(newValue)) { + PropertyDiff pDiff = new PropertyDiff(PropertyDiff.MODIFIED, name, referenceValue, newValue); + diffs.put(name, pDiff); + } + } + } + } catch (RepositoryException e) { + throw new JcrException("Cannot diff " + reference + " and " + observed, e); + } + return diffs; + } + + /** Builds a property relPath to be used in the diff. */ + private static String propertyRelPath(String baseRelPath, String propertyName) { + if (baseRelPath == null) + return propertyName; + else + return baseRelPath + '/' + propertyName; + } + + /** + * Normalizes a name so that it can be stored in contexts not supporting names + * with ':' (typically databases). Replaces ':' by '_'. + */ + public static String normalize(String name) { + return name.replace(':', '_'); + } + + /** + * Replaces characters which are invalid in a JCR name by '_'. Currently not + * exhaustive. + * + * @see JcrUtils#INVALID_NAME_CHARACTERS + */ + public static String replaceInvalidChars(String name) { + return replaceInvalidChars(name, '_'); + } + + /** + * Replaces characters which are invalid in a JCR name. Currently not + * exhaustive. + * + * @see JcrUtils#INVALID_NAME_CHARACTERS + */ + public static String replaceInvalidChars(String name, char replacement) { + boolean modified = false; + char[] arr = name.toCharArray(); + for (int i = 0; i < arr.length; i++) { + char c = arr[i]; + invalid: for (char invalid : INVALID_NAME_CHARACTERS) { + if (c == invalid) { + arr[i] = replacement; + modified = true; + break invalid; + } + } + } + if (modified) + return new String(arr); + else + // do not create new object if unnecessary + return name; + } + + // /** + // * Removes forbidden characters from a path, replacing them with '_' + // * + // * @deprecated use {@link #replaceInvalidChars(String)} instead + // */ + // public static String removeForbiddenCharacters(String str) { + // return str.replace('[', '_').replace(']', '_').replace('/', '_').replace('*', + // '_'); + // + // } + + /** Cleanly disposes a {@link Binary} even if it is null. */ + public static void closeQuietly(Binary binary) { + if (binary == null) + return; + binary.dispose(); + } + + /** Retrieve a {@link Binary} as a byte array */ + public static byte[] getBinaryAsBytes(Property property) { + try (ByteArrayOutputStream out = new ByteArrayOutputStream(); + Bin binary = new Bin(property); + InputStream in = binary.getStream()) { + IOUtils.copy(in, out); + return out.toByteArray(); + } catch (RepositoryException e) { + throw new JcrException("Cannot read binary " + property + " as bytes", e); + } catch (IOException e) { + throw new RuntimeException("Cannot read binary " + property + " as bytes", e); + } + } + + /** Writes a {@link Binary} from a byte array */ + public static void setBinaryAsBytes(Node node, String property, byte[] bytes) { + Binary binary = null; + try (InputStream in = new ByteArrayInputStream(bytes)) { + binary = node.getSession().getValueFactory().createBinary(in); + node.setProperty(property, binary); + } catch (RepositoryException e) { + throw new JcrException("Cannot set binary " + property + " as bytes", e); + } catch (IOException e) { + throw new RuntimeException("Cannot set binary " + property + " as bytes", e); + } finally { + closeQuietly(binary); + } + } + + /** Writes a {@link Binary} from a byte array */ + public static void setBinaryAsBytes(Property prop, byte[] bytes) { + Binary binary = null; + try (InputStream in = new ByteArrayInputStream(bytes)) { + binary = prop.getSession().getValueFactory().createBinary(in); + prop.setValue(binary); + } catch (RepositoryException e) { + throw new JcrException("Cannot set binary " + prop + " as bytes", e); + } catch (IOException e) { + throw new RuntimeException("Cannot set binary " + prop + " as bytes", e); + } finally { + closeQuietly(binary); + } + } + + /** + * Creates depth from a string (typically a username) by adding levels based on + * its first characters: "aBcD",2 becomes a/aB + */ + public static String firstCharsToPath(String str, Integer nbrOfChars) { + if (str.length() < nbrOfChars) + throw new IllegalArgumentException("String " + str + " length must be greater or equal than " + nbrOfChars); + StringBuffer path = new StringBuffer(""); + StringBuffer curr = new StringBuffer(""); + for (int i = 0; i < nbrOfChars; i++) { + curr.append(str.charAt(i)); + path.append(curr); + if (i < nbrOfChars - 1) + path.append('/'); + } + return path.toString(); + } + + /** + * Discards the current changes in the session attached to this node. To be used + * typically in a catch block. + * + * @see #discardQuietly(Session) + */ + public static void discardUnderlyingSessionQuietly(Node node) { + try { + discardQuietly(node.getSession()); + } catch (RepositoryException e) { + // silent + } + } + + /** + * Discards the current changes in a session by calling + * {@link Session#refresh(boolean)} with false, only logging + * potential errors when doing so. To be used typically in a catch block. + */ + public static void discardQuietly(Session session) { + try { + if (session != null) + session.refresh(false); + } catch (RepositoryException e) { + // silent + } + } + + /** + * Login to a workspace with implicit credentials, creates the workspace with + * these credentials if it does not already exist. + */ + public static Session loginOrCreateWorkspace(Repository repository, String workspaceName) + throws RepositoryException { + return loginOrCreateWorkspace(repository, workspaceName, null); + } + + /** + * Login to a workspace with implicit credentials, creates the workspace with + * these credentials if it does not already exist. + */ + public static Session loginOrCreateWorkspace(Repository repository, String workspaceName, Credentials credentials) + throws RepositoryException { + Session workspaceSession = null; + Session defaultSession = null; + try { + try { + workspaceSession = repository.login(credentials, workspaceName); + } catch (NoSuchWorkspaceException e) { + // try to create workspace + defaultSession = repository.login(credentials); + defaultSession.getWorkspace().createWorkspace(workspaceName); + workspaceSession = repository.login(credentials, workspaceName); + } + return workspaceSession; + } finally { + logoutQuietly(defaultSession); + } + } + + /** + * Logs out the session, not throwing any exception, even if it is null. + * {@link Jcr#logout(Session)} should rather be used. + */ + public static void logoutQuietly(Session session) { + Jcr.logout(session); +// try { +// if (session != null) +// if (session.isLive()) +// session.logout(); +// } catch (Exception e) { +// // silent +// } + } + + /** + * Convenient method to add a listener. uuids passed as null, deep=true, + * local=true, only one node type + */ + public static void addListener(Session session, EventListener listener, int eventTypes, String basePath, + String nodeType) { + try { + session.getWorkspace().getObservationManager().addEventListener(listener, eventTypes, basePath, true, null, + nodeType == null ? null : new String[] { nodeType }, true); + } catch (RepositoryException e) { + throw new JcrException("Cannot add JCR listener " + listener + " to session " + session, e); + } + } + + /** Removes a listener without throwing exception */ + public static void removeListenerQuietly(Session session, EventListener listener) { + if (session == null || !session.isLive()) + return; + try { + session.getWorkspace().getObservationManager().removeEventListener(listener); + } catch (RepositoryException e) { + // silent + } + } + + /** + * Quietly unregisters an {@link EventListener} from the udnerlying workspace of + * this node. + */ + public static void unregisterQuietly(Node node, EventListener eventListener) { + try { + unregisterQuietly(node.getSession().getWorkspace(), eventListener); + } catch (RepositoryException e) { + // silent + } + } + + /** Quietly unregisters an {@link EventListener} from this workspace */ + public static void unregisterQuietly(Workspace workspace, EventListener eventListener) { + if (eventListener == null) + return; + try { + workspace.getObservationManager().removeEventListener(eventListener); + } catch (RepositoryException e) { + // silent + } + } + + /** + * Checks whether {@link Property#JCR_LAST_MODIFIED} or (afterwards) + * {@link Property#JCR_CREATED} are set and returns it as an {@link Instant}. + */ + public static Instant getModified(Node node) { + Calendar calendar = null; + try { + if (node.hasProperty(Property.JCR_LAST_MODIFIED)) + calendar = node.getProperty(Property.JCR_LAST_MODIFIED).getDate(); + else if (node.hasProperty(Property.JCR_CREATED)) + calendar = node.getProperty(Property.JCR_CREATED).getDate(); + else + throw new IllegalArgumentException("No modification time found in " + node); + return calendar.toInstant(); + } catch (RepositoryException e) { + throw new JcrException("Cannot get modification time for " + node, e); + } + + } + + /** + * Get {@link Property#JCR_CREATED} as an {@link Instant}, if it is set. + */ + public static Instant getCreated(Node node) { + Calendar calendar = null; + try { + if (node.hasProperty(Property.JCR_CREATED)) + calendar = node.getProperty(Property.JCR_CREATED).getDate(); + else + throw new IllegalArgumentException("No created time found in " + node); + return calendar.toInstant(); + } catch (RepositoryException e) { + throw new JcrException("Cannot get created time for " + node, e); + } + + } + + /** + * Updates the {@link Property#JCR_LAST_MODIFIED} property with the current time + * and the {@link Property#JCR_LAST_MODIFIED_BY} property with the underlying + * session user id. + */ + public static void updateLastModified(Node node) { + updateLastModified(node, false); + } + + /** + * Updates the {@link Property#JCR_LAST_MODIFIED} property with the current time + * and the {@link Property#JCR_LAST_MODIFIED_BY} property with the underlying + * session user id. In Jackrabbit 2.x, + * these properties are + * not automatically updated, hence the need for manual update. The session + * is not saved. + */ + public static void updateLastModified(Node node, boolean addMixin) { + try { + if (addMixin && !node.isNodeType(NodeType.MIX_LAST_MODIFIED)) + node.addMixin(NodeType.MIX_LAST_MODIFIED); + node.setProperty(Property.JCR_LAST_MODIFIED, new GregorianCalendar()); + node.setProperty(Property.JCR_LAST_MODIFIED_BY, node.getSession().getUserID()); + } catch (RepositoryException e) { + throw new JcrException("Cannot update last modified on " + node, e); + } + } + + /** + * Update lastModified recursively until this parent. + * + * @param node the node + * @param untilPath the base path, null is equivalent to "/" + */ + public static void updateLastModifiedAndParents(Node node, String untilPath) { + updateLastModifiedAndParents(node, untilPath, true); + } + + /** + * Update lastModified recursively until this parent. + * + * @param node the node + * @param untilPath the base path, null is equivalent to "/" + */ + public static void updateLastModifiedAndParents(Node node, String untilPath, boolean addMixin) { + try { + if (untilPath != null && !node.getPath().startsWith(untilPath)) + throw new IllegalArgumentException(node + " is not under " + untilPath); + updateLastModified(node, addMixin); + if (untilPath == null) { + if (!node.getPath().equals("/")) + updateLastModifiedAndParents(node.getParent(), untilPath, addMixin); + } else { + if (!node.getPath().equals(untilPath)) + updateLastModifiedAndParents(node.getParent(), untilPath, addMixin); + } + } catch (RepositoryException e) { + throw new JcrException("Cannot update lastModified from " + node + " until " + untilPath, e); + } + } + + /** + * Returns a String representing the short version (see + * Node type + * Notation attributes grammar) of the main business attributes of this + * property definition + * + * @param prop + */ + public static String getPropertyDefinitionAsString(Property prop) { + StringBuffer sbuf = new StringBuffer(); + try { + if (prop.getDefinition().isAutoCreated()) + sbuf.append("a"); + if (prop.getDefinition().isMandatory()) + sbuf.append("m"); + if (prop.getDefinition().isProtected()) + sbuf.append("p"); + if (prop.getDefinition().isMultiple()) + sbuf.append("*"); + } catch (RepositoryException re) { + throw new JcrException("unexpected error while getting property definition as String", re); + } + return sbuf.toString(); + } + + /** + * Estimate the sub tree size from current node. Computation is based on the Jcr + * {@link Property#getLength()} method. Note : it is not the exact size used on + * the disk by the current part of the JCR Tree. + */ + + public static long getNodeApproxSize(Node node) { + long curNodeSize = 0; + try { + PropertyIterator pi = node.getProperties(); + while (pi.hasNext()) { + Property prop = pi.nextProperty(); + if (prop.isMultiple()) { + int nb = prop.getLengths().length; + for (int i = 0; i < nb; i++) { + curNodeSize += (prop.getLengths()[i] > 0 ? prop.getLengths()[i] : 0); + } + } else + curNodeSize += (prop.getLength() > 0 ? prop.getLength() : 0); + } + + NodeIterator ni = node.getNodes(); + while (ni.hasNext()) + curNodeSize += getNodeApproxSize(ni.nextNode()); + return curNodeSize; + } catch (RepositoryException re) { + throw new JcrException("Unexpected error while recursively determining node size.", re); + } + } + + /* + * SECURITY + */ + + /** + * Convenience method for adding a single privilege to a principal (user or + * role), typically jcr:all + */ + public synchronized static void addPrivilege(Session session, String path, String principal, String privilege) + throws RepositoryException { + List privileges = new ArrayList(); + privileges.add(session.getAccessControlManager().privilegeFromName(privilege)); + addPrivileges(session, path, new SimplePrincipal(principal), privileges); + } + + /** + * Add privileges on a path to a {@link Principal}. The path must already exist. + * Session is saved. Synchronized to prevent concurrent modifications of the + * same node. + */ + public synchronized static Boolean addPrivileges(Session session, String path, Principal principal, + List privs) throws RepositoryException { + // make sure the session is in line with the persisted state + session.refresh(false); + AccessControlManager acm = session.getAccessControlManager(); + AccessControlList acl = getAccessControlList(acm, path); + + accessControlEntries: for (AccessControlEntry ace : acl.getAccessControlEntries()) { + Principal currentPrincipal = ace.getPrincipal(); + if (currentPrincipal.getName().equals(principal.getName())) { + Privilege[] currentPrivileges = ace.getPrivileges(); + if (currentPrivileges.length != privs.size()) + break accessControlEntries; + for (int i = 0; i < currentPrivileges.length; i++) { + Privilege currP = currentPrivileges[i]; + Privilege p = privs.get(i); + if (!currP.getName().equals(p.getName())) { + break accessControlEntries; + } + } + return false; + } + } + + Privilege[] privileges = privs.toArray(new Privilege[privs.size()]); + acl.addAccessControlEntry(principal, privileges); + acm.setPolicy(path, acl); +// if (log.isDebugEnabled()) { +// StringBuffer privBuf = new StringBuffer(); +// for (Privilege priv : privs) +// privBuf.append(priv.getName()); +// log.debug("Added privileges " + privBuf + " to " + principal.getName() + " on " + path + " in '" +// + session.getWorkspace().getName() + "'"); +// } + session.refresh(true); + session.save(); + return true; + } + + /** + * Gets the first available access control list for this path, throws exception + * if not found + */ + public synchronized static AccessControlList getAccessControlList(AccessControlManager acm, String path) + throws RepositoryException { + // search for an access control list + AccessControlList acl = null; + AccessControlPolicyIterator policyIterator = acm.getApplicablePolicies(path); + applicablePolicies: if (policyIterator.hasNext()) { + while (policyIterator.hasNext()) { + AccessControlPolicy acp = policyIterator.nextAccessControlPolicy(); + if (acp instanceof AccessControlList) { + acl = ((AccessControlList) acp); + break applicablePolicies; + } + } + } else { + AccessControlPolicy[] existingPolicies = acm.getPolicies(path); + existingPolicies: for (AccessControlPolicy acp : existingPolicies) { + if (acp instanceof AccessControlList) { + acl = ((AccessControlList) acp); + break existingPolicies; + } + } + } + if (acl != null) + return acl; + else + throw new IllegalArgumentException("ACL not found at " + path); + } + + /** Clear authorizations for a user at this path */ + public synchronized static void clearAccessControList(Session session, String path, String username) + throws RepositoryException { + AccessControlManager acm = session.getAccessControlManager(); + AccessControlList acl = getAccessControlList(acm, path); + for (AccessControlEntry ace : acl.getAccessControlEntries()) { + if (ace.getPrincipal().getName().equals(username)) { + acl.removeAccessControlEntry(ace); + } + } + // the new access control list must be applied otherwise this call: + // acl.removeAccessControlEntry(ace); has no effect + acm.setPolicy(path, acl); + session.refresh(true); + session.save(); + } + + /* + * FILES UTILITIES + */ + /** + * Creates the nodes making the path as {@link NodeType#NT_FOLDER} + */ + public static Node mkfolders(Session session, String path) { + return mkdirs(session, path, NodeType.NT_FOLDER, NodeType.NT_FOLDER, false); + } + + /** + * Copy only nt:folder and nt:file, without their additional types and + * properties. + * + * @param recursive if true copies folders as well, otherwise only first level + * files + * @return how many files were copied + */ + public static Long copyFiles(Node fromNode, Node toNode, Boolean recursive, JcrMonitor monitor, boolean onlyAdd) { + long count = 0l; + + // Binary binary = null; + // InputStream in = null; + try { + NodeIterator fromChildren = fromNode.getNodes(); + children: while (fromChildren.hasNext()) { + if (monitor != null && monitor.isCanceled()) + throw new IllegalStateException("Copy cancelled before it was completed"); + + Node fromChild = fromChildren.nextNode(); + String fileName = fromChild.getName(); + if (fromChild.isNodeType(NodeType.NT_FILE)) { + if (onlyAdd && toNode.hasNode(fileName)) { + monitor.subTask("Skip existing " + fileName); + continue children; + } + + if (monitor != null) + monitor.subTask("Copy " + fileName); + try (Bin binary = new Bin(fromChild.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA)); + InputStream in = binary.getStream();) { + copyStreamAsFile(toNode, fileName, in); + } catch (IOException e) { + throw new RuntimeException("Cannot copy " + fileName + " to " + toNode, e); + } + + // save session + toNode.getSession().save(); + count++; + +// if (log.isDebugEnabled()) +// log.debug("Copied file " + fromChild.getPath()); + if (monitor != null) + monitor.worked(1); + } else if (fromChild.isNodeType(NodeType.NT_FOLDER) && recursive) { + Node toChildFolder; + if (toNode.hasNode(fileName)) { + toChildFolder = toNode.getNode(fileName); + if (!toChildFolder.isNodeType(NodeType.NT_FOLDER)) + throw new IllegalArgumentException(toChildFolder + " is not of type nt:folder"); + } else { + toChildFolder = toNode.addNode(fileName, NodeType.NT_FOLDER); + + // save session + toNode.getSession().save(); + } + count = count + copyFiles(fromChild, toChildFolder, recursive, monitor, onlyAdd); + } + } + return count; + } catch (RepositoryException e) { + throw new JcrException("Cannot copy files between " + fromNode + " and " + toNode, e); + } finally { + // in case there was an exception + // IOUtils.closeQuietly(in); + // closeQuietly(binary); + } + } + + /** + * Iteratively count all file nodes in subtree, inefficient but can be useful + * when query are poorly supported, such as in remoting. + */ + public static Long countFiles(Node node) { + Long localCount = 0l; + try { + for (NodeIterator nit = node.getNodes(); nit.hasNext();) { + Node child = nit.nextNode(); + if (child.isNodeType(NodeType.NT_FOLDER)) + localCount = localCount + countFiles(child); + else if (child.isNodeType(NodeType.NT_FILE)) + localCount = localCount + 1; + } + } catch (RepositoryException e) { + throw new JcrException("Cannot count all children of " + node, e); + } + return localCount; + } + + /** + * Copy a file as an nt:file, assuming an nt:folder hierarchy. The session is + * NOT saved. + * + * @return the created file node + */ + @Deprecated + public static Node copyFile(Node folderNode, File file) { + try (InputStream in = new FileInputStream(file)) { + return copyStreamAsFile(folderNode, file.getName(), in); + } catch (IOException e) { + throw new RuntimeException("Cannot copy file " + file + " under " + folderNode, e); + } + } + + /** Copy bytes as an nt:file */ + public static Node copyBytesAsFile(Node folderNode, String fileName, byte[] bytes) { + // InputStream in = null; + try (InputStream in = new ByteArrayInputStream(bytes)) { + // in = new ByteArrayInputStream(bytes); + return copyStreamAsFile(folderNode, fileName, in); + } catch (IOException e) { + throw new RuntimeException("Cannot copy file " + fileName + " under " + folderNode, e); + // } finally { + // IOUtils.closeQuietly(in); + } + } + + /** + * Copy a stream as an nt:file, assuming an nt:folder hierarchy. The session is + * NOT saved. + * + * @return the created file node + */ + public static Node copyStreamAsFile(Node folderNode, String fileName, InputStream in) { + Binary binary = null; + try { + Node fileNode; + Node contentNode; + if (folderNode.hasNode(fileName)) { + fileNode = folderNode.getNode(fileName); + if (!fileNode.isNodeType(NodeType.NT_FILE)) + throw new IllegalArgumentException(fileNode + " is not of type nt:file"); + // we assume that the content node is already there + contentNode = fileNode.getNode(Node.JCR_CONTENT); + } else { + fileNode = folderNode.addNode(fileName, NodeType.NT_FILE); + contentNode = fileNode.addNode(Node.JCR_CONTENT, NodeType.NT_UNSTRUCTURED); + } + binary = contentNode.getSession().getValueFactory().createBinary(in); + contentNode.setProperty(Property.JCR_DATA, binary); + updateLastModified(contentNode); + return fileNode; + } catch (RepositoryException e) { + throw new JcrException("Cannot create file node " + fileName + " under " + folderNode, e); + } finally { + closeQuietly(binary); + } + } + + /** Read an an nt:file as an {@link InputStream}. */ + public static InputStream getFileAsStream(Node fileNode) throws RepositoryException { + return fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary().getStream(); + } + + /** + * Set the properties of {@link NodeType#MIX_MIMETYPE} on the content of this + * file node. + */ + public static void setFileMimeType(Node fileNode, String mimeType, String encoding) throws RepositoryException { + Node contentNode = fileNode.getNode(Node.JCR_CONTENT); + if (mimeType != null) + contentNode.setProperty(Property.JCR_MIMETYPE, mimeType); + if (encoding != null) + contentNode.setProperty(Property.JCR_ENCODING, encoding); + // TODO remove properties if args are null? + } + + public static void copyFilesToFs(Node baseNode, Path targetDir, boolean recursive) { + try { + Files.createDirectories(targetDir); + for (NodeIterator nit = baseNode.getNodes(); nit.hasNext();) { + Node node = nit.nextNode(); + if (node.isNodeType(NodeType.NT_FILE)) { + Path filePath = targetDir.resolve(node.getName()); + try (OutputStream out = Files.newOutputStream(filePath); InputStream in = getFileAsStream(node)) { + IOUtils.copy(in, out); + } + } else if (recursive && node.isNodeType(NodeType.NT_FOLDER)) { + Path dirPath = targetDir.resolve(node.getName()); + copyFilesToFs(node, dirPath, true); + } + } + } catch (RepositoryException e) { + throw new JcrException("Cannot copy " + baseNode + " to " + targetDir, e); + } catch (IOException e) { + throw new RuntimeException("Cannot copy " + baseNode + " to " + targetDir, e); + } + } + + /** + * Computes the checksum of an nt:file. + * + * @deprecated use separate digest utilities + */ + @Deprecated + public static String checksumFile(Node fileNode, String algorithm) { + try (InputStream in = fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary() + .getStream()) { + return digest(algorithm, in); + } catch (IOException e) { + throw new RuntimeException("Cannot checksum file " + fileNode + " with algorithm " + algorithm, e); + } catch (RepositoryException e) { + throw new JcrException("Cannot checksum file " + fileNode + " with algorithm " + algorithm, e); + } + } + + @Deprecated + private static String digest(String algorithm, InputStream in) { + final Integer byteBufferCapacity = 100 * 1024;// 100 KB + try { + MessageDigest digest = MessageDigest.getInstance(algorithm); + byte[] buffer = new byte[byteBufferCapacity]; + int read = 0; + while ((read = in.read(buffer)) > 0) { + digest.update(buffer, 0, read); + } + + byte[] checksum = digest.digest(); + String res = encodeHexString(checksum); + return res; + } catch (IOException e) { + throw new RuntimeException("Cannot digest with algorithm " + algorithm, e); + } catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException("Cannot digest with algorithm " + algorithm, e); + } + } + + /** + * From + * http://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to + * -a-hex-string-in-java + */ + @Deprecated + private static String encodeHexString(byte[] bytes) { + final char[] hexArray = "0123456789abcdef".toCharArray(); + char[] hexChars = new char[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = hexArray[v >>> 4]; + hexChars[j * 2 + 1] = hexArray[v & 0x0F]; + } + return new String(hexChars); + } + + /** Export a subtree as a compact XML without namespaces. */ + public static void toSimpleXml(Node node, StringBuilder sb) throws RepositoryException { + sb.append('<'); + String nodeName = node.getName(); + int colIndex = nodeName.indexOf(':'); + if (colIndex > 0) { + nodeName = nodeName.substring(colIndex + 1); + } + sb.append(nodeName); + PropertyIterator pit = node.getProperties(); + properties: while (pit.hasNext()) { + Property p = pit.nextProperty(); + // skip multiple properties + if (p.isMultiple()) + continue properties; + String propertyName = p.getName(); + int pcolIndex = propertyName.indexOf(':'); + // skip properties with namespaces + if (pcolIndex > 0) + continue properties; + // skip binaries + if (p.getType() == PropertyType.BINARY) { + continue properties; + // TODO retrieve identifier? + } + sb.append(' '); + sb.append(propertyName); + sb.append('='); + sb.append('\"').append(p.getString()).append('\"'); + } + + if (node.hasNodes()) { + sb.append('>'); + NodeIterator children = node.getNodes(); + while (children.hasNext()) { + toSimpleXml(children.nextNode(), sb); + } + sb.append("'); + } else { + sb.append("/>"); + } + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxApi.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxApi.java new file mode 100644 index 000000000..666b2593e --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxApi.java @@ -0,0 +1,190 @@ +package org.argeo.jcr; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.RepositoryException; +import javax.jcr.Value; + +/** Uilities around the JCR extensions. */ +public class JcrxApi { + public final static String MD5 = "MD5"; + public final static String SHA1 = "SHA1"; + public final static String SHA256 = "SHA-256"; + public final static String SHA512 = "SHA-512"; + + public final static String EMPTY_MD5 = "d41d8cd98f00b204e9800998ecf8427e"; + public final static String EMPTY_SHA1 = "da39a3ee5e6b4b0d3255bfef95601890afd80709"; + public final static String EMPTY_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; + public final static String EMPTY_SHA512 = "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"; + + public final static int LENGTH_MD5 = EMPTY_MD5.length(); + public final static int LENGTH_SHA1 = EMPTY_SHA1.length(); + public final static int LENGTH_SHA256 = EMPTY_SHA256.length(); + public final static int LENGTH_SHA512 = EMPTY_SHA512.length(); + + /* + * XML + */ + /** + * Get the XML text of this child node. + */ + public static String getXmlValue(Node node, String name) { + try { + if (!node.hasNode(name)) + return null; + Node child = node.getNode(name); + return getXmlValue(child); + } catch (RepositoryException e) { + throw new IllegalStateException("Cannot get " + name + " as XML text", e); + } + } + + /** + * Get the XML text of this node. + */ + public static String getXmlValue(Node node) { + try { + if (!node.hasNode(Jcr.JCR_XMLTEXT)) + return null; + Node xmlText = node.getNode(Jcr.JCR_XMLTEXT); + if (!xmlText.hasProperty(Jcr.JCR_XMLCHARACTERS)) + throw new IllegalArgumentException( + "Node " + xmlText + " has no " + Jcr.JCR_XMLCHARACTERS + " property"); + return xmlText.getProperty(Jcr.JCR_XMLCHARACTERS).getString(); + } catch (RepositoryException e) { + throw new IllegalStateException("Cannot get " + node + " as XML text", e); + } + } + + /** + * Set as a subnode which will be exported as an XML element. + */ + public static void setXmlValue(Node node, String name, String value) { + try { + if (node.hasNode(name)) { + Node child = node.getNode(name); + setXmlValue(node, child, value); + } else + node.addNode(name, JcrxType.JCRX_XMLVALUE).addNode(Jcr.JCR_XMLTEXT, JcrxType.JCRX_XMLTEXT) + .setProperty(Jcr.JCR_XMLCHARACTERS, value); + } catch (RepositoryException e) { + throw new JcrException("Cannot set " + name + " as XML text", e); + } + } + + public static void setXmlValue(Node node, Node child, String value) { + try { + if (!child.hasNode(Jcr.JCR_XMLTEXT)) + child.addNode(Jcr.JCR_XMLTEXT, JcrxType.JCRX_XMLTEXT); + child.getNode(Jcr.JCR_XMLTEXT).setProperty(Jcr.JCR_XMLCHARACTERS, value); + } catch (RepositoryException e) { + throw new JcrException("Cannot set " + child + " as XML text", e); + } + } + + /** + * Add a checksum replacing the one which was previously set with the same + * length. + */ + public static void addChecksum(Node node, String checksum) { + try { + if (!node.hasProperty(JcrxName.JCRX_SUM)) { + node.setProperty(JcrxName.JCRX_SUM, new String[] { checksum }); + return; + } else { + int stringLength = checksum.length(); + Property property = node.getProperty(JcrxName.JCRX_SUM); + List values = Arrays.asList(property.getValues()); + Integer indexToRemove = null; + values: for (int i = 0; i < values.size(); i++) { + Value value = values.get(i); + if (value.getString().length() == stringLength) { + indexToRemove = i; + break values; + } + } + if (indexToRemove != null) + values.set(indexToRemove, node.getSession().getValueFactory().createValue(checksum)); + else + values.add(0, node.getSession().getValueFactory().createValue(checksum)); + property.setValue(values.toArray(new Value[values.size()])); + } + } catch (RepositoryException e) { + throw new JcrException("Cannot set checksum on " + node, e); + } + } + + /** Replace all checksums. */ + public static void setChecksums(Node node, List checksums) { + try { + node.setProperty(JcrxName.JCRX_SUM, checksums.toArray(new String[checksums.size()])); + } catch (RepositoryException e) { + throw new JcrException("Cannot set checksums on " + node, e); + } + } + + /** Replace all checksums. */ + public static List getChecksums(Node node) { + try { + List res = new ArrayList<>(); + if (!node.hasProperty(JcrxName.JCRX_SUM)) + return res; + Property property = node.getProperty(JcrxName.JCRX_SUM); + for (Value value : property.getValues()) { + res.add(value.getString()); + } + return res; + } catch (RepositoryException e) { + throw new JcrException("Cannot get checksums from " + node, e); + } + } + +// /** Replace all checksums with this single one. */ +// public static void setChecksum(Node node, String checksum) { +// setChecksums(node, Collections.singletonList(checksum)); +// } + + /** Retrieves the checksum with this algorithm, or null if not found. */ + public static String getChecksum(Node node, String algorithm) { + int stringLength; + switch (algorithm) { + case MD5: + stringLength = LENGTH_MD5; + break; + case SHA1: + stringLength = LENGTH_SHA1; + break; + case SHA256: + stringLength = LENGTH_SHA256; + break; + case SHA512: + stringLength = LENGTH_SHA512; + break; + default: + throw new IllegalArgumentException("Unkown algorithm " + algorithm); + } + return getChecksum(node, stringLength); + } + + /** Retrieves the checksum with this string length, or null if not found. */ + public static String getChecksum(Node node, int stringLength) { + try { + if (!node.hasProperty(JcrxName.JCRX_SUM)) + return null; + Property property = node.getProperty(JcrxName.JCRX_SUM); + for (Value value : property.getValues()) { + String str = value.getString(); + if (str.length() == stringLength) + return str; + } + return null; + } catch (RepositoryException e) { + throw new IllegalStateException("Cannot get checksum for " + node, e); + } + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxName.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxName.java new file mode 100644 index 000000000..9dd43adce --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxName.java @@ -0,0 +1,7 @@ +package org.argeo.jcr; + +/** Names declared by the JCR extensions. */ +public interface JcrxName { + /** The multiple property holding various coherent checksums. */ + public final static String JCRX_SUM = "{http://www.argeo.org/ns/jcrx}sum"; +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxType.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxType.java new file mode 100644 index 000000000..0cbad3341 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxType.java @@ -0,0 +1,17 @@ +package org.argeo.jcr; + +/** Node types declared by the JCR extensions. */ +public interface JcrxType { + /** + * Node type for an XML value, which will be serialized in XML as an element + * containing text. + */ + public final static String JCRX_XMLVALUE = "{http://www.argeo.org/ns/jcrx}xmlvalue"; + + /** Node type for the node containing the text. */ + public final static String JCRX_XMLTEXT = "{http://www.argeo.org/ns/jcrx}xmltext"; + + /** Mixin node type for a set of checksums. */ + public final static String JCRX_CSUM = "{http://www.argeo.org/ns/jcrx}csum"; + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/PropertyDiff.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/PropertyDiff.java new file mode 100644 index 000000000..71e76fe9b --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/PropertyDiff.java @@ -0,0 +1,57 @@ +package org.argeo.jcr; + +import javax.jcr.Value; + +/** The result of the comparison of two JCR properties. */ +public class PropertyDiff { + public final static Integer MODIFIED = 0; + public final static Integer ADDED = 1; + public final static Integer REMOVED = 2; + + private final Integer type; + private final String relPath; + private final Value referenceValue; + private final Value newValue; + + public PropertyDiff(Integer type, String relPath, Value referenceValue, Value newValue) { + super(); + + if (type == MODIFIED) { + if (referenceValue == null || newValue == null) + throw new IllegalArgumentException("Reference and new values must be specified."); + } else if (type == ADDED) { + if (referenceValue != null || newValue == null) + throw new IllegalArgumentException("New value and only it must be specified."); + } else if (type == REMOVED) { + if (referenceValue == null || newValue != null) + throw new IllegalArgumentException("Reference value and only it must be specified."); + } else { + throw new IllegalArgumentException("Unkown diff type " + type); + } + + if (relPath == null) + throw new IllegalArgumentException("Relative path must be specified"); + + this.type = type; + this.relPath = relPath; + this.referenceValue = referenceValue; + this.newValue = newValue; + } + + public Integer getType() { + return type; + } + + public String getRelPath() { + return relPath; + } + + public Value getReferenceValue() { + return referenceValue; + } + + public Value getNewValue() { + return newValue; + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/SimplePrincipal.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/SimplePrincipal.java new file mode 100644 index 000000000..4f42f2d9c --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/SimplePrincipal.java @@ -0,0 +1,43 @@ +package org.argeo.jcr; + +import java.security.Principal; + +/** Canonical implementation of a {@link Principal} */ +class SimplePrincipal implements Principal { + private final String name; + + public SimplePrincipal(String name) { + if (name == null) + throw new IllegalArgumentException("Principal name cannot be null"); + this.name = name; + } + + public String getName() { + return name; + } + + @Override + public int hashCode() { + return name.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) + return false; + if (obj instanceof Principal) + return name.equals((((Principal) obj).getName())); + return name.equals(obj.toString()); + } + + @Override + protected Object clone() throws CloneNotSupportedException { + return new SimplePrincipal(name); + } + + @Override + public String toString() { + return name; + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/ThreadBoundJcrSessionFactory.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/ThreadBoundJcrSessionFactory.java new file mode 100644 index 000000000..2208627ab --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/ThreadBoundJcrSessionFactory.java @@ -0,0 +1,279 @@ +package org.argeo.jcr; + +import java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.jcr.LoginException; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.SimpleCredentials; + +import org.argeo.api.cms.CmsLog; + +/** Proxy JCR sessions and attach them to calling threads. */ +@Deprecated +public abstract class ThreadBoundJcrSessionFactory { + private final static CmsLog log = CmsLog.getLog(ThreadBoundJcrSessionFactory.class); + + private Repository repository; + /** can be injected as list, only used if repository is null */ + private List repositories; + + private ThreadLocal session = new ThreadLocal(); + private final Session proxiedSession; + /** If workspace is null, default will be used. */ + private String workspace = null; + + private String defaultUsername = "demo"; + private String defaultPassword = "demo"; + private Boolean forceDefaultCredentials = false; + + private boolean active = true; + + // monitoring + private final List threads = Collections.synchronizedList(new ArrayList()); + private final Map activeSessions = Collections.synchronizedMap(new HashMap()); + private MonitoringThread monitoringThread; + + public ThreadBoundJcrSessionFactory() { + Class[] interfaces = { Session.class }; + proxiedSession = (Session) Proxy.newProxyInstance(ThreadBoundJcrSessionFactory.class.getClassLoader(), + interfaces, new JcrSessionInvocationHandler()); + } + + /** Logs in to the repository using various strategies. */ + protected synchronized Session login() { + if (!isActive()) + throw new IllegalStateException("Thread bound session factory inactive"); + + // discard session previously attached to this thread + Thread thread = Thread.currentThread(); + if (activeSessions.containsKey(thread.getId())) { + Session oldSession = activeSessions.remove(thread.getId()); + oldSession.logout(); + session.remove(); + } + + Session newSession = null; + // first try to login without credentials, assuming the underlying login + // module will have dealt with authentication (typically using Spring + // Security) + if (!forceDefaultCredentials) + try { + newSession = repository().login(workspace); + } catch (LoginException e1) { + log.warn("Cannot login without credentials: " + e1.getMessage()); + // invalid credentials, go to the next step + } catch (RepositoryException e1) { + // other kind of exception, fail + throw new JcrException("Cannot log in to repository", e1); + } + + // log using default username / password (useful for testing purposes) + if (newSession == null) + try { + SimpleCredentials sc = new SimpleCredentials(defaultUsername, defaultPassword.toCharArray()); + newSession = repository().login(sc, workspace); + } catch (RepositoryException e) { + throw new JcrException("Cannot log in to repository", e); + } + + session.set(newSession); + // Log and monitor new session + if (log.isTraceEnabled()) + log.trace("Logged in to JCR session " + newSession + "; userId=" + newSession.getUserID()); + + // monitoring + activeSessions.put(thread.getId(), newSession); + threads.add(thread); + return newSession; + } + + public Object getObject() { + return proxiedSession; + } + + public void init() throws Exception { + // log.error("SHOULD NOT BE USED ANYMORE"); + monitoringThread = new MonitoringThread(); + monitoringThread.start(); + } + + public void dispose() throws Exception { + // if (activeSessions.size() == 0) + // return; + + if (log.isTraceEnabled()) + log.trace("Cleaning up " + activeSessions.size() + " active JCR sessions..."); + + deactivate(); + for (Session sess : activeSessions.values()) { + JcrUtils.logoutQuietly(sess); + } + activeSessions.clear(); + } + + protected Boolean isActive() { + return active; + } + + protected synchronized void deactivate() { + active = false; + notifyAll(); + } + + protected synchronized void removeSession(Thread thread) { + if (!isActive()) + return; + activeSessions.remove(thread.getId()); + threads.remove(thread); + } + + protected synchronized void cleanDeadThreads() { + if (!isActive()) + return; + Iterator it = threads.iterator(); + while (it.hasNext()) { + Thread thread = it.next(); + if (!thread.isAlive() && isActive()) { + if (activeSessions.containsKey(thread.getId())) { + Session session = activeSessions.get(thread.getId()); + activeSessions.remove(thread.getId()); + session.logout(); + if (log.isTraceEnabled()) + log.trace("Cleaned up JCR session (userID=" + session.getUserID() + ") from dead thread " + + thread.getId()); + } + it.remove(); + } + } + try { + wait(1000); + } catch (InterruptedException e) { + // silent + } + } + + public Class getObjectType() { + return Session.class; + } + + public boolean isSingleton() { + return true; + } + + /** + * Called before a method is actually called, allowing to check the session or + * re-login it (e.g. if authentication has changed). The default implementation + * returns the session. + */ + protected Session preCall(Session session) { + return session; + } + + protected Repository repository() { + if (repository != null) + return repository; + if (repositories != null) { + // hardened for OSGi dynamic services + Iterator it = repositories.iterator(); + if (it.hasNext()) + return it.next(); + } + throw new IllegalStateException("No repository injected"); + } + + // /** Useful for declarative registration of OSGi services (blueprint) */ + // public void register(Repository repository, Map params) { + // this.repository = repository; + // } + // + // /** Useful for declarative registration of OSGi services (blueprint) */ + // public void unregister(Repository repository, Map params) { + // this.repository = null; + // } + + public void setRepository(Repository repository) { + this.repository = repository; + } + + public void setRepositories(List repositories) { + this.repositories = repositories; + } + + public void setDefaultUsername(String defaultUsername) { + this.defaultUsername = defaultUsername; + } + + public void setDefaultPassword(String defaultPassword) { + this.defaultPassword = defaultPassword; + } + + public void setForceDefaultCredentials(Boolean forceDefaultCredentials) { + this.forceDefaultCredentials = forceDefaultCredentials; + } + + public void setWorkspace(String workspace) { + this.workspace = workspace; + } + + protected class JcrSessionInvocationHandler implements InvocationHandler { + + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable, RepositoryException { + Session threadSession = session.get(); + if (threadSession == null) { + if ("logout".equals(method.getName()))// no need to login + return Void.TYPE; + else if ("toString".equals(method.getName()))// maybe logging + return "Uninitialized Argeo thread bound JCR session"; + threadSession = login(); + } + + preCall(threadSession); + Object ret; + try { + ret = method.invoke(threadSession, args); + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + if (cause instanceof RepositoryException) + throw (RepositoryException) cause; + else + throw cause; + } + if ("logout".equals(method.getName())) { + session.remove(); + Thread thread = Thread.currentThread(); + removeSession(thread); + if (log.isTraceEnabled()) + log.trace("Logged out JCR session (userId=" + threadSession.getUserID() + ") on thread " + + thread.getId()); + } + return ret; + } + } + + /** Monitors registered thread in order to clean up dead ones. */ + private class MonitoringThread extends Thread { + + public MonitoringThread() { + super("ThreadBound JCR Session Monitor"); + } + + @Override + public void run() { + while (isActive()) { + cleanDeadThreads(); + } + } + + } +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/VersionDiff.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/VersionDiff.java new file mode 100644 index 000000000..dab55548b --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/VersionDiff.java @@ -0,0 +1,38 @@ +package org.argeo.jcr; + +import java.util.Calendar; +import java.util.Map; + +/** + * Generic Object that enables the creation of history reports based on a JCR + * versionable node. userId and creation date are added to the map of + * PropertyDiff. + * + * These two fields might be null + * + */ +public class VersionDiff { + + private String userId; + private Map diffs; + private Calendar updateTime; + + public VersionDiff(String userId, Calendar updateTime, + Map diffs) { + this.userId = userId; + this.updateTime = updateTime; + this.diffs = diffs; + } + + public String getUserId() { + return userId; + } + + public Map getDiffs() { + return diffs; + } + + public Calendar getUpdateTime() { + return updateTime; + } +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/BinaryChannel.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/BinaryChannel.java new file mode 100644 index 000000000..d6550feee --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/BinaryChannel.java @@ -0,0 +1,190 @@ +package org.argeo.jcr.fs; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; +import java.nio.channels.Channels; +import java.nio.channels.FileChannel; +import java.nio.channels.ReadableByteChannel; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; + +import javax.jcr.Binary; +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.nodetype.NodeType; + +import org.argeo.jcr.JcrUtils; + +/** A read/write {@link SeekableByteChannel} based on a {@link Binary}. */ +public class BinaryChannel implements SeekableByteChannel { + private final Node file; + private Binary binary; + private boolean open = true; + + private long position = 0; + + private FileChannel fc = null; + + public BinaryChannel(Node file, Path path) throws RepositoryException, IOException { + this.file = file; + Session session = file.getSession(); + synchronized (session) { + if (file.isNodeType(NodeType.NT_FILE)) { + if (file.hasNode(Node.JCR_CONTENT)) { + Node data = file.getNode(Property.JCR_CONTENT); + this.binary = data.getProperty(Property.JCR_DATA).getBinary(); + } else { + Node data = file.addNode(Node.JCR_CONTENT, NodeType.NT_UNSTRUCTURED); + data.addMixin(NodeType.MIX_LAST_MODIFIED); + try (InputStream in = new ByteArrayInputStream(new byte[0])) { + this.binary = data.getSession().getValueFactory().createBinary(in); + } + data.setProperty(Property.JCR_DATA, this.binary); + + // MIME type + String mime = Files.probeContentType(path); + // String mime = fileTypeMap.getContentType(file.getName()); + data.setProperty(Property.JCR_MIMETYPE, mime); + + session.refresh(true); + session.save(); + session.notifyAll(); + } + } else { + throw new IllegalArgumentException( + "Unsupported file node " + file + " (" + file.getPrimaryNodeType() + ")"); + } + } + } + + @Override + public synchronized boolean isOpen() { + return open; + } + + @Override + public synchronized void close() throws IOException { + if (isModified()) { + Binary newBinary = null; + try { + Session session = file.getSession(); + synchronized (session) { + fc.position(0); + InputStream in = Channels.newInputStream(fc); + newBinary = session.getValueFactory().createBinary(in); + file.getNode(Property.JCR_CONTENT).setProperty(Property.JCR_DATA, newBinary); + session.refresh(true); + session.save(); + open = false; + session.notifyAll(); + } + } catch (RepositoryException e) { + throw new IOException("Cannot close " + file, e); + } finally { + JcrUtils.closeQuietly(newBinary); + // IOUtils.closeQuietly(fc); + if (fc != null) { + fc.close(); + } + } + } else { + clearReadState(); + open = false; + } + } + + @Override + public int read(ByteBuffer dst) throws IOException { + if (isModified()) { + return fc.read(dst); + } else { + + try { + int read; + byte[] arr = dst.array(); + read = binary.read(arr, position); + + if (read != -1) + position = position + read; + return read; + } catch (RepositoryException e) { + throw new IOException("Cannot read into buffer", e); + } + } + } + + @Override + public int write(ByteBuffer src) throws IOException { + int written = getFileChannel().write(src); + return written; + } + + @Override + public long position() throws IOException { + if (isModified()) + return getFileChannel().position(); + else + return position; + } + + @Override + public SeekableByteChannel position(long newPosition) throws IOException { + if (isModified()) { + getFileChannel().position(position); + } else { + this.position = newPosition; + } + return this; + } + + @Override + public long size() throws IOException { + if (isModified()) { + return getFileChannel().size(); + } else { + try { + return binary.getSize(); + } catch (RepositoryException e) { + throw new IOException("Cannot get size", e); + } + } + } + + @Override + public SeekableByteChannel truncate(long size) throws IOException { + getFileChannel().truncate(size); + return this; + } + + private FileChannel getFileChannel() throws IOException { + try { + if (fc == null) { + Path tempPath = Files.createTempFile(getClass().getSimpleName(), null); + fc = FileChannel.open(tempPath, StandardOpenOption.WRITE, StandardOpenOption.READ, + StandardOpenOption.DELETE_ON_CLOSE, StandardOpenOption.SPARSE); + ReadableByteChannel readChannel = Channels.newChannel(binary.getStream()); + fc.transferFrom(readChannel, 0, binary.getSize()); + clearReadState(); + } + return fc; + } catch (RepositoryException e) { + throw new IOException("Cannot get temp file channel", e); + } + } + + private boolean isModified() { + return fc != null; + } + + private void clearReadState() { + position = -1; + JcrUtils.closeQuietly(binary); + binary = null; + } +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrBasicfileAttributes.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrBasicfileAttributes.java new file mode 100644 index 000000000..7c9711bf0 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrBasicfileAttributes.java @@ -0,0 +1,138 @@ +package org.argeo.jcr.fs; + +import static javax.jcr.Property.JCR_CREATED; +import static javax.jcr.Property.JCR_LAST_MODIFIED; + +import java.nio.file.attribute.FileTime; +import java.time.Instant; + +import javax.jcr.Binary; +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.RepositoryException; +import javax.jcr.nodetype.NodeType; + +import org.argeo.jcr.JcrUtils; + +public class JcrBasicfileAttributes implements NodeFileAttributes { + private final Node node; + + private final static FileTime EPOCH = FileTime.fromMillis(0); + + public JcrBasicfileAttributes(Node node) { + if (node == null) + throw new JcrFsException("Node underlying the attributes cannot be null"); + this.node = node; + } + + @Override + public FileTime lastModifiedTime() { + try { + if (node.hasProperty(JCR_LAST_MODIFIED)) { + Instant instant = node.getProperty(JCR_LAST_MODIFIED).getDate().toInstant(); + return FileTime.from(instant); + } else if (node.hasProperty(JCR_CREATED)) { + Instant instant = node.getProperty(JCR_CREATED).getDate().toInstant(); + return FileTime.from(instant); + } +// if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) { +// Instant instant = node.getProperty(Property.JCR_LAST_MODIFIED).getDate().toInstant(); +// return FileTime.from(instant); +// } + return EPOCH; + } catch (RepositoryException e) { + throw new JcrFsException("Cannot get last modified time", e); + } + } + + @Override + public FileTime lastAccessTime() { + return lastModifiedTime(); + } + + @Override + public FileTime creationTime() { + try { + if (node.hasProperty(JCR_CREATED)) { + Instant instant = node.getProperty(JCR_CREATED).getDate().toInstant(); + return FileTime.from(instant); + } else if (node.hasProperty(JCR_LAST_MODIFIED)) { + Instant instant = node.getProperty(JCR_LAST_MODIFIED).getDate().toInstant(); + return FileTime.from(instant); + } +// if (node.isNodeType(NodeType.MIX_CREATED)) { +// Instant instant = node.getProperty(JCR_CREATED).getDate().toInstant(); +// return FileTime.from(instant); +// } + return EPOCH; + } catch (RepositoryException e) { + throw new JcrFsException("Cannot get creation time", e); + } + } + + @Override + public boolean isRegularFile() { + try { + return node.isNodeType(NodeType.NT_FILE); + } catch (RepositoryException e) { + throw new JcrFsException("Cannot check if regular file", e); + } + } + + @Override + public boolean isDirectory() { + try { + if (node.isNodeType(NodeType.NT_FOLDER)) + return true; + // all other non file nodes + return !(node.isNodeType(NodeType.NT_FILE) || node.isNodeType(NodeType.NT_LINKED_FILE)); + } catch (RepositoryException e) { + throw new JcrFsException("Cannot check if directory", e); + } + } + + @Override + public boolean isSymbolicLink() { + try { + return node.isNodeType(NodeType.NT_LINKED_FILE); + } catch (RepositoryException e) { + throw new JcrFsException("Cannot check if linked file", e); + } + } + + @Override + public boolean isOther() { + return !(isDirectory() || isRegularFile() || isSymbolicLink()); + } + + @Override + public long size() { + if (isRegularFile()) { + Binary binary = null; + try { + binary = node.getNode(Property.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary(); + return binary.getSize(); + } catch (RepositoryException e) { + throw new JcrFsException("Cannot check size", e); + } finally { + JcrUtils.closeQuietly(binary); + } + } + return -1; + } + + @Override + public Object fileKey() { + try { + return node.getIdentifier(); + } catch (RepositoryException e) { + throw new JcrFsException("Cannot get identifier", e); + } + } + + @Override + public Node getNode() { + return node; + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystem.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystem.java new file mode 100644 index 000000000..4b329810f --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystem.java @@ -0,0 +1,252 @@ +package org.argeo.jcr.fs; + +import java.io.IOException; +import java.nio.file.FileStore; +import java.nio.file.FileSystem; +import java.nio.file.Path; +import java.nio.file.PathMatcher; +import java.nio.file.WatchService; +import java.nio.file.attribute.UserPrincipalLookupService; +import java.nio.file.spi.FileSystemProvider; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; + +import javax.jcr.Credentials; +import javax.jcr.Node; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.nodetype.NodeType; + +import org.argeo.api.acr.fs.AbstractFsStore; +import org.argeo.api.acr.fs.AbstractFsSystem; +import org.argeo.jcr.Jcr; +import org.argeo.jcr.JcrUtils; + +public class JcrFileSystem extends AbstractFsSystem { + private final JcrFileSystemProvider provider; + + private final Repository repository; + private Session session; + private WorkspaceFileStore baseFileStore; + + private Map mounts = new TreeMap<>(); + + private String userHomePath = null; + + @Deprecated + public JcrFileSystem(JcrFileSystemProvider provider, Session session) throws IOException { + super(); + this.provider = provider; + baseFileStore = new WorkspaceFileStore(null, session.getWorkspace()); + this.session = session; +// Node userHome = provider.getUserHome(session); +// if (userHome != null) +// try { +// userHomePath = userHome.getPath(); +// } catch (RepositoryException e) { +// throw new IOException("Cannot retrieve user home path", e); +// } + this.repository = null; + } + + public JcrFileSystem(JcrFileSystemProvider provider, Repository repository) throws IOException { + this(provider, repository, null); + } + + public JcrFileSystem(JcrFileSystemProvider provider, Repository repository, Credentials credentials) + throws IOException { + super(); + this.provider = provider; + this.repository = repository; + try { + this.session = credentials == null ? repository.login() : repository.login(credentials); + baseFileStore = new WorkspaceFileStore(null, session.getWorkspace()); + workspaces: for (String workspaceName : baseFileStore.getWorkspace().getAccessibleWorkspaceNames()) { + if (workspaceName.equals(baseFileStore.getWorkspace().getName())) + continue workspaces;// do not mount base + if (workspaceName.equals("security")) { + continue workspaces;// do not mount security workspace + // TODO make it configurable + } + Session mountSession = credentials == null ? repository.login(workspaceName) + : repository.login(credentials, workspaceName); + String mountPath = JcrPath.separator + workspaceName; + mounts.put(mountPath, new WorkspaceFileStore(mountPath, mountSession.getWorkspace())); + } + } catch (RepositoryException e) { + throw new IOException("Cannot initialise file system", e); + } + + Node userHome = provider.getUserHome(repository); + if (userHome != null) + try { + userHomePath = toFsPath(userHome); + } catch (RepositoryException e) { + throw new IOException("Cannot retrieve user home path", e); + } finally { + JcrUtils.logoutQuietly(Jcr.session(userHome)); + } + } + + public String toFsPath(Node node) throws RepositoryException { + return getFileStore(node).toFsPath(node); + } + + /** Whether this node should be skipped in directory listings */ + public boolean skipNode(Node node) throws RepositoryException { + if (node.isNodeType(NodeType.NT_HIERARCHY_NODE)) + return false; + return true; + } + + public String getUserHomePath() { + return userHomePath; + } + + public WorkspaceFileStore getFileStore(String path) { + WorkspaceFileStore res = baseFileStore; + for (String mountPath : mounts.keySet()) { + if (path.equals(mountPath)) + return mounts.get(mountPath); + if (path.startsWith(mountPath + JcrPath.separator)) { + res = mounts.get(mountPath); + // we keep the last one + } + } + assert res != null; + return res; + } + + public WorkspaceFileStore getFileStore(Node node) throws RepositoryException { + String workspaceName = node.getSession().getWorkspace().getName(); + if (workspaceName.equals(baseFileStore.getWorkspace().getName())) + return baseFileStore; + for (String mountPath : mounts.keySet()) { + WorkspaceFileStore fileStore = mounts.get(mountPath); + if (workspaceName.equals(fileStore.getWorkspace().getName())) + return fileStore; + } + throw new IllegalStateException("No workspace mount found for " + node + " in workspace " + workspaceName); + } + + public Iterator listDirectMounts(Path base) { + String baseStr = base.toString(); + Set res = new HashSet<>(); + mounts: for (String mountPath : mounts.keySet()) { + if (mountPath.equals(baseStr)) + continue mounts; + if (mountPath.startsWith(baseStr)) { + JcrPath path = new JcrPath(this, mountPath); + Path relPath = base.relativize(path); + if (relPath.getNameCount() == 1) + res.add(path); + } + } + return res.iterator(); + } + + public WorkspaceFileStore getBaseFileStore() { + return baseFileStore; + } + + @Override + public FileSystemProvider provider() { + return provider; + } + + @Override + public void close() throws IOException { + JcrUtils.logoutQuietly(session); + for (String mountPath : mounts.keySet()) { + WorkspaceFileStore fileStore = mounts.get(mountPath); + try { + fileStore.close(); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + @Override + public boolean isOpen() { + return session.isLive(); + } + + @Override + public boolean isReadOnly() { + return false; + } + + @Override + public String getSeparator() { + return JcrPath.separator; + } + + @Override + public Iterable getRootDirectories() { + Set single = new HashSet<>(); + single.add(new JcrPath(this, JcrPath.separator)); + return single; + } + + @Override + public Iterable getFileStores() { + List stores = new ArrayList<>(); + stores.add(baseFileStore); + stores.addAll(mounts.values()); + return stores; + } + + @Override + public Set supportedFileAttributeViews() { + try { + String[] prefixes = session.getNamespacePrefixes(); + Set res = new HashSet<>(); + for (String prefix : prefixes) + res.add(prefix); + res.add("basic"); + return res; + } catch (RepositoryException e) { + throw new JcrFsException("Cannot get supported file attributes views", e); + } + } + + @Override + public Path getPath(String first, String... more) { + StringBuilder sb = new StringBuilder(first); + // TODO Make it more robust + for (String part : more) + sb.append('/').append(part); + return new JcrPath(this, sb.toString()); + } + + @Override + public PathMatcher getPathMatcher(String syntaxAndPattern) { + throw new UnsupportedOperationException(); + } + + @Override + public UserPrincipalLookupService getUserPrincipalLookupService() { + throw new UnsupportedOperationException(); + } + + @Override + public WatchService newWatchService() throws IOException { + throw new UnsupportedOperationException(); + } + +// public Session getSession() { +// return session; +// } + + public Repository getRepository() { + return repository; + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystemProvider.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystemProvider.java new file mode 100644 index 000000000..74d9a198e --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystemProvider.java @@ -0,0 +1,337 @@ +package org.argeo.jcr.fs; + +import java.io.IOException; +import java.nio.channels.SeekableByteChannel; +import java.nio.file.AccessMode; +import java.nio.file.CopyOption; +import java.nio.file.DirectoryNotEmptyException; +import java.nio.file.DirectoryStream; +import java.nio.file.DirectoryStream.Filter; +import java.nio.file.FileStore; +import java.nio.file.LinkOption; +import java.nio.file.NoSuchFileException; +import java.nio.file.OpenOption; +import java.nio.file.Path; +import java.nio.file.attribute.BasicFileAttributes; +import java.nio.file.attribute.FileAttribute; +import java.nio.file.attribute.FileAttributeView; +import java.nio.file.spi.FileSystemProvider; +import java.util.Calendar; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.PropertyIterator; +import javax.jcr.PropertyType; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.nodetype.NodeType; +import javax.jcr.nodetype.PropertyDefinition; + +import org.argeo.jcr.JcrUtils; + +/** Operations on a {@link JcrFileSystem}. */ +public abstract class JcrFileSystemProvider extends FileSystemProvider { + + @Override + public SeekableByteChannel newByteChannel(Path path, Set options, FileAttribute... attrs) + throws IOException { + Node node = toNode(path); + try { + if (node == null) { + Node parent = toNode(path.getParent()); + if (parent == null) + throw new IOException("No parent directory for " + path); + if (parent.getPrimaryNodeType().isNodeType(NodeType.NT_FILE) + || parent.getPrimaryNodeType().isNodeType(NodeType.NT_LINKED_FILE)) + throw new IOException(path + " parent is a file"); + + String fileName = path.getFileName().toString(); + fileName = Text.escapeIllegalJcrChars(fileName); + node = parent.addNode(fileName, NodeType.NT_FILE); + node.addMixin(NodeType.MIX_CREATED); +// node.addMixin(NodeType.MIX_LAST_MODIFIED); + } + if (!node.isNodeType(NodeType.NT_FILE)) + throw new UnsupportedOperationException(node + " must be a file"); + return new BinaryChannel(node, path); + } catch (RepositoryException e) { + discardChanges(node); + throw new IOException("Cannot read file", e); + } + } + + @Override + public DirectoryStream newDirectoryStream(Path dir, Filter filter) throws IOException { + try { + Node base = toNode(dir); + if (base == null) + throw new IOException(dir + " is not a JCR node"); + JcrFileSystem fileSystem = (JcrFileSystem) dir.getFileSystem(); + return new NodeDirectoryStream(fileSystem, base.getNodes(), fileSystem.listDirectMounts(dir), filter); + } catch (RepositoryException e) { + throw new IOException("Cannot list directory", e); + } + } + + @Override + public void createDirectory(Path dir, FileAttribute... attrs) throws IOException { + Node node = toNode(dir); + try { + if (node == null) { + Node parent = toNode(dir.getParent()); + if (parent == null) + throw new IOException("Parent of " + dir + " does not exist"); + Session session = parent.getSession(); + synchronized (session) { + if (parent.getPrimaryNodeType().isNodeType(NodeType.NT_FILE) + || parent.getPrimaryNodeType().isNodeType(NodeType.NT_LINKED_FILE)) + throw new IOException(dir + " parent is a file"); + String fileName = dir.getFileName().toString(); + fileName = Text.escapeIllegalJcrChars(fileName); + node = parent.addNode(fileName, NodeType.NT_FOLDER); + node.addMixin(NodeType.MIX_CREATED); + node.addMixin(NodeType.MIX_LAST_MODIFIED); + save(session); + } + } else { + // if (!node.getPrimaryNodeType().isNodeType(NodeType.NT_FOLDER)) + // throw new FileExistsException(dir + " exists and is not a directory"); + } + } catch (RepositoryException e) { + discardChanges(node); + throw new IOException("Cannot create directory " + dir, e); + } + } + + @Override + public void delete(Path path) throws IOException { + Node node = toNode(path); + try { + if (node == null) + throw new NoSuchFileException(path + " does not exist"); + Session session = node.getSession(); + synchronized (session) { + session.refresh(false); + if (node.getPrimaryNodeType().isNodeType(NodeType.NT_FILE)) + node.remove(); + else if (node.getPrimaryNodeType().isNodeType(NodeType.NT_FOLDER)) { + if (node.hasNodes())// TODO check only files + throw new DirectoryNotEmptyException(path.toString()); + node.remove(); + } + save(session); + } + } catch (RepositoryException e) { + discardChanges(node); + throw new IOException("Cannot delete " + path, e); + } + + } + + @Override + public void copy(Path source, Path target, CopyOption... options) throws IOException { + Node sourceNode = toNode(source); + Node targetNode = toNode(target); + try { + Session targetSession = targetNode.getSession(); + synchronized (targetSession) { + JcrUtils.copy(sourceNode, targetNode); + save(targetSession); + } + } catch (RepositoryException e) { + discardChanges(sourceNode); + discardChanges(targetNode); + throw new IOException("Cannot copy from " + source + " to " + target, e); + } + } + + @Override + public void move(Path source, Path target, CopyOption... options) throws IOException { + JcrFileSystem sourceFileSystem = (JcrFileSystem) source.getFileSystem(); + WorkspaceFileStore sourceStore = sourceFileSystem.getFileStore(source.toString()); + WorkspaceFileStore targetStore = sourceFileSystem.getFileStore(target.toString()); + try { + if (sourceStore.equals(targetStore)) { + sourceStore.getWorkspace().move(sourceStore.toJcrPath(source.toString()), + targetStore.toJcrPath(target.toString())); + } else { + // TODO implement it + throw new UnsupportedOperationException("Can only move paths within the same workspace."); + } + } catch (RepositoryException e) { + throw new IOException("Cannot move from " + source + " to " + target, e); + } + +// Node sourceNode = toNode(source); +// try { +// Session session = sourceNode.getSession(); +// synchronized (session) { +// session.move(sourceNode.getPath(), target.toString()); +// save(session); +// } +// } catch (RepositoryException e) { +// discardChanges(sourceNode); +// throw new IOException("Cannot move from " + source + " to " + target, e); +// } + } + + @Override + public boolean isSameFile(Path path, Path path2) throws IOException { + if (path.getFileSystem() != path2.getFileSystem()) + return false; + boolean equ = path.equals(path2); + if (equ) + return true; + else { + try { + Node node = toNode(path); + Node node2 = toNode(path2); + return node.isSame(node2); + } catch (RepositoryException e) { + throw new IOException("Cannot check whether " + path + " and " + path2 + " are same", e); + } + } + + } + + @Override + public boolean isHidden(Path path) throws IOException { + return path.getFileName().toString().charAt(0) == '.'; + } + + @Override + public FileStore getFileStore(Path path) throws IOException { + JcrFileSystem fileSystem = (JcrFileSystem) path.getFileSystem(); + return fileSystem.getFileStore(path.toString()); + } + + @Override + public void checkAccess(Path path, AccessMode... modes) throws IOException { + Node node = toNode(path); + if (node == null) + throw new NoSuchFileException(path + " does not exist"); + // TODO check access via JCR api + } + + @Override + public V getFileAttributeView(Path path, Class type, LinkOption... options) { + throw new UnsupportedOperationException(); + } + + @SuppressWarnings("unchecked") + @Override + public A readAttributes(Path path, Class type, LinkOption... options) + throws IOException { + // TODO check if assignable + Node node = toNode(path); + if (node == null) { + throw new IOException("JCR node not found for " + path); + } + return (A) new JcrBasicfileAttributes(node); + } + + @Override + public Map readAttributes(Path path, String attributes, LinkOption... options) throws IOException { + try { + Node node = toNode(path); + String pattern = attributes.replace(',', '|'); + Map res = new HashMap(); + PropertyIterator it = node.getProperties(pattern); + props: while (it.hasNext()) { + Property prop = it.nextProperty(); + PropertyDefinition pd = prop.getDefinition(); + if (pd.isMultiple()) + continue props; + int requiredType = pd.getRequiredType(); + switch (requiredType) { + case PropertyType.LONG: + res.put(prop.getName(), prop.getLong()); + break; + case PropertyType.DOUBLE: + res.put(prop.getName(), prop.getDouble()); + break; + case PropertyType.BOOLEAN: + res.put(prop.getName(), prop.getBoolean()); + break; + case PropertyType.DATE: + res.put(prop.getName(), prop.getDate()); + break; + case PropertyType.BINARY: + byte[] arr = JcrUtils.getBinaryAsBytes(prop); + res.put(prop.getName(), arr); + break; + default: + res.put(prop.getName(), prop.getString()); + } + } + return res; + } catch (RepositoryException e) { + throw new IOException("Cannot read attributes of " + path, e); + } + } + + @Override + public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException { + Node node = toNode(path); + try { + Session session = node.getSession(); + synchronized (session) { + if (value instanceof byte[]) { + JcrUtils.setBinaryAsBytes(node, attribute, (byte[]) value); + } else if (value instanceof Calendar) { + node.setProperty(attribute, (Calendar) value); + } else { + node.setProperty(attribute, value.toString()); + } + save(session); + } + } catch (RepositoryException e) { + discardChanges(node); + throw new IOException("Cannot set attribute " + attribute + " on " + path, e); + } + } + + protected Node toNode(Path path) { + try { + return ((JcrPath) path).getNode(); + } catch (RepositoryException e) { + throw new JcrFsException("Cannot convert path " + path + " to JCR Node", e); + } + } + + /** Discard changes in the underlying session */ + protected void discardChanges(Node node) { + if (node == null) + return; + try { + // discard changes + node.getSession().refresh(false); + } catch (RepositoryException e) { + e.printStackTrace(); + // TODO log out session? + // TODO use Commons logging? + } + } + + /** Make sure save is robust. */ + protected void save(Session session) throws RepositoryException { + session.refresh(true); + session.save(); + session.notifyAll(); + } + + /** + * To be overriden in order to support the ~ path, with an implementation + * specific concept of user home. + * + * @return null by default + */ + public Node getUserHome(Repository session) { + return null; + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFsException.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFsException.java new file mode 100644 index 000000000..f214fdc44 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFsException.java @@ -0,0 +1,15 @@ +package org.argeo.jcr.fs; + + +/** Exception related to the JCR FS */ +public class JcrFsException extends RuntimeException { + private static final long serialVersionUID = -7973896038244922980L; + + public JcrFsException(String message, Throwable e) { + super(message, e); + } + + public JcrFsException(String message) { + super(message); + } +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrPath.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrPath.java new file mode 100644 index 000000000..7318b7096 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrPath.java @@ -0,0 +1,384 @@ +package org.argeo.jcr.fs; + +import java.nio.file.Path; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.api.acr.fs.AbstractFsPath; + +/** A {@link Path} which contains a reference to a JCR {@link Node}. */ +public class JcrPath extends AbstractFsPath { + final static String separator = "/"; + final static char separatorChar = '/'; + +// private final JcrFileSystem fs; +// /** null for non absolute paths */ +// private final WorkspaceFileStore fileStore; +// private final String[] path;// null means root +// private final boolean absolute; +// +// // optim +// private final int hashCode; + + public JcrPath(JcrFileSystem filesSystem, String path) { + super(filesSystem, path); +// this.fs = filesSystem; +// if (path == null) +// throw new JcrFsException("Path cannot be null"); +// if (path.equals(separator)) {// root +// this.path = null; +// this.absolute = true; +// this.hashCode = 0; +// this.fileStore = fs.getBaseFileStore(); +// return; +// } else if (path.equals("")) {// empty path +// this.path = new String[] { "" }; +// this.absolute = false; +// this.fileStore = null; +// this.hashCode = "".hashCode(); +// return; +// } +// +// if (path.equals("~")) {// home +// path = filesSystem.getUserHomePath(); +// if (path == null) +// throw new JcrFsException("No home directory available"); +// } +// +// this.absolute = path.charAt(0) == separatorChar ? true : false; +// +// this.fileStore = absolute ? fs.getFileStore(path) : null; +// +// String trimmedPath = path.substring(absolute ? 1 : 0, +// path.charAt(path.length() - 1) == separatorChar ? path.length() - 1 : path.length()); +// this.path = trimmedPath.split(separator); +// for (int i = 0; i < this.path.length; i++) { +// this.path[i] = Text.unescapeIllegalJcrChars(this.path[i]); +// } +// this.hashCode = this.path[this.path.length - 1].hashCode(); +// assert !(absolute && fileStore == null); + } + + public JcrPath(JcrFileSystem filesSystem, Node node) throws RepositoryException { + this(filesSystem, filesSystem.getFileStore(node).toFsPath(node)); + } + + /** Internal optimisation */ + private JcrPath(JcrFileSystem filesSystem, WorkspaceFileStore fileStore, String[] path, boolean absolute) { + super(filesSystem, fileStore, path, absolute); +// this.fs = filesSystem; +// this.path = path; +// this.absolute = path == null ? true : absolute; +// if (this.absolute && fileStore == null) +// throw new IllegalArgumentException("Absolute path requires a file store"); +// if (!this.absolute && fileStore != null) +// throw new IllegalArgumentException("A file store should not be provided for a relative path"); +// this.fileStore = fileStore; +// this.hashCode = path == null ? 0 : path[path.length - 1].hashCode(); +// assert !(absolute && fileStore == null); + } + + protected String cleanUpSegment(String segment) { + return Text.unescapeIllegalJcrChars(segment); + } + + @Override + protected JcrPath newInstance(String path) { + return new JcrPath(getFileSystem(), path); + } + + @Override + protected JcrPath newInstance(String[] segments, boolean absolute) { + return new JcrPath(getFileSystem(), getFileStore(), segments, absolute); + + } + +// @Override +// public FileSystem getFileSystem() { +// return fs; +// } +// +// @Override +// public boolean isAbsolute() { +// return absolute; +// } +// +// @Override +// public Path getRoot() { +// if (path == null) +// return this; +// return new JcrPath(fs, separator); +// } +// +// @Override +// public String toString() { +// return toFsPath(path); +// } +// +// private String toFsPath(String[] path) { +// if (path == null) +// return "/"; +// StringBuilder sb = new StringBuilder(); +// if (isAbsolute()) +// sb.append('/'); +// for (int i = 0; i < path.length; i++) { +// if (i != 0) +// sb.append('/'); +// sb.append(path[i]); +// } +// return sb.toString(); +// } + +// @Deprecated +// private String toJcrPath() { +// return toJcrPath(path); +// } +// +// @Deprecated +// private String toJcrPath(String[] path) { +// if (path == null) +// return "/"; +// StringBuilder sb = new StringBuilder(); +// if (isAbsolute()) +// sb.append('/'); +// for (int i = 0; i < path.length; i++) { +// if (i != 0) +// sb.append('/'); +// sb.append(Text.escapeIllegalJcrChars(path[i])); +// } +// return sb.toString(); +// } + +// @Override +// public Path getFileName() { +// if (path == null) +// return null; +// return new JcrPath(fs, path[path.length - 1]); +// } +// +// @Override +// public Path getParent() { +// if (path == null) +// return null; +// if (path.length == 1)// root +// return new JcrPath(fs, separator); +// String[] parentPath = Arrays.copyOfRange(path, 0, path.length - 1); +// if (!absolute) +// return new JcrPath(fs, null, parentPath, absolute); +// else +// return new JcrPath(fs, toFsPath(parentPath)); +// } +// +// @Override +// public int getNameCount() { +// if (path == null) +// return 0; +// return path.length; +// } +// +// @Override +// public Path getName(int index) { +// if (path == null) +// return null; +// return new JcrPath(fs, path[index]); +// } +// +// @Override +// public Path subpath(int beginIndex, int endIndex) { +// if (path == null) +// return null; +// String[] parentPath = Arrays.copyOfRange(path, beginIndex, endIndex); +// return new JcrPath(fs, null, parentPath, false); +// } +// +// @Override +// public boolean startsWith(Path other) { +// return toString().startsWith(other.toString()); +// } +// +// @Override +// public boolean startsWith(String other) { +// return toString().startsWith(other); +// } +// +// @Override +// public boolean endsWith(Path other) { +// return toString().endsWith(other.toString()); +// } +// +// @Override +// public boolean endsWith(String other) { +// return toString().endsWith(other); +// } + +// @Override +// public Path normalize() { +// // always normalized +// return this; +// } + +// @Override +// public Path resolve(Path other) { +// JcrPath otherPath = (JcrPath) other; +// if (otherPath.isAbsolute()) +// return other; +// String[] newPath; +// if (path == null) { +// newPath = new String[otherPath.path.length]; +// System.arraycopy(otherPath.path, 0, newPath, 0, otherPath.path.length); +// } else { +// newPath = new String[path.length + otherPath.path.length]; +// System.arraycopy(path, 0, newPath, 0, path.length); +// System.arraycopy(otherPath.path, 0, newPath, path.length, otherPath.path.length); +// } +// if (!absolute) +// return new JcrPath(fs, null, newPath, absolute); +// else { +// return new JcrPath(fs, toFsPath(newPath)); +// } +// } +// +// @Override +// public final Path resolve(String other) { +// return resolve(getFileSystem().getPath(other)); +// } +// +// @Override +// public final Path resolveSibling(Path other) { +// if (other == null) +// throw new NullPointerException(); +// Path parent = getParent(); +// return (parent == null) ? other : parent.resolve(other); +// } +// +// @Override +// public final Path resolveSibling(String other) { +// return resolveSibling(getFileSystem().getPath(other)); +// } +// +// @Override +// public final Iterator iterator() { +// return new Iterator() { +// private int i = 0; +// +// @Override +// public boolean hasNext() { +// return (i < getNameCount()); +// } +// +// @Override +// public Path next() { +// if (i < getNameCount()) { +// Path result = getName(i); +// i++; +// return result; +// } else { +// throw new NoSuchElementException(); +// } +// } +// +// @Override +// public void remove() { +// throw new UnsupportedOperationException(); +// } +// }; +// } +// +// @Override +// public Path relativize(Path other) { +// if (equals(other)) +// return new JcrPath(fs, ""); +// if (other.startsWith(this)) { +// String p1 = toString(); +// String p2 = other.toString(); +// String relative = p2.substring(p1.length(), p2.length()); +// if (relative.charAt(0) == '/') +// relative = relative.substring(1); +// return new JcrPath(fs, relative); +// } +// throw new IllegalArgumentException(other + " cannot be relativized against " + this); +// } + +// @Override +// public URI toUri() { +// try { +// return new URI(fs.provider().getScheme(), toString(), null); +// } catch (URISyntaxException e) { +// throw new JcrFsException("Cannot create URI for " + toString(), e); +// } +// } +// +// @Override +// public Path toAbsolutePath() { +// if (isAbsolute()) +// return this; +// return new JcrPath(fs, fileStore, path, true); +// } +// +// @Override +// public Path toRealPath(LinkOption... options) throws IOException { +// return this; +// } +// +// @Override +// public File toFile() { +// throw new UnsupportedOperationException(); +// } + + public Node getNode() throws RepositoryException { + if (!isAbsolute())// TODO default dir + throw new JcrFsException("Cannot get a JCR node from a relative path"); + assert getFileStore() != null; + return getFileStore().toNode(getSegments()); +// String pathStr = toJcrPath(); +// Session session = fs.getSession(); +// // TODO synchronize on the session ? +// if (!session.itemExists(pathStr)) +// return null; +// return session.getNode(pathStr); + } +// +// @Override +// public boolean equals(Object obj) { +// if (!(obj instanceof JcrPath)) +// return false; +// JcrPath other = (JcrPath) obj; +// +// if (path == null) {// root +// if (other.path == null)// root +// return true; +// else +// return false; +// } else { +// if (other.path == null)// root +// return false; +// } +// // non root +// if (path.length != other.path.length) +// return false; +// for (int i = 0; i < path.length; i++) { +// if (!path[i].equals(other.path[i])) +// return false; +// } +// return true; +// } + +// @Override +// public int hashCode() { +// return hashCode; +// } + +// @Override +// protected Object clone() throws CloneNotSupportedException { +// return new JcrPath(fs, toString()); +// } + +// @Override +// protected void finalize() throws Throwable { +// Arrays.fill(path, null); +// } + + + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/NodeDirectoryStream.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/NodeDirectoryStream.java new file mode 100644 index 000000000..eda07a548 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/NodeDirectoryStream.java @@ -0,0 +1,77 @@ +package org.argeo.jcr.fs; + +import java.io.IOException; +import java.nio.file.DirectoryStream; +import java.nio.file.Path; +import java.util.Iterator; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; + +public class NodeDirectoryStream implements DirectoryStream { + private final JcrFileSystem fs; + private final NodeIterator nodeIterator; + private final Iterator additionalPaths; + private final Filter filter; + + public NodeDirectoryStream(JcrFileSystem fs, NodeIterator nodeIterator, Iterator additionalPaths, + Filter filter) { + this.fs = fs; + this.nodeIterator = nodeIterator; + this.additionalPaths = additionalPaths; + this.filter = filter; + } + + @Override + public void close() throws IOException { + } + + @Override + public Iterator iterator() { + return new Iterator() { + private JcrPath next = null; + + @Override + public synchronized boolean hasNext() { + if (next != null) + return true; + nodes: while (nodeIterator.hasNext()) { + try { + Node node = nodeIterator.nextNode(); + String nodeName = node.getName(); + if (nodeName.startsWith("rep:") || nodeName.startsWith("jcr:")) + continue nodes; + if (fs.skipNode(node)) + continue nodes; + next = new JcrPath(fs, node); + if (filter != null) { + if (filter.accept(next)) + break nodes; + } else + break nodes; + } catch (Exception e) { + throw new JcrFsException("Could not get next path", e); + } + } + + if (next == null) { + if (additionalPaths.hasNext()) + next = additionalPaths.next(); + } + + return next != null; + } + + @Override + public synchronized Path next() { + if (!hasNext())// make sure has next has been called + return null; + JcrPath res = next; + next = null; + return res; + } + + }; + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/NodeFileAttributes.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/NodeFileAttributes.java new file mode 100644 index 000000000..8054d52f8 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/NodeFileAttributes.java @@ -0,0 +1,9 @@ +package org.argeo.jcr.fs; + +import java.nio.file.attribute.BasicFileAttributes; + +import javax.jcr.Node; + +public interface NodeFileAttributes extends BasicFileAttributes { + public Node getNode(); +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/Text.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/Text.java new file mode 100644 index 000000000..4643c8c9c --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/Text.java @@ -0,0 +1,877 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.jcr.fs; + +import java.io.ByteArrayOutputStream; +import java.io.UnsupportedEncodingException; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.ArrayList; +import java.util.BitSet; +import java.util.Properties; + +/** + * Hacked from org.apache.jackrabbit.util.Text in Jackrabbit JCR Commons + * This Class provides some text related utilities + */ +class Text { + + /** + * Hidden constructor. + */ + private Text() { + } + + /** + * used for the md5 + */ + public static final char[] hexTable = "0123456789abcdef".toCharArray(); + + /** + * Calculate an MD5 hash of the string given. + * + * @param data + * the data to encode + * @param enc + * the character encoding to use + * @return a hex encoded string of the md5 digested input + */ + public static String md5(String data, String enc) throws UnsupportedEncodingException { + try { + return digest("MD5", data.getBytes(enc)); + } catch (NoSuchAlgorithmException e) { + throw new InternalError("MD5 digest not available???"); + } + } + + /** + * Calculate an MD5 hash of the string given using 'utf-8' encoding. + * + * @param data + * the data to encode + * @return a hex encoded string of the md5 digested input + */ + public static String md5(String data) { + try { + return md5(data, "utf-8"); + } catch (UnsupportedEncodingException e) { + throw new InternalError("UTF8 digest not available???"); + } + } + + /** + * Digest the plain string using the given algorithm. + * + * @param algorithm + * The alogrithm for the digest. This algorithm must be supported + * by the MessageDigest class. + * @param data + * The plain text String to be digested. + * @param enc + * The character encoding to use + * @return The digested plain text String represented as Hex digits. + * @throws java.security.NoSuchAlgorithmException + * if the desired algorithm is not supported by the + * MessageDigest class. + * @throws java.io.UnsupportedEncodingException + * if the encoding is not supported + */ + public static String digest(String algorithm, String data, String enc) + throws NoSuchAlgorithmException, UnsupportedEncodingException { + + return digest(algorithm, data.getBytes(enc)); + } + + /** + * Digest the plain string using the given algorithm. + * + * @param algorithm + * The algorithm for the digest. This algorithm must be supported + * by the MessageDigest class. + * @param data + * the data to digest with the given algorithm + * @return The digested plain text String represented as Hex digits. + * @throws java.security.NoSuchAlgorithmException + * if the desired algorithm is not supported by the + * MessageDigest class. + */ + public static String digest(String algorithm, byte[] data) throws NoSuchAlgorithmException { + + MessageDigest md = MessageDigest.getInstance(algorithm); + byte[] digest = md.digest(data); + StringBuilder res = new StringBuilder(digest.length * 2); + for (byte b : digest) { + res.append(hexTable[(b >> 4) & 15]); + res.append(hexTable[b & 15]); + } + return res.toString(); + } + + /** + * returns an array of strings decomposed of the original string, split at + * every occurrence of 'ch'. if 2 'ch' follow each other with no + * intermediate characters, empty "" entries are avoided. + * + * @param str + * the string to decompose + * @param ch + * the character to use a split pattern + * @return an array of strings + */ + public static String[] explode(String str, int ch) { + return explode(str, ch, false); + } + + /** + * returns an array of strings decomposed of the original string, split at + * every occurrence of 'ch'. + * + * @param str + * the string to decompose + * @param ch + * the character to use a split pattern + * @param respectEmpty + * if true, empty elements are generated + * @return an array of strings + */ + public static String[] explode(String str, int ch, boolean respectEmpty) { + if (str == null || str.length() == 0) { + return new String[0]; + } + + ArrayList strings = new ArrayList(); + int pos; + int lastpos = 0; + + // add snipples + while ((pos = str.indexOf(ch, lastpos)) >= 0) { + if (pos - lastpos > 0 || respectEmpty) { + strings.add(str.substring(lastpos, pos)); + } + lastpos = pos + 1; + } + // add rest + if (lastpos < str.length()) { + strings.add(str.substring(lastpos)); + } else if (respectEmpty && lastpos == str.length()) { + strings.add(""); + } + + // return string array + return strings.toArray(new String[strings.size()]); + } + + /** + * Concatenates all strings in the string array using the specified + * delimiter. + * + * @param arr + * @param delim + * @return the concatenated string + */ + public static String implode(String[] arr, String delim) { + StringBuilder buf = new StringBuilder(); + for (int i = 0; i < arr.length; i++) { + if (i > 0) { + buf.append(delim); + } + buf.append(arr[i]); + } + return buf.toString(); + } + + /** + * Replaces all occurrences of oldString in text + * with newString. + * + * @param text + * @param oldString + * old substring to be replaced with newString + * @param newString + * new substring to replace occurrences of oldString + * @return a string + */ + public static String replace(String text, String oldString, String newString) { + if (text == null || oldString == null || newString == null) { + throw new IllegalArgumentException("null argument"); + } + int pos = text.indexOf(oldString); + if (pos == -1) { + return text; + } + int lastPos = 0; + StringBuilder sb = new StringBuilder(text.length()); + while (pos != -1) { + sb.append(text.substring(lastPos, pos)); + sb.append(newString); + lastPos = pos + oldString.length(); + pos = text.indexOf(oldString, lastPos); + } + if (lastPos < text.length()) { + sb.append(text.substring(lastPos)); + } + return sb.toString(); + } + + /** + * Replaces XML characters in the given string that might need escaping as + * XML text or attribute + * + * @param text + * text to be escaped + * @return a string + */ + public static String encodeIllegalXMLCharacters(String text) { + return encodeMarkupCharacters(text, false); + } + + /** + * Replaces HTML characters in the given string that might need escaping as + * HTML text or attribute + * + * @param text + * text to be escaped + * @return a string + */ + public static String encodeIllegalHTMLCharacters(String text) { + return encodeMarkupCharacters(text, true); + } + + private static String encodeMarkupCharacters(String text, boolean isHtml) { + if (text == null) { + throw new IllegalArgumentException("null argument"); + } + StringBuilder buf = null; + int length = text.length(); + int pos = 0; + for (int i = 0; i < length; i++) { + int ch = text.charAt(i); + switch (ch) { + case '<': + case '>': + case '&': + case '"': + case '\'': + if (buf == null) { + buf = new StringBuilder(); + } + if (i > 0) { + buf.append(text.substring(pos, i)); + } + pos = i + 1; + break; + default: + continue; + } + if (ch == '<') { + buf.append("<"); + } else if (ch == '>') { + buf.append(">"); + } else if (ch == '&') { + buf.append("&"); + } else if (ch == '"') { + buf.append("""); + } else if (ch == '\'') { + buf.append(isHtml ? "'" : "'"); + } + } + if (buf == null) { + return text; + } else { + if (pos < length) { + buf.append(text.substring(pos)); + } + return buf.toString(); + } + } + + /** + * The list of characters that are not encoded by the escape() + * and unescape() METHODS. They contains the characters as + * defined 'unreserved' in section 2.3 of the RFC 2396 'URI generic syntax': + *

+ * + *

+	 * unreserved  = alphanum | mark
+	 * mark        = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
+	 * 
+ */ + public static BitSet URISave; + + /** + * Same as {@link #URISave} but also contains the '/' + */ + public static BitSet URISaveEx; + + static { + URISave = new BitSet(256); + int i; + for (i = 'a'; i <= 'z'; i++) { + URISave.set(i); + } + for (i = 'A'; i <= 'Z'; i++) { + URISave.set(i); + } + for (i = '0'; i <= '9'; i++) { + URISave.set(i); + } + URISave.set('-'); + URISave.set('_'); + URISave.set('.'); + URISave.set('!'); + URISave.set('~'); + URISave.set('*'); + URISave.set('\''); + URISave.set('('); + URISave.set(')'); + + URISaveEx = (BitSet) URISave.clone(); + URISaveEx.set('/'); + } + + /** + * Does an URL encoding of the string using the + * escape character. The characters that don't need encoding + * are those defined 'unreserved' in section 2.3 of the 'URI generic syntax' + * RFC 2396, but without the escape character. + * + * @param string + * the string to encode. + * @param escape + * the escape character. + * @return the escaped string + * @throws NullPointerException + * if string is null. + */ + public static String escape(String string, char escape) { + return escape(string, escape, false); + } + + /** + * Does an URL encoding of the string using the + * escape character. The characters that don't need encoding + * are those defined 'unreserved' in section 2.3 of the 'URI generic syntax' + * RFC 2396, but without the escape character. If isPath is + * true, additionally the slash '/' is ignored, too. + * + * @param string + * the string to encode. + * @param escape + * the escape character. + * @param isPath + * if true, the string is treated as path + * @return the escaped string + * @throws NullPointerException + * if string is null. + */ + public static String escape(String string, char escape, boolean isPath) { + try { + BitSet validChars = isPath ? URISaveEx : URISave; + byte[] bytes = string.getBytes("utf-8"); + StringBuilder out = new StringBuilder(bytes.length); + for (byte aByte : bytes) { + int c = aByte & 0xff; + if (validChars.get(c) && c != escape) { + out.append((char) c); + } else { + out.append(escape); + out.append(hexTable[(c >> 4) & 0x0f]); + out.append(hexTable[(c) & 0x0f]); + } + } + return out.toString(); + } catch (UnsupportedEncodingException e) { + throw new InternalError(e.toString()); + } + } + + /** + * Does a URL encoding of the string. The characters that don't + * need encoding are those defined 'unreserved' in section 2.3 of the 'URI + * generic syntax' RFC 2396. + * + * @param string + * the string to encode + * @return the escaped string + * @throws NullPointerException + * if string is null. + */ + public static String escape(String string) { + return escape(string, '%'); + } + + /** + * Does a URL encoding of the path. The characters that don't + * need encoding are those defined 'unreserved' in section 2.3 of the 'URI + * generic syntax' RFC 2396. In contrast to the {@link #escape(String)} + * method, not the entire path string is escaped, but every individual part + * (i.e. the slashes are not escaped). + * + * @param path + * the path to encode + * @return the escaped path + * @throws NullPointerException + * if path is null. + */ + public static String escapePath(String path) { + return escape(path, '%', true); + } + + /** + * Does a URL decoding of the string using the + * escape character. Please note that in opposite to the + * {@link java.net.URLDecoder} it does not transform the + into spaces. + * + * @param string + * the string to decode + * @param escape + * the escape character + * @return the decoded string + * @throws NullPointerException + * if string is null. + * @throws IllegalArgumentException + * if the 2 characters following the escape character do not + * represent a hex-number or if not enough characters follow an + * escape character + */ + public static String unescape(String string, char escape) { + try { + byte[] utf8 = string.getBytes("utf-8"); + + // Check whether escape occurs at invalid position + if ((utf8.length >= 1 && utf8[utf8.length - 1] == escape) + || (utf8.length >= 2 && utf8[utf8.length - 2] == escape)) { + throw new IllegalArgumentException("Premature end of escape sequence at end of input"); + } + + ByteArrayOutputStream out = new ByteArrayOutputStream(utf8.length); + for (int k = 0; k < utf8.length; k++) { + byte b = utf8[k]; + if (b == escape) { + out.write((decodeDigit(utf8[++k]) << 4) + decodeDigit(utf8[++k])); + } else { + out.write(b); + } + } + + return new String(out.toByteArray(), "utf-8"); + } catch (UnsupportedEncodingException e) { + throw new InternalError(e.toString()); + } + } + + /** + * Does a URL decoding of the string. Please note that in + * opposite to the {@link java.net.URLDecoder} it does not transform the + + * into spaces. + * + * @param string + * the string to decode + * @return the decoded string + * @throws NullPointerException + * if string is null. + * @throws ArrayIndexOutOfBoundsException + * if not enough character follow an escape character + * @throws IllegalArgumentException + * if the 2 characters following the escape character do not + * represent a hex-number. + */ + public static String unescape(String string) { + return unescape(string, '%'); + } + + /** + * Escapes all illegal JCR name characters of a string. The encoding is + * loosely modeled after URI encoding, but only encodes the characters it + * absolutely needs to in order to make the resulting string a valid JCR + * name. Use {@link #unescapeIllegalJcrChars(String)} for decoding. + *

+ * QName EBNF:
+ *

simplename ::= onecharsimplename | twocharsimplename | + * threeormorecharname onecharsimplename ::= (* Any Unicode character + * except: '.', '/', ':', '[', ']', '*', '|' or any whitespace character *) + * twocharsimplename ::= '.' onecharsimplename | onecharsimplename '.' | + * onecharsimplename onecharsimplename threeormorecharname ::= nonspace + * string nonspace string ::= char | string char char ::= nonspace | ' ' + * nonspace ::= (* Any Unicode character except: '/', ':', '[', ']', '*', + * '|' or any whitespace character *) + * + * @param name + * the name to escape + * @return the escaped name + */ + public static String escapeIllegalJcrChars(String name) { + return escapeIllegalChars(name, "%/:[]*|\t\r\n"); + } + + /** + * Escapes all illegal JCR 1.0 name characters of a string. Use + * {@link #unescapeIllegalJcrChars(String)} for decoding. + *

+ * QName EBNF:
+ *

simplename ::= onecharsimplename | twocharsimplename | + * threeormorecharname onecharsimplename ::= (* Any Unicode character + * except: '.', '/', ':', '[', ']', '*', ''', '"', '|' or any whitespace + * character *) twocharsimplename ::= '.' onecharsimplename | + * onecharsimplename '.' | onecharsimplename onecharsimplename + * threeormorecharname ::= nonspace string nonspace string ::= char | string + * char char ::= nonspace | ' ' nonspace ::= (* Any Unicode character + * except: '/', ':', '[', ']', '*', ''', '"', '|' or any whitespace + * character *) + * + * @since Apache Jackrabbit 2.3.2 and 2.2.10 + * @see
JCR-3128 + * @param name + * the name to escape + * @return the escaped name + */ + public static String escapeIllegalJcr10Chars(String name) { + return escapeIllegalChars(name, "%/:[]*'\"|\t\r\n"); + } + + private static String escapeIllegalChars(String name, String illegal) { + StringBuilder buffer = new StringBuilder(name.length() * 2); + for (int i = 0; i < name.length(); i++) { + char ch = name.charAt(i); + if (illegal.indexOf(ch) != -1 || (ch == '.' && name.length() < 3) + || (ch == ' ' && (i == 0 || i == name.length() - 1))) { + buffer.append('%'); + buffer.append(Character.toUpperCase(Character.forDigit(ch / 16, 16))); + buffer.append(Character.toUpperCase(Character.forDigit(ch % 16, 16))); + } else { + buffer.append(ch); + } + } + return buffer.toString(); + } + + /** + * Escapes illegal XPath search characters at the end of a string. + *

+ * Example:
+ * A search string like 'test?' will run into a ParseException documented in + * http://issues.apache.org/jira/browse/JCR-1248 + * + * @param s + * the string to encode + * @return the escaped string + */ + public static String escapeIllegalXpathSearchChars(String s) { + StringBuilder sb = new StringBuilder(); + sb.append(s.substring(0, (s.length() - 1))); + char c = s.charAt(s.length() - 1); + // NOTE: keep this in sync with _ESCAPED_CHAR below! + if (c == '!' || c == '(' || c == ':' || c == '^' || c == '[' || c == ']' || c == '{' || c == '}' || c == '?') { + sb.append('\\'); + } + sb.append(c); + return sb.toString(); + } + + /** + * Unescapes previously escaped jcr chars. + *

+ * Please note, that this does not exactly the same as the url related + * {@link #unescape(String)}, since it handles the byte-encoding + * differently. + * + * @param name + * the name to unescape + * @return the unescaped name + */ + public static String unescapeIllegalJcrChars(String name) { + StringBuilder buffer = new StringBuilder(name.length()); + int i = name.indexOf('%'); + while (i > -1 && i + 2 < name.length()) { + buffer.append(name.toCharArray(), 0, i); + int a = Character.digit(name.charAt(i + 1), 16); + int b = Character.digit(name.charAt(i + 2), 16); + if (a > -1 && b > -1) { + buffer.append((char) (a * 16 + b)); + name = name.substring(i + 3); + } else { + buffer.append('%'); + name = name.substring(i + 1); + } + i = name.indexOf('%'); + } + buffer.append(name); + return buffer.toString(); + } + + /** + * Returns the name part of the path. If the given path is already a name + * (i.e. contains no slashes) it is returned. + * + * @param path + * the path + * @return the name part or null if path is + * null. + */ + public static String getName(String path) { + return getName(path, '/'); + } + + /** + * Returns the name part of the path, delimited by the given + * delim. If the given path is already a name (i.e. contains no + * delim characters) it is returned. + * + * @param path + * the path + * @param delim + * the delimiter + * @return the name part or null if path is + * null. + */ + public static String getName(String path, char delim) { + return path == null ? null : path.substring(path.lastIndexOf(delim) + 1); + } + + /** + * Same as {@link #getName(String)} but adding the possibility to pass paths + * that end with a trailing '/' + * + * @see #getName(String) + */ + public static String getName(String path, boolean ignoreTrailingSlash) { + if (ignoreTrailingSlash && path != null && path.endsWith("/") && path.length() > 1) { + path = path.substring(0, path.length() - 1); + } + return getName(path); + } + + /** + * Returns the namespace prefix of the given qname. If the + * prefix is missing, an empty string is returned. Please note, that this + * method does not validate the name or prefix. + *

+ * the qname has the format: qname := [prefix ':'] local; + * + * @param qname + * a qualified name + * @return the prefix of the name or "". + * + * @see #getLocalName(String) + * + * @throws NullPointerException + * if qname is null + */ + public static String getNamespacePrefix(String qname) { + int pos = qname.indexOf(':'); + return pos >= 0 ? qname.substring(0, pos) : ""; + } + + /** + * Returns the local name of the given qname. Please note, that + * this method does not validate the name. + *

+ * the qname has the format: qname := [prefix ':'] local; + * + * @param qname + * a qualified name + * @return the localname + * + * @see #getNamespacePrefix(String) + * + * @throws NullPointerException + * if qname is null + */ + public static String getLocalName(String qname) { + int pos = qname.indexOf(':'); + return pos >= 0 ? qname.substring(pos + 1) : qname; + } + + /** + * Determines, if two paths denote hierarchical siblins. + * + * @param p1 + * first path + * @param p2 + * second path + * @return true if on same level, false otherwise + */ + public static boolean isSibling(String p1, String p2) { + int pos1 = p1.lastIndexOf('/'); + int pos2 = p2.lastIndexOf('/'); + return (pos1 == pos2 && pos1 >= 0 && p1.regionMatches(0, p2, 0, pos1)); + } + + /** + * Determines if the descendant path is hierarchical a + * descendant of path. + * + * @param path + * the current path + * @param descendant + * the potential descendant + * @return true if the descendant is a descendant; + * false otherwise. + */ + public static boolean isDescendant(String path, String descendant) { + String pattern = path.endsWith("/") ? path : path + "/"; + return !pattern.equals(descendant) && descendant.startsWith(pattern); + } + + /** + * Determines if the descendant path is hierarchical a + * descendant of path or equal to it. + * + * @param path + * the path to check + * @param descendant + * the potential descendant + * @return true if the descendant is a descendant + * or equal; false otherwise. + */ + public static boolean isDescendantOrEqual(String path, String descendant) { + if (path.equals(descendant)) { + return true; + } else { + String pattern = path.endsWith("/") ? path : path + "/"; + return descendant.startsWith(pattern); + } + } + + /** + * Returns the nth relative parent of the path, where n=level. + *

+ * Example:
+ * + * Text.getRelativeParent("/foo/bar/test", 1) == "/foo/bar" + * + * + * @param path + * the path of the page + * @param level + * the level of the parent + */ + public static String getRelativeParent(String path, int level) { + int idx = path.length(); + while (level > 0) { + idx = path.lastIndexOf('/', idx - 1); + if (idx < 0) { + return ""; + } + level--; + } + return (idx == 0) ? "/" : path.substring(0, idx); + } + + /** + * Same as {@link #getRelativeParent(String, int)} but adding the + * possibility to pass paths that end with a trailing '/' + * + * @see #getRelativeParent(String, int) + */ + public static String getRelativeParent(String path, int level, boolean ignoreTrailingSlash) { + if (ignoreTrailingSlash && path.endsWith("/") && path.length() > 1) { + path = path.substring(0, path.length() - 1); + } + return getRelativeParent(path, level); + } + + /** + * Returns the nth absolute parent of the path, where n=level. + *

+ * Example:
+ * + * Text.getAbsoluteParent("/foo/bar/test", 1) == "/foo/bar" + * + * + * @param path + * the path of the page + * @param level + * the level of the parent + */ + public static String getAbsoluteParent(String path, int level) { + int idx = 0; + int len = path.length(); + while (level >= 0 && idx < len) { + idx = path.indexOf('/', idx + 1); + if (idx < 0) { + idx = len; + } + level--; + } + return level >= 0 ? "" : path.substring(0, idx); + } + + /** + * Performs variable replacement on the given string value. Each + * ${...} sequence within the given value is replaced with the + * value of the named parser variable. If a variable is not found in the + * properties an IllegalArgumentException is thrown unless + * ignoreMissing is true. In the later case, the + * missing variable is replaced by the empty string. + * + * @param value + * the original value + * @param ignoreMissing + * if true, missing variables are replaced by the + * empty string. + * @return value after variable replacements + * @throws IllegalArgumentException + * if the replacement of a referenced variable is not found + */ + public static String replaceVariables(Properties variables, String value, boolean ignoreMissing) + throws IllegalArgumentException { + StringBuilder result = new StringBuilder(); + + // Value: + // +--+-+--------+-+-----------------+ + // | |p|--> |q|--> | + // +--+-+--------+-+-----------------+ + int p = 0, q = value.indexOf("${"); // Find first ${ + while (q != -1) { + result.append(value.substring(p, q)); // Text before ${ + p = q; + q = value.indexOf("}", q + 2); // Find } + if (q != -1) { + String variable = value.substring(p + 2, q); + String replacement = variables.getProperty(variable); + if (replacement == null) { + if (ignoreMissing) { + replacement = ""; + } else { + throw new IllegalArgumentException("Replacement not found for ${" + variable + "}."); + } + } + result.append(replacement); + p = q + 1; + q = value.indexOf("${", p); // Find next ${ + } + } + result.append(value.substring(p, value.length())); // Trailing text + + return result.toString(); + } + + private static byte decodeDigit(byte b) { + if (b >= 0x30 && b <= 0x39) { + return (byte) (b - 0x30); + } else if (b >= 0x41 && b <= 0x46) { + return (byte) (b - 0x37); + } else if (b >= 0x61 && b <= 0x66) { + return (byte) (b - 0x57); + } else { + throw new IllegalArgumentException("Escape sequence is not hexadecimal: " + (char) b); + } + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/WorkspaceFileStore.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/WorkspaceFileStore.java new file mode 100644 index 000000000..ce4205a97 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/WorkspaceFileStore.java @@ -0,0 +1,192 @@ +package org.argeo.jcr.fs; + +import java.io.IOException; +import java.nio.file.FileStore; +import java.nio.file.attribute.FileAttributeView; +import java.nio.file.attribute.FileStoreAttributeView; +import java.util.Arrays; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.Workspace; + +import org.argeo.api.acr.fs.AbstractFsStore; +import org.argeo.jcr.JcrUtils; + +/** A {@link FileStore} implementation based on JCR {@link Workspace}. */ +public class WorkspaceFileStore extends AbstractFsStore { + private final String mountPath; + private final Workspace workspace; + private final String workspaceName; + private final int mountDepth; + + public WorkspaceFileStore(String mountPath, Workspace workspace) { + if ("/".equals(mountPath) || "".equals(mountPath)) + throw new IllegalArgumentException( + "Mount path '" + mountPath + "' is unsupported, use null for the base file store"); + if (mountPath != null && !mountPath.startsWith(JcrPath.separator)) + throw new IllegalArgumentException("Mount path '" + mountPath + "' cannot end with /"); + if (mountPath != null && mountPath.endsWith(JcrPath.separator)) + throw new IllegalArgumentException("Mount path '" + mountPath + "' cannot end with /"); + this.mountPath = mountPath; + if (mountPath == null) + mountDepth = 0; + else { + mountDepth = mountPath.split(JcrPath.separator).length - 1; + } + this.workspace = workspace; + this.workspaceName = workspace.getName(); + } + + public void close() { + JcrUtils.logoutQuietly(workspace.getSession()); + } + + @Override + public String name() { + return workspace.getName(); + } + + @Override + public String type() { + return "workspace"; + } + + @Override + public boolean isReadOnly() { + return false; + } + + @Override + public long getTotalSpace() throws IOException { + return 0; + } + + @Override + public long getUsableSpace() throws IOException { + return 0; + } + + @Override + public long getUnallocatedSpace() throws IOException { + return 0; + } + + @Override + public boolean supportsFileAttributeView(Class type) { + return false; + } + + @Override + public boolean supportsFileAttributeView(String name) { + return false; + } + + @Override + public V getFileStoreAttributeView(Class type) { + return null; + } + + @Override + public Object getAttribute(String attribute) throws IOException { + return workspace.getSession().getRepository().getDescriptor(attribute); + } + + public Workspace getWorkspace() { + return workspace; + } + + public String toFsPath(Node node) throws RepositoryException { + String nodeWorkspaceName = node.getSession().getWorkspace().getName(); + if (!nodeWorkspaceName.equals(workspace.getName())) + throw new IllegalArgumentException("Icompatible " + node + " from workspace '" + nodeWorkspaceName + + "' in file store '" + workspace.getName() + "'"); + return mountPath == null ? node.getPath() : mountPath + node.getPath(); + } + + public boolean isBase() { + return mountPath == null; + } + + Node toNode(String[] fullPath) throws RepositoryException { + String jcrPath = toJcrPath(fullPath); + Session session = workspace.getSession(); + if (!session.itemExists(jcrPath)) + return null; + Node node = session.getNode(jcrPath); + return node; + } + + String toJcrPath(String fsPath) { + if (fsPath.length() == 1) + return toJcrPath((String[]) null);// root + String[] arr = fsPath.substring(1).split("/"); +// if (arr.length == 0 || (arr.length == 1 && arr[0].equals(""))) +// return toJcrPath((String[]) null);// root +// else + return toJcrPath(arr); + } + + private String toJcrPath(String[] path) { + if (path == null) + return "/"; + if (path.length < mountDepth) + throw new IllegalArgumentException( + "Path " + Arrays.asList(path) + " is no compatible with mount " + mountPath); + + if (!isBase()) { + // check mount compatibility + StringBuilder mount = new StringBuilder(); + mount.append('/'); + for (int i = 0; i < mountDepth; i++) { + if (i != 0) + mount.append('/'); + mount.append(Text.escapeIllegalJcrChars(path[i])); + } + if (!mountPath.equals(mount.toString())) + throw new IllegalArgumentException( + "Path " + Arrays.asList(path) + " is no compatible with mount " + mountPath); + } + + StringBuilder sb = new StringBuilder(); + sb.append('/'); + for (int i = mountDepth; i < path.length; i++) { + if (i != mountDepth) + sb.append('/'); + sb.append(Text.escapeIllegalJcrChars(path[i])); + } + return sb.toString(); + } + + public String getMountPath() { + return mountPath; + } + + public String getWorkspaceName() { + return workspaceName; + } + + public int getMountDepth() { + return mountDepth; + } + + @Override + public int hashCode() { + return workspaceName.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof WorkspaceFileStore)) + return false; + WorkspaceFileStore other = (WorkspaceFileStore) obj; + return workspaceName.equals(other.workspaceName); + } + + @Override + public String toString() { + return "WorkspaceFileStore " + workspaceName; + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/package-info.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/package-info.java new file mode 100644 index 000000000..0cdfdaf43 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/fs/package-info.java @@ -0,0 +1,2 @@ +/** Java NIO file system implementation based on plain JCR. */ +package org.argeo.jcr.fs; \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/jcrx.cnd b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/jcrx.cnd new file mode 100644 index 000000000..3eb0e7a3d --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/jcrx.cnd @@ -0,0 +1,16 @@ +// +// JCR EXTENSIONS +// + + +[jcrx:xmlvalue] +- * ++ jcr:xmltext (jcrx:xmltext) = jcrx:xmltext + +[jcrx:xmltext] + - jcr:xmlcharacters (STRING) mandatory + +[jcrx:csum] +mixin + - jcrx:sum (STRING) * + \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/package-info.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/package-info.java new file mode 100644 index 000000000..1837749f1 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/package-info.java @@ -0,0 +1,2 @@ +/** Generic JCR utilities. */ +package org.argeo.jcr; \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/AbstractUrlProxy.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/AbstractUrlProxy.java new file mode 100644 index 000000000..0177636f8 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/AbstractUrlProxy.java @@ -0,0 +1,153 @@ +package org.argeo.jcr.proxy; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +import javax.jcr.Binary; +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.nodetype.NodeType; + +import org.argeo.api.cms.CmsLog; +import org.argeo.jcr.JcrException; +import org.argeo.jcr.JcrUtils; + +/** Base class for URL based proxys. */ +public abstract class AbstractUrlProxy implements ResourceProxy { + private final static CmsLog log = CmsLog.getLog(AbstractUrlProxy.class); + + private Repository jcrRepository; + private Session jcrAdminSession; + private String proxyWorkspace = "proxy"; + + protected abstract Node retrieve(Session session, String path); + + void init() { + try { + jcrAdminSession = JcrUtils.loginOrCreateWorkspace(jcrRepository, proxyWorkspace); + beforeInitSessionSave(jcrAdminSession); + if (jcrAdminSession.hasPendingChanges()) + jcrAdminSession.save(); + } catch (RepositoryException e) { + JcrUtils.discardQuietly(jcrAdminSession); + throw new JcrException("Cannot initialize URL proxy", e); + } + } + + /** + * Called before the (admin) session is saved at the end of the initialization. + * Does nothing by default, to be overridden. + */ + protected void beforeInitSessionSave(Session session) throws RepositoryException { + } + + void destroy() { + JcrUtils.logoutQuietly(jcrAdminSession); + } + + /** + * Called before the (admin) session is logged out when resources are released. + * Does nothing by default, to be overridden. + */ + protected void beforeDestroySessionLogout() throws RepositoryException { + } + + public Node proxy(String path) { + // we open a JCR session with client credentials in order not to use the + // admin session in multiple thread or make it a bottleneck. + Node nodeAdmin = null; + Node nodeClient = null; + Session clientSession = null; + try { + clientSession = jcrRepository.login(proxyWorkspace); + if (!clientSession.itemExists(path) || shouldUpdate(clientSession, path)) { + nodeAdmin = retrieveAndSave(path); + if (nodeAdmin != null) + nodeClient = clientSession.getNode(path); + } else + nodeClient = clientSession.getNode(path); + return nodeClient; + } catch (RepositoryException e) { + throw new JcrException("Cannot proxy " + path, e); + } finally { + if (nodeClient == null) + JcrUtils.logoutQuietly(clientSession); + } + } + + protected synchronized Node retrieveAndSave(String path) { + try { + Node node = retrieve(jcrAdminSession, path); + if (node == null) + return null; + jcrAdminSession.save(); + return node; + } catch (RepositoryException e) { + JcrUtils.discardQuietly(jcrAdminSession); + throw new JcrException("Cannot retrieve and save " + path, e); + } finally { + notifyAll(); + } + } + + /** Session is not saved */ + protected synchronized Node proxyUrl(Session session, String remoteUrl, String path) throws RepositoryException { + Node node = null; + if (session.itemExists(path)) { + // throw new ArgeoJcrException("Node " + path + " already exists"); + } + try (InputStream in = new URL(remoteUrl).openStream()) { + // URL u = new URL(remoteUrl); + // in = u.openStream(); + node = importFile(session, path, in); + } catch (IOException e) { + if (log.isDebugEnabled()) { + log.debug("Cannot read " + remoteUrl + ", skipping... " + e.getMessage()); + // log.trace("Cannot read because of ", e); + } + JcrUtils.discardQuietly(session); + // } finally { + // IOUtils.closeQuietly(in); + } + return node; + } + + protected synchronized Node importFile(Session session, String path, InputStream in) throws RepositoryException { + Binary binary = null; + try { + Node content = null; + Node node = null; + if (!session.itemExists(path)) { + node = JcrUtils.mkdirs(session, path, NodeType.NT_FILE, NodeType.NT_FOLDER, false); + content = node.addNode(Node.JCR_CONTENT, NodeType.NT_UNSTRUCTURED); + } else { + node = session.getNode(path); + content = node.getNode(Node.JCR_CONTENT); + } + binary = session.getValueFactory().createBinary(in); + content.setProperty(Property.JCR_DATA, binary); + JcrUtils.updateLastModifiedAndParents(node, null, true); + return node; + } finally { + JcrUtils.closeQuietly(binary); + } + } + + /** Whether the file should be updated. */ + protected Boolean shouldUpdate(Session clientSession, String nodePath) { + return false; + } + + public void setJcrRepository(Repository jcrRepository) { + this.jcrRepository = jcrRepository; + } + + public void setProxyWorkspace(String localWorkspace) { + this.proxyWorkspace = localWorkspace; + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/ResourceProxy.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/ResourceProxy.java new file mode 100644 index 000000000..84eea1f31 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/ResourceProxy.java @@ -0,0 +1,16 @@ +package org.argeo.jcr.proxy; + +import javax.jcr.Node; + +/** A proxy which nows how to resolve and synchronize relative URLs */ +public interface ResourceProxy { + /** + * Proxy the file referenced by this relative path in the underlying + * repository. A new session is created by each call, so the underlying + * session of the returned node must be closed by the caller. + * + * @return the proxied Node, null if the resource was not found + * (e.g. HTTP 404) + */ + public Node proxy(String relativePath); +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/ResourceProxyServlet.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/ResourceProxyServlet.java new file mode 100644 index 000000000..a8e00df60 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/ResourceProxyServlet.java @@ -0,0 +1,115 @@ +package org.argeo.jcr.proxy; + +import java.io.IOException; +import java.io.InputStream; + +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.RepositoryException; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.io.FilenameUtils; +import org.apache.commons.io.IOUtils; +import org.argeo.jcr.JcrException; +import org.argeo.api.cms.CmsLog; +import org.argeo.jcr.Bin; +import org.argeo.jcr.JcrUtils; + +/** Wraps a proxy via HTTP */ +public class ResourceProxyServlet extends HttpServlet { + private static final long serialVersionUID = -8886549549223155801L; + + private final static CmsLog log = CmsLog + .getLog(ResourceProxyServlet.class); + + private ResourceProxy proxy; + + private String contentTypeCharset = "UTF-8"; + + @Override + protected void doGet(HttpServletRequest request, + HttpServletResponse response) throws ServletException, IOException { + String path = request.getPathInfo(); + + if (log.isTraceEnabled()) { + log.trace("path=" + path); + log.trace("UserPrincipal = " + request.getUserPrincipal().getName()); + log.trace("SessionID = " + request.getSession(false).getId()); + log.trace("ContextPath = " + request.getContextPath()); + log.trace("ServletPath = " + request.getServletPath()); + log.trace("PathInfo = " + request.getPathInfo()); + log.trace("Method = " + request.getMethod()); + log.trace("User-Agent = " + request.getHeader("User-Agent")); + } + + Node node = null; + try { + node = proxy.proxy(path); + if (node == null) + response.sendError(404); + else + processResponse(node, response); + } finally { + if (node != null) + try { + JcrUtils.logoutQuietly(node.getSession()); + } catch (RepositoryException e) { + // silent + } + } + + } + + /** Retrieve the content of the node. */ + protected void processResponse(Node node, HttpServletResponse response) { +// Binary binary = null; +// InputStream in = null; + try(Bin binary = new Bin( node.getNode(Property.JCR_CONTENT) + .getProperty(Property.JCR_DATA));InputStream in = binary.getStream()) { + String fileName = node.getName(); + String ext = FilenameUtils.getExtension(fileName); + + // TODO use a more generic / standard approach + // see http://svn.apache.org/viewvc/tomcat/trunk/conf/web.xml + String contentType; + if ("xml".equals(ext)) + contentType = "text/xml;charset=" + contentTypeCharset; + else if ("jar".equals(ext)) + contentType = "application/java-archive"; + else if ("zip".equals(ext)) + contentType = "application/zip"; + else if ("gz".equals(ext)) + contentType = "application/x-gzip"; + else if ("bz2".equals(ext)) + contentType = "application/x-bzip2"; + else if ("tar".equals(ext)) + contentType = "application/x-tar"; + else if ("rpm".equals(ext)) + contentType = "application/x-redhat-package-manager"; + else + contentType = "application/octet-stream"; + contentType = contentType + ";name=\"" + fileName + "\""; + response.setHeader("Content-Disposition", "attachment; filename=\"" + + fileName + "\""); + response.setHeader("Expires", "0"); + response.setHeader("Cache-Control", "no-cache, must-revalidate"); + response.setHeader("Pragma", "no-cache"); + + response.setContentType(contentType); + + IOUtils.copy(in, response.getOutputStream()); + } catch (RepositoryException e) { + throw new JcrException("Cannot download " + node, e); + } catch (IOException e) { + throw new RuntimeException("Cannot download " + node, e); + } + } + + public void setProxy(ResourceProxy resourceProxy) { + this.proxy = resourceProxy; + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/package-info.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/package-info.java new file mode 100644 index 000000000..a578c456e --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/package-info.java @@ -0,0 +1,2 @@ +/** Components to build proxys based on JCR. */ +package org.argeo.jcr.proxy; \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/unit/AbstractJcrTestCase.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/unit/AbstractJcrTestCase.java new file mode 100644 index 000000000..84e8cd31a --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/unit/AbstractJcrTestCase.java @@ -0,0 +1,115 @@ +package org.argeo.jcr.unit; + +import java.io.File; +import java.security.AccessController; +import java.security.PrivilegedAction; + +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.SimpleCredentials; +import javax.security.auth.Subject; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; + +import org.apache.commons.io.FileUtils; +import org.argeo.api.cms.CmsLog; +import org.argeo.jcr.JcrException; + +import junit.framework.TestCase; + +/** Base for unit tests with a JCR repository. */ +public abstract class AbstractJcrTestCase extends TestCase { + private final static CmsLog log = CmsLog.getLog(AbstractJcrTestCase.class); + + private Repository repository; + private Session session = null; + + public final static String LOGIN_CONTEXT_TEST_SYSTEM = "TEST_JACKRABBIT_ADMIN"; + + // protected abstract File getRepositoryFile() throws Exception; + + protected abstract Repository createRepository() throws Exception; + + protected abstract void clearRepository(Repository repository) throws Exception; + + @Override + protected void setUp() throws Exception { + File homeDir = getHomeDir(); + FileUtils.deleteDirectory(homeDir); + repository = createRepository(); + } + + @Override + protected void tearDown() throws Exception { + if (session != null) { + session.logout(); + if (log.isTraceEnabled()) + log.trace("Logout session"); + } + clearRepository(repository); + } + + protected Session session() { + if (session != null && session.isLive()) + return session; + Session session; + if (getLoginContext() != null) { + LoginContext lc; + try { + lc = new LoginContext(getLoginContext()); + lc.login(); + } catch (LoginException e) { + throw new IllegalStateException("JAAS login failed", e); + } + session = Subject.doAs(lc.getSubject(), new PrivilegedAction() { + + @Override + public Session run() { + return login(); + } + + }); + } else + session = login(); + this.session = session; + return this.session; + } + + protected String getLoginContext() { + return null; + } + + protected Session login() { + try { + if (log.isTraceEnabled()) + log.trace("Login session"); + Subject subject = Subject.getSubject(AccessController.getContext()); + if (subject != null) + return getRepository().login(); + else + return getRepository().login(new SimpleCredentials("demo", "demo".toCharArray())); + } catch (RepositoryException e) { + throw new JcrException("Cannot login to repository", e); + } + } + + protected Repository getRepository() { + return repository; + } + + /** + * enables children class to set an existing repository in case it is not + * deleted on startup, to test migration by instance + */ + public void setRepository(Repository repository) { + this.repository = repository; + } + + protected File getHomeDir() { + File homeDir = new File(System.getProperty("java.io.tmpdir"), + AbstractJcrTestCase.class.getSimpleName() + "-" + System.getProperty("user.name")); + return homeDir; + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/unit/package-info.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/unit/package-info.java new file mode 100644 index 000000000..c6e741524 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/unit/package-info.java @@ -0,0 +1,2 @@ +/** Helpers for unit tests with JCR repositories. */ +package org.argeo.jcr.unit; \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/xml/JcrXmlUtils.java b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/xml/JcrXmlUtils.java new file mode 100644 index 000000000..2adb6a97e --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/xml/JcrXmlUtils.java @@ -0,0 +1,186 @@ +package org.argeo.jcr.xml; + +import java.io.IOException; +import java.io.Writer; +import java.util.Map; +import java.util.TreeMap; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.Property; +import javax.jcr.PropertyIterator; +import javax.jcr.RepositoryException; +import javax.jcr.Value; +import javax.jcr.nodetype.NodeType; + +import org.argeo.jcr.Jcr; + +/** Utilities around JCR and XML. */ +public class JcrXmlUtils { + /** + * Convenience method calling {@link #toXmlElements(Writer, Node, boolean)} with + * false. + */ + public static void toXmlElements(Writer writer, Node node) throws RepositoryException, IOException { + toXmlElements(writer, node, null, false, false, false); + } + + /** + * Write JCR properties as XML elements in a tree structure whose elements are + * named by node primary type. + * + * @param writer the writer to use + * @param node the subtree + * @param depth maximal depth, or if null the whole + * subtree. It must be positive, with depth 0 + * describing just the node without its children. + * @param withMetadata whether to write the primary type and mixins as + * elements + * @param withPrefix whether to keep the namespace prefixes + * @param propertiesAsElements whether single properties should be written as + * elements rather than attributes. If + * false, multiple properties will be + * skipped. + */ + public static void toXmlElements(Writer writer, Node node, Integer depth, boolean withMetadata, boolean withPrefix, + boolean propertiesAsElements) throws RepositoryException, IOException { + if (depth != null && depth < 0) + throw new IllegalArgumentException("Depth " + depth + " is negative."); + + if (node.getName().equals(Jcr.JCR_XMLTEXT)) { + writer.write(node.getProperty(Jcr.JCR_XMLCHARACTERS).getString()); + return; + } + + if (!propertiesAsElements) { + Map attrs = new TreeMap<>(); + PropertyIterator pit = node.getProperties(); + properties: while (pit.hasNext()) { + Property p = pit.nextProperty(); + if (!p.isMultiple()) { + String pName = p.getName(); + if (!withMetadata && (pName.equals(Jcr.JCR_PRIMARY_TYPE) || pName.equals(Jcr.JCR_UUID) + || pName.equals(Jcr.JCR_CREATED) || pName.equals(Jcr.JCR_CREATED_BY) + || pName.equals(Jcr.JCR_LAST_MODIFIED) || pName.equals(Jcr.JCR_LAST_MODIFIED_BY))) + continue properties; + attrs.put(withPrefix(p.getName(), withPrefix), p.getString()); + } + } + if (withMetadata && node.hasProperty(Property.JCR_UUID)) + attrs.put("id", "urn:uuid:" + node.getProperty(Property.JCR_UUID).getString()); + attrs.put(withPrefix ? Jcr.JCR_NAME : "name", node.getName()); + writeStart(writer, withPrefix(node.getPrimaryNodeType().getName(), withPrefix), attrs, node.hasNodes()); + } else { + if (withMetadata && node.hasProperty(Property.JCR_UUID)) { + writeStart(writer, withPrefix(node.getPrimaryNodeType().getName(), withPrefix), "id", + "urn:uuid:" + node.getProperty(Property.JCR_UUID).getString()); + } else { + writeStart(writer, withPrefix(node.getPrimaryNodeType().getName(), withPrefix)); + } + // name + writeStart(writer, withPrefix ? Jcr.JCR_NAME : "name"); + writer.append(node.getName()); + writeEnd(writer, withPrefix ? Jcr.JCR_NAME : "name"); + } + + // mixins + if (withMetadata) { + for (NodeType mixin : node.getMixinNodeTypes()) { + writeStart(writer, withPrefix ? Jcr.JCR_MIXIN_TYPES : "mixinTypes"); + writer.append(mixin.getName()); + writeEnd(writer, withPrefix ? Jcr.JCR_MIXIN_TYPES : "mixinTypes"); + } + } + + // properties as elements + if (propertiesAsElements) { + PropertyIterator pit = node.getProperties(); + properties: while (pit.hasNext()) { + Property p = pit.nextProperty(); + if (p.isMultiple()) { + for (Value value : p.getValues()) { + writeStart(writer, withPrefix(p.getName(), withPrefix)); + writer.write(value.getString()); + writeEnd(writer, withPrefix(p.getName(), withPrefix)); + } + } else { + Value value = p.getValue(); + String pName = p.getName(); + if (!withMetadata && (pName.equals(Jcr.JCR_PRIMARY_TYPE) || pName.equals(Jcr.JCR_UUID) + || pName.equals(Jcr.JCR_CREATED) || pName.equals(Jcr.JCR_CREATED_BY) + || pName.equals(Jcr.JCR_LAST_MODIFIED) || pName.equals(Jcr.JCR_LAST_MODIFIED_BY))) + continue properties; + writeStart(writer, withPrefix(p.getName(), withPrefix)); + writer.write(value.getString()); + writeEnd(writer, withPrefix(p.getName(), withPrefix)); + } + } + } + + // children + if (node.hasNodes()) { + if (depth == null || depth > 0) { + NodeIterator nit = node.getNodes(); + while (nit.hasNext()) { + toXmlElements(writer, nit.nextNode(), depth == null ? null : depth - 1, withMetadata, withPrefix, + propertiesAsElements); + } + } + writeEnd(writer, withPrefix(node.getPrimaryNodeType().getName(), withPrefix)); + } + } + + private static String withPrefix(String str, boolean withPrefix) { + if (withPrefix) + return str; + int index = str.indexOf(':'); + if (index < 0) + return str; + return str.substring(index + 1); + } + + private static void writeStart(Writer writer, String tagName) throws IOException { + writer.append('<'); + writer.append(tagName); + writer.append('>'); + } + + private static void writeStart(Writer writer, String tagName, String attr, String value) throws IOException { + writer.append('<'); + writer.append(tagName); + writer.append(' '); + writer.append(attr); + writer.append("=\""); + writer.append(value); + writer.append("\">"); + } + + private static void writeStart(Writer writer, String tagName, Map attrs, boolean hasChildren) + throws IOException { + writer.append('<'); + writer.append(tagName); + for (String attr : attrs.keySet()) { + writer.append(' '); + writer.append(attr); + writer.append("=\""); + writer.append(attrs.get(attr)); + writer.append('\"'); + } + if (hasChildren) + writer.append('>'); + else + writer.append("/>"); + } + + private static void writeEnd(Writer writer, String tagName) throws IOException { + writer.append("'); + } + + /** Singleton. */ + private JcrXmlUtils() { + + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/xml/removePrefixes.xsl b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/xml/removePrefixes.xsl new file mode 100644 index 000000000..813d06570 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/jcr/xml/removePrefixes.xsl @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/AbstractMaintenanceService.java b/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/AbstractMaintenanceService.java new file mode 100644 index 000000000..3c8f296c4 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/AbstractMaintenanceService.java @@ -0,0 +1,220 @@ +package org.argeo.maintenance; + +import java.io.IOException; +import java.util.EnumSet; +import java.util.HashSet; +import java.util.Set; + +import javax.jcr.NoSuchWorkspaceException; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.jcr.CmsJcrUtils; +import org.argeo.jcr.Jcr; +import org.argeo.jcr.JcrUtils; +import org.argeo.osgi.transaction.WorkTransaction; +import org.argeo.util.naming.Distinguished; +import org.osgi.service.useradmin.Group; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.UserAdmin; + +/** Make sure roles and access rights are properly configured. */ +public abstract class AbstractMaintenanceService { + private final static CmsLog log = CmsLog.getLog(AbstractMaintenanceService.class); + + private Repository repository; +// private UserAdminService userAdminService; + private UserAdmin userAdmin; + private WorkTransaction userTransaction; + + public void init() { + makeSureRolesExists(getRequiredRoles()); + configureStandardRoles(); + + Set workspaceNames = getWorkspaceNames(); + if (workspaceNames == null || workspaceNames.isEmpty()) { + configureJcr(repository, null); + } else { + for (String workspaceName : workspaceNames) + configureJcr(repository, workspaceName); + } + } + + /** Configures a workspace. */ + protected void configureJcr(Repository repository, String workspaceName) { + Session adminSession; + try { + adminSession = CmsJcrUtils.openDataAdminSession(repository, workspaceName); + } catch (RuntimeException e1) { + if (e1.getCause() != null && e1.getCause() instanceof NoSuchWorkspaceException) { + Session defaultAdminSession = CmsJcrUtils.openDataAdminSession(repository, null); + try { + defaultAdminSession.getWorkspace().createWorkspace(workspaceName); + log.info("Created JCR workspace " + workspaceName); + } catch (RepositoryException e) { + throw new IllegalStateException("Cannot create workspace " + workspaceName, e); + } finally { + Jcr.logout(defaultAdminSession); + } + adminSession = CmsJcrUtils.openDataAdminSession(repository, workspaceName); + } else + throw e1; + } + try { + if (prepareJcrTree(adminSession)) { + configurePrivileges(adminSession); + } + } catch (RepositoryException | IOException e) { + throw new IllegalStateException("Cannot initialise JCR data layer.", e); + } finally { + JcrUtils.logoutQuietly(adminSession); + } + } + + /** To be overridden. */ + protected Set getWorkspaceNames() { + return null; + } + + /** + * To be overridden in order to programmatically set relationships between + * roles. Does nothing by default. + */ + protected void configureStandardRoles() { + } + + /** + * Creates the base JCR tree structure expected for this app if necessary. + * + * Expects a clean session ({@link Session#hasPendingChanges()} should return + * false) and saves it once the changes have been done. Thus the session can be + * rolled back if an exception occurs. + * + * @return true if something as been updated + */ + public boolean prepareJcrTree(Session adminSession) throws RepositoryException, IOException { + return false; + } + + /** + * Adds app specific default privileges. + * + * Expects a clean session ({@link Session#hasPendingChanges()} should return + * false} and saves it once the changes have been done. Thus the session can be + * rolled back if an exception occurs. + * + * Warning: no check is done and corresponding privileges are always added, so + * only call this when necessary + */ + public void configurePrivileges(Session session) throws RepositoryException { + } + + /** The system roles that must be available in the system. */ + protected Set getRequiredRoles() { + return new HashSet<>(); + } + + public void destroy() { + + } + + /* + * UTILITIES + */ + + /** Create these roles as group if they don't exist. */ + protected void makeSureRolesExists(EnumSet enumSet) { + makeSureRolesExists(Distinguished.enumToDns(enumSet)); + } + + /** Create these roles as group if they don't exist. */ + protected void makeSureRolesExists(Set requiredRoles) { + if (requiredRoles == null) + return; + if (getUserAdmin() == null) { + log.warn("No user admin service available, cannot make sure that role exists"); + return; + } + for (String role : requiredRoles) { + Role systemRole = getUserAdmin().getRole(role); + if (systemRole == null) { + try { + getUserTransaction().begin(); + getUserAdmin().createRole(role, Role.GROUP); + getUserTransaction().commit(); + log.info("Created role " + role); + } catch (Exception e) { + try { + getUserTransaction().rollback(); + } catch (Exception e1) { + // silent + } + throw new IllegalStateException("Cannot create role " + role, e); + } + } + } + } + + /** Add a user or group to a group. */ + protected void addToGroup(String groupToAddDn, String groupDn) { + if (groupToAddDn.contentEquals(groupDn)) { + if (log.isTraceEnabled()) + log.trace("Ignore adding group " + groupDn + " to itself"); + return; + } + + if (getUserAdmin() == null) { + log.warn("No user admin service available, cannot add group " + groupToAddDn + " to " + groupDn); + return; + } + Group groupToAdd = (Group) getUserAdmin().getRole(groupToAddDn); + if (groupToAdd == null) + throw new IllegalArgumentException("Group " + groupToAddDn + " not found"); + Group group = (Group) getUserAdmin().getRole(groupDn); + if (group == null) + throw new IllegalArgumentException("Group " + groupDn + " not found"); + try { + getUserTransaction().begin(); + if (group.addMember(groupToAdd)) + log.info("Added " + groupToAddDn + " to " + group); + getUserTransaction().commit(); + } catch (Exception e) { + try { + getUserTransaction().rollback(); + } catch (Exception e1) { + // silent + } + throw new IllegalStateException("Cannot add " + groupToAddDn + " to " + groupDn); + } + } + + /* + * DEPENDENCY INJECTION + */ + public void setRepository(Repository repository) { + this.repository = repository; + } + +// public void setUserAdminService(UserAdminService userAdminService) { +// this.userAdminService = userAdminService; +// } + + protected WorkTransaction getUserTransaction() { + return userTransaction; + } + + protected UserAdmin getUserAdmin() { + return userAdmin; + } + + public void setUserAdmin(UserAdmin userAdmin) { + this.userAdmin = userAdmin; + } + + public void setUserTransaction(WorkTransaction userTransaction) { + this.userTransaction = userTransaction; + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/SimpleRoleRegistration.java b/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/SimpleRoleRegistration.java new file mode 100644 index 000000000..ebb8c534d --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/SimpleRoleRegistration.java @@ -0,0 +1,86 @@ +package org.argeo.maintenance; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import javax.naming.InvalidNameException; +import javax.naming.ldap.LdapName; + +import org.argeo.api.cms.CmsLog; +import org.argeo.osgi.transaction.WorkTransaction; +import org.osgi.service.useradmin.Role; +import org.osgi.service.useradmin.UserAdmin; + +/** + * Register one or many roles via a user admin service. Does nothing if the role + * is already registered. + */ +public class SimpleRoleRegistration implements Runnable { + private final static CmsLog log = CmsLog.getLog(SimpleRoleRegistration.class); + + private String role; + private List roles = new ArrayList(); + private UserAdmin userAdmin; + private WorkTransaction userTransaction; + + @Override + public void run() { + try { + userTransaction.begin(); + if (role != null && !roleExists(role)) + newRole(toDn(role)); + + for (String r : roles) + if (!roleExists(r)) + newRole(toDn(r)); + userTransaction.commit(); + } catch (Exception e) { + try { + userTransaction.rollback(); + } catch (Exception e1) { + log.error("Cannot rollback", e1); + } + throw new IllegalArgumentException("Cannot add roles", e); + } + } + + private boolean roleExists(String role) { + return userAdmin.getRole(toDn(role).toString()) != null; + } + + protected void newRole(LdapName r) { + userAdmin.createRole(r.toString(), Role.GROUP); + log.info("Added role " + r + " required by application."); + } + + public void register(UserAdmin userAdminService, Map properties) { + this.userAdmin = userAdminService; + run(); + } + + protected LdapName toDn(String name) { + try { + return new LdapName("cn=" + name + ",ou=roles,ou=node"); + } catch (InvalidNameException e) { + throw new IllegalArgumentException("Badly formatted role name " + name, e); + } + } + + public void setRole(String role) { + this.role = role; + } + + public void setRoles(List roles) { + this.roles = roles; + } + + public void setUserAdmin(UserAdmin userAdminService) { + this.userAdmin = userAdminService; + } + + public void setUserTransaction(WorkTransaction userTransaction) { + this.userTransaction = userTransaction; + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/BackupContentHandler.java b/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/BackupContentHandler.java new file mode 100644 index 000000000..ef83c1ff9 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/BackupContentHandler.java @@ -0,0 +1,257 @@ +package org.argeo.maintenance.backup; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Writer; +import java.util.Arrays; +import java.util.Base64; +import java.util.Set; +import java.util.TreeSet; + +import javax.jcr.Binary; +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.apache.commons.io.IOUtils; +import org.argeo.jcr.Jcr; +import org.argeo.jcr.JcrException; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; +import org.xml.sax.helpers.DefaultHandler; + +/** XML handler serialising a JCR system view. */ +public class BackupContentHandler extends DefaultHandler { + final static int MAX_DEPTH = 1024; + final static String SV_NAMESPACE_URI = "http://www.jcp.org/jcr/sv/1.0"; + final static String SV_PREFIX = "sv"; + // elements + final static String NODE = "node"; + final static String PROPERTY = "property"; + final static String VALUE = "value"; + // attributes + final static String NAME = "name"; + final static String MULTIPLE = "multiple"; + final static String TYPE = "type"; + + // values + final static String BINARY = "Binary"; + final static String JCR_CONTENT = "jcr:content"; + + private Writer out; + private Session session; + private Set contentPaths = new TreeSet<>(); + + boolean prettyPrint = true; + + private final String parentPath; + +// private boolean inSystem = false; + + public BackupContentHandler(Writer out, Node node) { + super(); + this.out = out; + this.session = Jcr.getSession(node); + parentPath = Jcr.getParentPath(node); + } + + private int currentDepth = -1; + private String[] currentPath = new String[MAX_DEPTH]; + + private boolean currentPropertyIsMultiple = false; + private String currentEncoded = null; + private Base64.Encoder base64encore = Base64.getEncoder(); + + @Override + public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { + boolean isNode; + boolean isProperty; + switch (localName) { + case NODE: + isNode = true; + isProperty = false; + break; + case PROPERTY: + isNode = false; + isProperty = true; + break; + default: + isNode = false; + isProperty = false; + } + + if (isNode) { + String nodeName = attributes.getValue(SV_NAMESPACE_URI, NAME); + currentDepth = currentDepth + 1; +// if (currentDepth >= 0) + currentPath[currentDepth] = nodeName; +// System.out.println(getCurrentPath() + " , depth=" + currentDepth); +// if ("jcr:system".equals(nodeName)) { +// inSystem = true; +// } + } +// if (inSystem) +// return; + + if (SV_NAMESPACE_URI.equals(uri)) + try { + if (prettyPrint) { + if (isNode) { + out.write(spaces()); + out.write("\n"); + out.write(spaces()); + } else if (isProperty) + out.write(spaces()); + else if (currentPropertyIsMultiple) + out.write(spaces()); + } + + out.write("<"); + out.write(SV_PREFIX + ":" + localName); + if (isProperty) + currentPropertyIsMultiple = false; // always reset + for (int i = 0; i < attributes.getLength(); i++) { + String ns = attributes.getURI(i); + if (SV_NAMESPACE_URI.equals(ns)) { + String attrName = attributes.getLocalName(i); + String attrValue = attributes.getValue(i); + out.write(" "); + out.write(SV_PREFIX + ":" + attrName); + out.write("="); + out.write("\""); + out.write(attrValue); + out.write("\""); + if (isProperty) { + if (MULTIPLE.equals(attrName)) + currentPropertyIsMultiple = Boolean.parseBoolean(attrValue); + else if (TYPE.equals(attrName)) { + if (BINARY.equals(attrValue)) { + if (JCR_CONTENT.equals(getCurrentName())) { + contentPaths.add(getCurrentJcrPath()); + } else { + Binary binary = session.getNode(getCurrentJcrPath()).getProperty(attrName) + .getBinary(); + try (InputStream in = binary.getStream()) { + currentEncoded = base64encore.encodeToString(IOUtils.toByteArray(in)); + } finally { + + } + } + } + } + } + } + } + if (isNode && currentDepth == 0) { + // out.write(" xmlns=\"" + SV_NAMESPACE_URI + "\""); + out.write(" xmlns:" + SV_PREFIX + "=\"" + SV_NAMESPACE_URI + "\""); + } + out.write(">"); + + if (prettyPrint) + if (isNode) + out.write("\n"); + else if (isProperty && currentPropertyIsMultiple) + out.write("\n"); + } catch (IOException e) { + throw new RuntimeException(e); + } catch (RepositoryException e) { + throw new JcrException(e); + } + } + + @Override + public void endElement(String uri, String localName, String qName) throws SAXException { + boolean isNode = localName.equals(NODE); + boolean isValue = localName.equals(VALUE); + if (prettyPrint) + if (!isValue) + try { + if (isNode || currentPropertyIsMultiple) + out.write(spaces()); + } catch (IOException e1) { + throw new RuntimeException(e1); + } + if (isNode) { +// System.out.println("endElement " + getCurrentPath() + " , depth=" + currentDepth); +// if (currentDepth > 0) + currentPath[currentDepth] = null; + currentDepth = currentDepth - 1; +// if (inSystem) { +// // System.out.println("Skip " + getCurrentPath()+" , +// // currentDepth="+currentDepth); +// if (currentDepth == 0) { +// inSystem = false; +// return; +// } +// } + } +// if (inSystem) +// return; + if (SV_NAMESPACE_URI.equals(uri)) + try { + if (isValue && currentEncoded != null) { + out.write(currentEncoded); + } + currentEncoded = null; + out.write(""); + if (prettyPrint) + if (!isValue) + out.write("\n"); + else { + if (currentPropertyIsMultiple) + out.write("\n"); + } + if (currentDepth == 0) + out.flush(); + } catch (IOException e) { + throw new RuntimeException(e); + } + + } + + private char[] spaces() { + char[] arr = new char[currentDepth]; + Arrays.fill(arr, ' '); + return arr; + } + + @Override + public void characters(char[] ch, int start, int length) throws SAXException { +// if (inSystem) +// return; + try { + out.write(ch, start, length); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + protected String getCurrentName() { + assert currentDepth >= 0; +// if (currentDepth == 0) +// return "jcr:root"; + return currentPath[currentDepth]; + } + + protected String getCurrentJcrPath() { +// if (currentDepth == 0) +// return "/"; + StringBuilder sb = new StringBuilder(parentPath.equals("/") ? "" : parentPath); + for (int i = 0; i <= currentDepth; i++) { +// if (i != 0) + sb.append('/'); + sb.append(currentPath[i]); + } + return sb.toString(); + } + + public Set getContentPaths() { + return contentPaths; + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/LogicalBackup.java b/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/LogicalBackup.java new file mode 100644 index 000000000..00d4be8b0 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/LogicalBackup.java @@ -0,0 +1,448 @@ +package org.argeo.maintenance.backup; + +import java.io.BufferedWriter; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.net.URI; +import java.net.URL; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Dictionary; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.TreeMap; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.jar.JarOutputStream; +import java.util.jar.Manifest; +import java.util.zip.ZipEntry; +import java.util.zip.ZipException; +import java.util.zip.ZipOutputStream; + +import javax.jcr.Binary; +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.Property; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.RepositoryFactory; +import javax.jcr.Session; + +import org.apache.commons.io.IOUtils; +import org.apache.jackrabbit.api.JackrabbitSession; +import org.apache.jackrabbit.api.JackrabbitValue; +import org.argeo.api.cms.CmsConstants; +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.jcr.CmsJcrUtils; +import org.argeo.jackrabbit.client.ClientDavexRepositoryFactory; +import org.argeo.jcr.Jcr; +import org.argeo.jcr.JcrException; +import org.argeo.jcr.JcrUtils; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; + +/** + * Performs a backup of the data based only on programmatic interfaces. Useful + * for migration or live backup. Physical backups of the underlying file + * systems, databases, LDAP servers, etc. should be performed for disaster + * recovery. + */ +public class LogicalBackup implements Runnable { + private final static CmsLog log = CmsLog.getLog(LogicalBackup.class); + + public final static String WORKSPACES_BASE = "workspaces/"; + public final static String FILES_BASE = "files/"; + public final static String OSGI_BASE = "share/osgi/"; + + public final static String JCR_SYSTEM = "jcr:system"; + public final static String JCR_VERSION_STORAGE_PATH = "/jcr:system/jcr:versionStorage"; + + private final Repository repository; + private String defaultWorkspace; + private final BundleContext bundleContext; + + private final ZipOutputStream zout; + private final Path basePath; + + private ExecutorService executorService; + + private boolean performSoftwareBackup = false; + + private Map checksums = new TreeMap<>(); + + private int threadCount = 5; + + private boolean backupFailed = false; + + public LogicalBackup(BundleContext bundleContext, Repository repository, Path basePath) { + this.repository = repository; + this.zout = null; + this.basePath = basePath; + this.bundleContext = bundleContext; + } + + @Override + public void run() { + try { + log.info("Start logical backup to " + basePath); + perform(); + } catch (Exception e) { + log.error("Unexpected exception when performing logical backup", e); + throw new IllegalStateException("Logical backup failed", e); + } + + } + + public void perform() throws RepositoryException, IOException { + if (executorService != null && !executorService.isTerminated()) + throw new IllegalStateException("Another backup is running"); + executorService = Executors.newFixedThreadPool(threadCount); + long begin = System.currentTimeMillis(); + // software backup + if (bundleContext != null && performSoftwareBackup) + executorService.submit(() -> performSoftwareBackup(bundleContext)); + + // data backup + Session defaultSession = login(null); + defaultWorkspace = defaultSession.getWorkspace().getName(); + try { + String[] workspaceNames = defaultSession.getWorkspace().getAccessibleWorkspaceNames(); + workspaces: for (String workspaceName : workspaceNames) { + if ("security".equals(workspaceName)) + continue workspaces; + performDataBackup(workspaceName); + } + } finally { + JcrUtils.logoutQuietly(defaultSession); + executorService.shutdown(); + try { + executorService.awaitTermination(24, TimeUnit.HOURS); + } catch (InterruptedException e) { + // silent + throw new IllegalStateException("Backup was interrupted before completion", e); + } + } + // versions + executorService = Executors.newFixedThreadPool(threadCount); + try { + performVersionsBackup(); + } finally { + executorService.shutdown(); + try { + executorService.awaitTermination(24, TimeUnit.HOURS); + } catch (InterruptedException e) { + // silent + throw new IllegalStateException("Backup was interrupted before completion", e); + } + } + long duration = System.currentTimeMillis() - begin; + if (isBackupFailed()) + log.info("System logical backup failed after " + (duration / 60000) + "min " + (duration / 1000) + "s"); + else + log.info("System logical backup completed in " + (duration / 60000) + "min " + (duration / 1000) + "s"); + } + + protected void performDataBackup(String workspaceName) throws RepositoryException, IOException { + Session session = login(workspaceName); + try { + nodes: for (NodeIterator nit = session.getRootNode().getNodes(); nit.hasNext();) { + if (isBackupFailed()) + return; + Node nodeToExport = nit.nextNode(); + if (JCR_SYSTEM.equals(nodeToExport.getName())) + continue nodes; + String nodePath = nodeToExport.getPath(); + Future> contentPathsFuture = executorService + .submit(() -> performNodeBackup(workspaceName, nodePath)); + executorService.submit(() -> performFilesBackup(workspaceName, contentPathsFuture)); + } + } finally { + Jcr.logout(session); + } + } + + protected void performVersionsBackup() throws RepositoryException, IOException { + Session session = login(defaultWorkspace); + Node versionStorageNode = session.getNode(JCR_VERSION_STORAGE_PATH); + try { + for (NodeIterator nit = versionStorageNode.getNodes(); nit.hasNext();) { + Node nodeToExport = nit.nextNode(); + String nodePath = nodeToExport.getPath(); + if (isBackupFailed()) + return; + Future> contentPathsFuture = executorService + .submit(() -> performNodeBackup(defaultWorkspace, nodePath)); + executorService.submit(() -> performFilesBackup(defaultWorkspace, contentPathsFuture)); + } + } finally { + Jcr.logout(session); + } + + } + + protected Set performNodeBackup(String workspaceName, String nodePath) { + Session session = login(workspaceName); + try { + Node nodeToExport = session.getNode(nodePath); +// String nodeName = nodeToExport.getName(); +// if (nodeName.startsWith("jcr:") || nodeName.startsWith("rep:")) +// continue nodes; +// // TODO make it more robust / configurable +// if (nodeName.equals("user")) +// continue nodes; + String relativePath = WORKSPACES_BASE + workspaceName + nodePath + ".xml"; + OutputStream xmlOut = openOutputStream(relativePath); + BackupContentHandler contentHandler; + try (Writer writer = new BufferedWriter(new OutputStreamWriter(xmlOut, StandardCharsets.UTF_8))) { + contentHandler = new BackupContentHandler(writer, nodeToExport); + session.exportSystemView(nodeToExport.getPath(), contentHandler, true, false); + if (log.isDebugEnabled()) + log.debug(workspaceName + ":" + nodePath + " metadata exported to " + relativePath); + } + + // Files + Set contentPaths = contentHandler.getContentPaths(); + return contentPaths; + } catch (Exception e) { + markBackupFailed("Cannot backup node " + workspaceName + ":" + nodePath, e); + throw new ThreadDeath(); + } finally { + Jcr.logout(session); + } + } + + protected void performFilesBackup(String workspaceName, Future> contentPathsFuture) { + Set contentPaths; + try { + contentPaths = contentPathsFuture.get(24, TimeUnit.HOURS); + } catch (InterruptedException | ExecutionException | TimeoutException e1) { + markBackupFailed("Cannot retrieve content paths for workspace " + workspaceName, e1); + return; + } + if (contentPaths == null || contentPaths.size() == 0) + return; + Session session = login(workspaceName); + try { + String workspacesFilesBasePath = FILES_BASE + workspaceName; + for (String path : contentPaths) { + if (isBackupFailed()) + return; + Node contentNode = session.getNode(path); + Binary binary = null; + try { + binary = contentNode.getProperty(Property.JCR_DATA).getBinary(); + String fileRelativePath = workspacesFilesBasePath + contentNode.getParent().getPath(); + + // checksum + boolean skip = false; + String checksum = null; + if (session instanceof JackrabbitSession) { + JackrabbitValue value = (JackrabbitValue) contentNode.getProperty(Property.JCR_DATA).getValue(); +// ReferenceBinary referenceBinary = (ReferenceBinary) binary; + checksum = value.getContentIdentity(); + } + if (checksum != null) { + if (!checksums.containsKey(checksum)) { + checksums.put(checksum, fileRelativePath); + } else { + skip = true; + String sourcePath = checksums.get(checksum); + if (log.isTraceEnabled()) + log.trace(fileRelativePath + " : already " + sourcePath + " with checksum " + checksum); + createLink(sourcePath, fileRelativePath); + try (Writer writerSum = new OutputStreamWriter( + openOutputStream(fileRelativePath + ".sha256"), StandardCharsets.UTF_8)) { + writerSum.write(checksum); + } + } + } + + // copy file + if (!skip) + try (InputStream in = binary.getStream(); + OutputStream out = openOutputStream(fileRelativePath)) { + IOUtils.copy(in, out); + if (log.isTraceEnabled()) + log.trace("Workspace " + workspaceName + ": file content exported to " + + fileRelativePath); + } + } finally { + JcrUtils.closeQuietly(binary); + } + } + if (log.isDebugEnabled()) + log.debug(workspaceName + ":" + contentPaths.size() + " files exported to " + workspacesFilesBasePath); + } catch (Exception e) { + markBackupFailed("Cannot backup files from " + workspaceName + ":", e); + } finally { + Jcr.logout(session); + } + } + + protected OutputStream openOutputStream(String relativePath) throws IOException { + if (zout != null) { + ZipEntry entry = new ZipEntry(relativePath); + zout.putNextEntry(entry); + return zout; + } else if (basePath != null) { + Path targetPath = basePath.resolve(Paths.get(relativePath)); + Files.createDirectories(targetPath.getParent()); + return Files.newOutputStream(targetPath); + } else { + throw new UnsupportedOperationException(); + } + } + + protected void createLink(String source, String target) throws IOException { + if (zout != null) { + // TODO implement for zip + throw new UnsupportedOperationException(); + } else if (basePath != null) { + Path sourcePath = basePath.resolve(Paths.get(source)); + Path targetPath = basePath.resolve(Paths.get(target)); + Path relativeSource = targetPath.getParent().relativize(sourcePath); + Files.createDirectories(targetPath.getParent()); + Files.createSymbolicLink(targetPath, relativeSource); + } else { + throw new UnsupportedOperationException(); + } + } + + protected void closeOutputStream(String relativePath, OutputStream out) throws IOException { + if (zout != null) { + zout.closeEntry(); + } else if (basePath != null) { + out.close(); + } else { + throw new UnsupportedOperationException(); + } + } + + protected Session login(String workspaceName) { + if (bundleContext != null) {// local + return CmsJcrUtils.openDataAdminSession(repository, workspaceName); + } else {// remote + try { + return repository.login(workspaceName); + } catch (RepositoryException e) { + throw new JcrException(e); + } + } + } + + public final static void main(String[] args) throws Exception { + if (args.length == 0) { + printUsage("No argument"); + System.exit(1); + } + URI uri = new URI(args[0]); + Repository repository = createRemoteRepository(uri); + Path basePath = args.length > 1 ? Paths.get(args[1]) : Paths.get(System.getProperty("user.dir")); + if (!Files.exists(basePath)) + Files.createDirectories(basePath); + LogicalBackup backup = new LogicalBackup(null, repository, basePath); + backup.run(); + } + + private static void printUsage(String errorMessage) { + if (errorMessage != null) + System.err.println(errorMessage); + System.out.println("Usage: LogicalBackup []"); + + } + + protected static Repository createRemoteRepository(URI uri) throws RepositoryException { + RepositoryFactory repositoryFactory = new ClientDavexRepositoryFactory(); + Map params = new HashMap(); + params.put(ClientDavexRepositoryFactory.JACKRABBIT_DAVEX_URI, uri.toString()); + // TODO make it configurable + params.put(ClientDavexRepositoryFactory.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, CmsConstants.SYS_WORKSPACE); + return repositoryFactory.getRepository(params); + } + + public void performSoftwareBackup(BundleContext bundleContext) { + String bootBasePath = OSGI_BASE + "boot"; + Bundle[] bundles = bundleContext.getBundles(); + for (Bundle bundle : bundles) { + String relativePath = bootBasePath + "/" + bundle.getSymbolicName() + ".jar"; + Dictionary headers = bundle.getHeaders(); + Manifest manifest = new Manifest(); + Enumeration headerKeys = headers.keys(); + while (headerKeys.hasMoreElements()) { + String headerKey = headerKeys.nextElement(); + String headerValue = headers.get(headerKey); + manifest.getMainAttributes().putValue(headerKey, headerValue); + } + try (JarOutputStream jarOut = new JarOutputStream(openOutputStream(relativePath), manifest)) { + Enumeration resourcePaths = bundle.findEntries("/", "*", true); + resources: while (resourcePaths.hasMoreElements()) { + URL entryUrl = resourcePaths.nextElement(); + String entryPath = entryUrl.getPath(); + if (entryPath.equals("")) + continue resources; + if (entryPath.endsWith("/")) + continue resources; + String entryName = entryPath.substring(1);// remove first '/' + if (entryUrl.getPath().equals("/META-INF/")) + continue resources; + if (entryUrl.getPath().equals("/META-INF/MANIFEST.MF")) + continue resources; + // dev + if (entryUrl.getPath().startsWith("/target")) + continue resources; + if (entryUrl.getPath().startsWith("/src")) + continue resources; + if (entryUrl.getPath().startsWith("/ext")) + continue resources; + + if (entryName.startsWith("bin/")) {// dev + entryName = entryName.substring("bin/".length()); + } + + ZipEntry entry = new ZipEntry(entryName); + try (InputStream in = entryUrl.openStream()) { + try { + jarOut.putNextEntry(entry); + } catch (ZipException e) {// duplicate + continue resources; + } + IOUtils.copy(in, jarOut); + jarOut.closeEntry(); +// log.info(entryUrl); + } catch (FileNotFoundException e) { + log.warn(entryUrl + ": " + e.getMessage()); + } + } + } catch (IOException e1) { + throw new RuntimeException("Cannot export bundle " + bundle, e1); + } + } + if (log.isDebugEnabled()) + log.debug(bundles.length + " OSGi bundles exported to " + bootBasePath); + + } + + protected synchronized void markBackupFailed(Object message, Exception e) { + log.error(message, e); + backupFailed = true; + notifyAll(); + if (executorService != null) + executorService.shutdownNow(); + } + + protected boolean isBackupFailed() { + return backupFailed; + } +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/LogicalRestore.java b/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/LogicalRestore.java new file mode 100644 index 000000000..122c96701 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/LogicalRestore.java @@ -0,0 +1,85 @@ +package org.argeo.maintenance.backup; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; + +import javax.jcr.ImportUUIDBehavior; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.jcr.CmsJcrUtils; +import org.argeo.jcr.Jcr; +import org.argeo.jcr.JcrException; +import org.argeo.jcr.JcrUtils; +import org.osgi.framework.BundleContext; + +/** Restores a backup in the format defined by {@link LogicalBackup}. */ +public class LogicalRestore implements Runnable { + private final static CmsLog log = CmsLog.getLog(LogicalRestore.class); + + private final Repository repository; + private final BundleContext bundleContext; + private final Path basePath; + + public LogicalRestore(BundleContext bundleContext, Repository repository, Path basePath) { + this.repository = repository; + this.basePath = basePath; + this.bundleContext = bundleContext; + } + + @Override + public void run() { + Path workspaces = basePath.resolve(LogicalBackup.WORKSPACES_BASE); + try { + // import jcr:system first +// Session defaultSession = NodeUtils.openDataAdminSession(repository, null); +// try (DirectoryStream xmls = Files.newDirectoryStream( +// workspaces.resolve(NodeConstants.SYS_WORKSPACE + LogicalBackup.JCR_VERSION_STORAGE_PATH), +// "*.xml")) { +// for (Path xml : xmls) { +// try (InputStream in = Files.newInputStream(xml)) { +// defaultSession.getWorkspace().importXML(LogicalBackup.JCR_VERSION_STORAGE_PATH, in, +// ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING); +// if (log.isDebugEnabled()) +// log.debug("Restored " + xml + " to " + defaultSession.getWorkspace().getName() + ":"); +// } +// } +// } finally { +// Jcr.logout(defaultSession); +// } + + // non-system content + try (DirectoryStream workspaceDirs = Files.newDirectoryStream(workspaces)) { + for (Path workspacePath : workspaceDirs) { + String workspaceName = workspacePath.getFileName().toString(); + Session session = JcrUtils.loginOrCreateWorkspace(repository, workspaceName); + try (DirectoryStream xmls = Files.newDirectoryStream(workspacePath, "*.xml")) { + xmls: for (Path xml : xmls) { + if (xml.getFileName().toString().startsWith("rep:")) + continue xmls; + try (InputStream in = Files.newInputStream(xml)) { + session.getWorkspace().importXML("/", in, + ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING); + if (log.isDebugEnabled()) + log.debug("Restored " + xml + " to workspace " + workspaceName); + } + } + } finally { + Jcr.logout(session); + } + } + } + } catch (IOException e) { + throw new RuntimeException("Cannot restore backup from " + basePath, e); + } catch (RepositoryException e) { + throw new JcrException("Cannot restore backup from " + basePath, e); + } + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/package-info.java b/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/package-info.java new file mode 100644 index 000000000..a61e19bd4 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/package-info.java @@ -0,0 +1,2 @@ +/** Argeo Node backup utilities. */ +package org.argeo.maintenance.backup; \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/internal/Activator.java b/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/internal/Activator.java new file mode 100644 index 000000000..ef40ab3a3 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/internal/Activator.java @@ -0,0 +1,27 @@ +package org.argeo.maintenance.internal; + +import java.nio.file.Path; +import java.nio.file.Paths; + +import javax.jcr.Repository; + +import org.argeo.maintenance.backup.LogicalBackup; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; + +public class Activator implements BundleActivator { + + @Override + public void start(BundleContext context) throws Exception { + // Start backup + Repository repository = context.getService(context.getServiceReference(Repository.class)); + Path basePath = Paths.get(System.getProperty("user.dir"), "backup"); + LogicalBackup backup = new LogicalBackup(context, repository, basePath); + backup.run(); + } + + @Override + public void stop(BundleContext context) throws Exception { + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/package-info.java b/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/package-info.java new file mode 100644 index 000000000..1ce974c6f --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/maintenance/package-info.java @@ -0,0 +1,2 @@ +/** Utilities for the maintenance of an Argeo Node. */ +package org.argeo.maintenance; \ No newline at end of file diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAccessControlProvider.java b/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAccessControlProvider.java new file mode 100644 index 000000000..bffe531a1 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAccessControlProvider.java @@ -0,0 +1,30 @@ +package org.argeo.security.jackrabbit; + +import java.security.Principal; +import java.util.Map; +import java.util.Set; + +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.apache.jackrabbit.core.security.authorization.acl.ACLProvider; + +/** Argeo specific access control provider */ +public class ArgeoAccessControlProvider extends ACLProvider { + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public void init(Session systemSession, Map configuration) throws RepositoryException { + if (!configuration.containsKey(PARAM_ALLOW_UNKNOWN_PRINCIPALS)) + configuration.put(PARAM_ALLOW_UNKNOWN_PRINCIPALS, "true"); + if (!configuration.containsKey(PARAM_OMIT_DEFAULT_PERMISSIONS)) + configuration.put(PARAM_OMIT_DEFAULT_PERMISSIONS, "true"); + super.init(systemSession, configuration); + } + + @Override + public boolean canAccessRoot(Set principals) throws RepositoryException { + return super.canAccessRoot(principals); + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAccessManager.java b/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAccessManager.java new file mode 100644 index 000000000..7464078d8 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAccessManager.java @@ -0,0 +1,35 @@ +package org.argeo.security.jackrabbit; + +import javax.jcr.PathNotFoundException; +import javax.jcr.RepositoryException; +import javax.jcr.security.Privilege; + +import org.apache.jackrabbit.core.id.ItemId; +import org.apache.jackrabbit.core.security.DefaultAccessManager; +import org.apache.jackrabbit.spi.Path; + +/** + * Intermediary class in order to have a consistent naming in config files. Does + * nothing for the time being, but may in the future. + */ +public class ArgeoAccessManager extends DefaultAccessManager { + + @Override + public boolean canRead(Path itemPath, ItemId itemId) + throws RepositoryException { + return super.canRead(itemPath, itemId); + } + + @Override + public Privilege[] getPrivileges(String absPath) + throws PathNotFoundException, RepositoryException { + return super.getPrivileges(absPath); + } + + @Override + public boolean hasPrivileges(String absPath, Privilege[] privileges) + throws PathNotFoundException, RepositoryException { + return super.hasPrivileges(absPath, privileges); + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAuthContext.java b/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAuthContext.java new file mode 100644 index 000000000..d679c45f9 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAuthContext.java @@ -0,0 +1,37 @@ +package org.argeo.security.jackrabbit; + +import javax.security.auth.Subject; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.login.LoginContext; +import javax.security.auth.login.LoginException; + +import org.apache.jackrabbit.core.security.authentication.AuthContext; + +/** Wraps a regular {@link LoginContext}, using the proper class loader. */ +class ArgeoAuthContext implements AuthContext { + private LoginContext lc; + + public ArgeoAuthContext(String appName, Subject subject, CallbackHandler callbackHandler) { + try { + lc = new LoginContext(appName, subject, callbackHandler); + } catch (LoginException e) { + throw new IllegalStateException("Cannot configure Jackrabbit login context", e); + } + } + + @Override + public void login() throws LoginException { + lc.login(); + } + + @Override + public Subject getSubject() { + return lc.getSubject(); + } + + @Override + public void logout() throws LoginException { + lc.logout(); + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoSecurityManager.java b/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoSecurityManager.java new file mode 100644 index 000000000..36ee547e5 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoSecurityManager.java @@ -0,0 +1,163 @@ +package org.argeo.security.jackrabbit; + +import java.security.Principal; +import java.util.HashSet; +import java.util.Properties; +import java.util.Set; + +import javax.jcr.Credentials; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.security.auth.Subject; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.x500.X500Principal; + +import org.apache.jackrabbit.api.security.user.UserManager; +import org.apache.jackrabbit.core.DefaultSecurityManager; +import org.apache.jackrabbit.core.security.AMContext; +import org.apache.jackrabbit.core.security.AccessManager; +import org.apache.jackrabbit.core.security.SecurityConstants; +import org.apache.jackrabbit.core.security.SystemPrincipal; +import org.apache.jackrabbit.core.security.authentication.AuthContext; +import org.apache.jackrabbit.core.security.authentication.CallbackHandlerImpl; +import org.apache.jackrabbit.core.security.authorization.WorkspaceAccessManager; +import org.apache.jackrabbit.core.security.principal.AdminPrincipal; +import org.apache.jackrabbit.core.security.principal.PrincipalProvider; +import org.argeo.api.cms.CmsSession; +import org.argeo.api.cms.DataAdminPrincipal; +import org.argeo.api.cms.CmsLog; +import org.argeo.api.cms.AnonymousPrincipal; +import org.argeo.api.cms.CmsConstants; +import org.argeo.cms.osgi.CmsOsgiUtils; +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; + +/** Customises Jackrabbit security. */ +public class ArgeoSecurityManager extends DefaultSecurityManager { + private final static CmsLog log = CmsLog.getLog(ArgeoSecurityManager.class); + + private BundleContext cmsBundleContext = null; + + public ArgeoSecurityManager() { + if (FrameworkUtil.getBundle(CmsSession.class) != null) { + cmsBundleContext = FrameworkUtil.getBundle(CmsSession.class).getBundleContext(); + } + } + + public AuthContext getAuthContext(Credentials creds, Subject subject, String workspaceName) + throws RepositoryException { + checkInitialized(); + + CallbackHandler cbHandler = new CallbackHandlerImpl(creds, getSystemSession(), getPrincipalProviderRegistry(), + adminId, anonymousId); + String appName = "Jackrabbit"; + return new ArgeoAuthContext(appName, subject, cbHandler); + } + + @Override + public AccessManager getAccessManager(Session session, AMContext amContext) throws RepositoryException { + synchronized (getSystemSession()) { + return super.getAccessManager(session, amContext); + } + } + + @Override + public UserManager getUserManager(Session session) throws RepositoryException { + synchronized (getSystemSession()) { + return super.getUserManager(session); + } + } + + @Override + protected PrincipalProvider createDefaultPrincipalProvider(Properties[] moduleConfig) throws RepositoryException { + return super.createDefaultPrincipalProvider(moduleConfig); + } + + /** Called once when the session is created */ + @Override + public String getUserID(Subject subject, String workspaceName) throws RepositoryException { + boolean isAnonymous = !subject.getPrincipals(AnonymousPrincipal.class).isEmpty(); + boolean isDataAdmin = !subject.getPrincipals(DataAdminPrincipal.class).isEmpty(); + boolean isJackrabbitSystem = !subject.getPrincipals(SystemPrincipal.class).isEmpty(); + Set userPrincipal = subject.getPrincipals(X500Principal.class); + boolean isRegularUser = !userPrincipal.isEmpty(); + CmsSession cmsSession = null; + if (cmsBundleContext != null) { + cmsSession = CmsOsgiUtils.getCmsSession(cmsBundleContext, subject); + if (log.isTraceEnabled()) + log.trace("Opening JCR session for CMS session " + cmsSession); + } + + if (isAnonymous) { + if (isDataAdmin || isJackrabbitSystem || isRegularUser) + throw new IllegalStateException("Inconsistent " + subject); + else + return CmsConstants.ROLE_ANONYMOUS; + } else if (isRegularUser) {// must be before DataAdmin + if (isAnonymous || isJackrabbitSystem) + throw new IllegalStateException("Inconsistent " + subject); + else { + if (userPrincipal.size() > 1) { + StringBuilder buf = new StringBuilder(); + for (X500Principal principal : userPrincipal) + buf.append(' ').append('\"').append(principal).append('\"'); + throw new RuntimeException("Multiple user principals:" + buf); + } + return userPrincipal.iterator().next().getName(); + } + } else if (isDataAdmin) { + if (isAnonymous || isJackrabbitSystem || isRegularUser) + throw new IllegalStateException("Inconsistent " + subject); + else { + assert !subject.getPrincipals(AdminPrincipal.class).isEmpty(); + return CmsConstants.ROLE_DATA_ADMIN; + } + } else if (isJackrabbitSystem) { + if (isAnonymous || isDataAdmin || isRegularUser) + throw new IllegalStateException("Inconsistent " + subject); + else + return super.getUserID(subject, workspaceName); + } else { + throw new IllegalStateException("Unrecognized subject type: " + subject); + } + } + + @Override + protected WorkspaceAccessManager createDefaultWorkspaceAccessManager() { + WorkspaceAccessManager wam = super.createDefaultWorkspaceAccessManager(); + ArgeoWorkspaceAccessManagerImpl workspaceAccessManager = new ArgeoWorkspaceAccessManagerImpl(wam); + if (log.isTraceEnabled()) + log.trace("Created workspace access manager"); + return workspaceAccessManager; + } + + private class ArgeoWorkspaceAccessManagerImpl implements SecurityConstants, WorkspaceAccessManager { + private final WorkspaceAccessManager wam; + + public ArgeoWorkspaceAccessManagerImpl(WorkspaceAccessManager wam) { + super(); + this.wam = wam; + } + + public void init(Session systemSession) throws RepositoryException { + wam.init(systemSession); + Repository repository = systemSession.getRepository(); + if (log.isTraceEnabled()) + log.trace("Initialised workspace access manager on repository " + repository + + ", systemSession workspace: " + systemSession.getWorkspace().getName()); + } + + public void close() throws RepositoryException { + } + + public boolean grants(Set principals, String workspaceName) throws RepositoryException { + // TODO: implements finer access to workspaces + if (log.isTraceEnabled()) + log.trace("Grants " + new HashSet<>(principals) + " access to workspace '" + workspaceName + "'"); + return true; + // return wam.grants(principals, workspaceName); + } + } + +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/SystemJackrabbitLoginModule.java b/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/SystemJackrabbitLoginModule.java new file mode 100644 index 000000000..0f63957b7 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/SystemJackrabbitLoginModule.java @@ -0,0 +1,67 @@ +package org.argeo.security.jackrabbit; + +import java.util.Map; +import java.util.Set; + +import javax.security.auth.Subject; +import javax.security.auth.callback.CallbackHandler; +import javax.security.auth.login.LoginException; +import javax.security.auth.spi.LoginModule; +import javax.security.auth.x500.X500Principal; + +import org.apache.jackrabbit.core.security.AnonymousPrincipal; +import org.apache.jackrabbit.core.security.SecurityConstants; +import org.apache.jackrabbit.core.security.principal.AdminPrincipal; +import org.argeo.api.cms.DataAdminPrincipal; + +/** JAAS login module used when initiating a new Jackrabbit session. */ +public class SystemJackrabbitLoginModule implements LoginModule { + private Subject subject; + + @Override + public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, + Map options) { + this.subject = subject; + } + + @Override + public boolean login() throws LoginException { + return true; + } + + @Override + public boolean commit() throws LoginException { + Set anonPrincipal = subject + .getPrincipals(org.argeo.api.cms.AnonymousPrincipal.class); + if (!anonPrincipal.isEmpty()) { + subject.getPrincipals().add(new AnonymousPrincipal()); + return true; + } + + Set initPrincipal = subject.getPrincipals(DataAdminPrincipal.class); + if (!initPrincipal.isEmpty()) { + subject.getPrincipals().add(new AdminPrincipal(SecurityConstants.ADMIN_ID)); + return true; + } + + Set userPrincipal = subject.getPrincipals(X500Principal.class); + if (userPrincipal.isEmpty()) + throw new LoginException("Subject must be pre-authenticated"); + if (userPrincipal.size() > 1) + throw new LoginException("Multiple user principals " + userPrincipal); + + return true; + } + + @Override + public boolean abort() throws LoginException { + return true; + } + + @Override + public boolean logout() throws LoginException { + subject.getPrincipals().removeAll(subject.getPrincipals(AnonymousPrincipal.class)); + subject.getPrincipals().removeAll(subject.getPrincipals(AdminPrincipal.class)); + return true; + } +} diff --git a/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/package-info.java b/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/package-info.java new file mode 100644 index 000000000..8529cc207 --- /dev/null +++ b/jcr/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/package-info.java @@ -0,0 +1,2 @@ +/** Integration of Jackrabbit with Argeo security model. */ +package org.argeo.security.jackrabbit; \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/.classpath b/jcr/org.argeo.cms.ui/.classpath new file mode 100644 index 000000000..e801ebfb4 --- /dev/null +++ b/jcr/org.argeo.cms.ui/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/jcr/org.argeo.cms.ui/.project b/jcr/org.argeo.cms.ui/.project new file mode 100644 index 000000000..e52eb8ef7 --- /dev/null +++ b/jcr/org.argeo.cms.ui/.project @@ -0,0 +1,28 @@ + + + org.argeo.cms.ui + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.pde.ManifestBuilder + + + + + org.eclipse.pde.SchemaBuilder + + + + + + org.eclipse.pde.PluginNature + org.eclipse.jdt.core.javanature + + diff --git a/jcr/org.argeo.cms.ui/META-INF/.gitignore b/jcr/org.argeo.cms.ui/META-INF/.gitignore new file mode 100644 index 000000000..4854a41b9 --- /dev/null +++ b/jcr/org.argeo.cms.ui/META-INF/.gitignore @@ -0,0 +1 @@ +/MANIFEST.MF diff --git a/jcr/org.argeo.cms.ui/bnd.bnd b/jcr/org.argeo.cms.ui/bnd.bnd new file mode 100644 index 000000000..ed842413b --- /dev/null +++ b/jcr/org.argeo.cms.ui/bnd.bnd @@ -0,0 +1,22 @@ +Bundle-Activator: org.argeo.cms.ui.internal.Activator +Bundle-ActivationPolicy: lazy + +Import-Package: org.eclipse.swt,\ +org.eclipse.jface.window,\ +org.eclipse.core.commands,\ +javax.jcr.security,\ +org.eclipse.rap.fileupload;version="[2.1,4)",\ +org.eclipse.rap.rwt;version="[2.1,4)",\ +org.eclipse.rap.rwt.application;version="[2.1,4)",\ +org.eclipse.rap.rwt.client;version="[2.1,4)",\ +org.eclipse.rap.rwt.client.service;version="[2.1,4)",\ +org.eclipse.rap.rwt.service;version="[2.1,4)",\ +org.eclipse.rap.rwt.widgets;version="[2.1,4)",\ +org.osgi.*;version=0.0.0,\ +* + +## TODO: in order to enable single sourcing, we have introduced dummy RAP packages +# in the RCP specific bundle org.argeo.eclipse.ui.rap. +# this was working with RAP 2.x but since we upgrade Rap to 3.1.x, +# there is a version range issue cause org.argeo.eclipse.ui.rap bundle is still in 2.x +# We enable large RAP version range as a workaround that must be fixed \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/build.properties b/jcr/org.argeo.cms.ui/build.properties new file mode 100644 index 000000000..c6baffa00 --- /dev/null +++ b/jcr/org.argeo.cms.ui/build.properties @@ -0,0 +1,5 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + icons/ diff --git a/jcr/org.argeo.cms.ui/icons/loading.gif b/jcr/org.argeo.cms.ui/icons/loading.gif new file mode 100644 index 000000000..3288d1035 Binary files /dev/null and b/jcr/org.argeo.cms.ui/icons/loading.gif differ diff --git a/jcr/org.argeo.cms.ui/icons/noPic-goldenRatio-640px.png b/jcr/org.argeo.cms.ui/icons/noPic-goldenRatio-640px.png new file mode 100644 index 000000000..039650638 Binary files /dev/null and b/jcr/org.argeo.cms.ui/icons/noPic-goldenRatio-640px.png differ diff --git a/jcr/org.argeo.cms.ui/icons/noPic-square-640px.png b/jcr/org.argeo.cms.ui/icons/noPic-square-640px.png new file mode 100644 index 000000000..8e3abb518 Binary files /dev/null and b/jcr/org.argeo.cms.ui/icons/noPic-square-640px.png differ diff --git a/jcr/org.argeo.cms.ui/pom.xml b/jcr/org.argeo.cms.ui/pom.xml new file mode 100644 index 000000000..33053d25e --- /dev/null +++ b/jcr/org.argeo.cms.ui/pom.xml @@ -0,0 +1,68 @@ + + + 4.0.0 + + org.argeo.commons + jcr + 2.3-SNAPSHOT + .. + + org.argeo.cms.ui + CMS UI + jar + + + org.argeo.commons + org.argeo.cms + 2.3-SNAPSHOT + + + org.argeo.commons + org.argeo.cms.swt + 2.3-SNAPSHOT + + + org.argeo.commons + org.argeo.cms.jcr + 2.3-SNAPSHOT + + + + + org.argeo.commons.rap + org.argeo.swt.specific.rap + 2.3-SNAPSHOT + provided + + + + + org.argeo.tp.rap.e4 + org.eclipse.rap.rwt + provided + + + org.argeo.tp.rap.e4 + org.eclipse.core.commands + provided + + + org.argeo.tp.rap.e4 + org.eclipse.rap.jface + provided + + + + + org.argeo.tp.rap.e4 + org.eclipse.rap.filedialog + provided + + + org.argeo.tp.rap.e4 + org.eclipse.rap.fileupload + provided + + + + \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsEditionEvent.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsEditionEvent.java new file mode 100644 index 000000000..872142bca --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsEditionEvent.java @@ -0,0 +1,23 @@ +package org.argeo.cms.ui; + +import java.util.EventObject; + +/** Notify of the edition lifecycle */ +public class CmsEditionEvent extends EventObject { + private static final long serialVersionUID = 950914736016693110L; + + public final static Integer START_EDITING = 0; + public final static Integer STOP_EDITING = 1; + + private final Integer type; + + public CmsEditionEvent(Object source, Integer type) { + super(source); + this.type = type; + } + + public Integer getType() { + return type; + } + +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsUiConstants.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsUiConstants.java new file mode 100644 index 000000000..9df61dcca --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsUiConstants.java @@ -0,0 +1,28 @@ +package org.argeo.cms.ui; + +import org.argeo.api.cms.Cms2DSize; + +/** Commons constants */ +@Deprecated +public interface CmsUiConstants { + // DATAKEYS +// public final static String STYLE = EclipseUiConstants.CSS_CLASS; +// public final static String MARKUP = EclipseUiConstants.MARKUP_SUPPORT; + @Deprecated + /* RWT.CUSTOM_ITEM_HEIGHT */ + public final static String ITEM_HEIGHT = "org.eclipse.rap.rwt.customItemHeight"; + + // EVENT DETAILS + @Deprecated + /* RWT.HYPERLINK */ + public final static int HYPERLINK = 1 << 26; + + // STANDARD RESOURCES + public final static String LOADING_IMAGE = "icons/loading.gif"; + + public final static String NO_IMAGE = "icons/noPic-square-640px.png"; + public final static Cms2DSize NO_IMAGE_SIZE = new Cms2DSize(320, 320); + public final static Float NO_IMAGE_RATIO = 1f; + // MISCEALLENEOUS + String DATE_TIME_FORMAT = "dd/MM/yyyy, HH:mm"; +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsUiProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsUiProvider.java new file mode 100644 index 000000000..ec76321fe --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsUiProvider.java @@ -0,0 +1,30 @@ +package org.argeo.cms.ui; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.api.cms.MvcProvider; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + +/** Stateless factory building an SWT user interface given a JCR context. */ +@FunctionalInterface +public interface CmsUiProvider extends MvcProvider { + /** + * Initialises a user interface. + * + * @param parent the parent composite + * @param context a context node (holding the JCR underlying session), or null + */ + Control createUi(Composite parent, Node context) throws RepositoryException; + + @Override + default Control createUiPart(Composite parent, Node context) { + try { + return createUi(parent, context); + } catch (RepositoryException e) { + throw new IllegalStateException("Cannot create UI for context " + context, e); + } + } + +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/LifeCycleUiProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/LifeCycleUiProvider.java new file mode 100644 index 000000000..5d77c156c --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/LifeCycleUiProvider.java @@ -0,0 +1,11 @@ +package org.argeo.cms.ui; + +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +/** CmsUiProvider notified of initialisation with a system session. */ +public interface LifeCycleUiProvider extends CmsUiProvider { + public void init(Session adminSession) throws RepositoryException; + + public void destroy(); +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/AbstractFormPart.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/AbstractFormPart.java new file mode 100644 index 000000000..4ce468826 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/AbstractFormPart.java @@ -0,0 +1,108 @@ +package org.argeo.cms.ui.eclipse.forms; +/** + * AbstractFormPart implements IFormPart interface and can be used as a + * convenient base class for concrete form parts. If a method contains + * code that must be called, look for instructions to call 'super' + * when overriding. + * + * @see org.eclipse.ui.forms.widgets.Section + * @since 1.0 + */ +public abstract class AbstractFormPart implements IFormPart { + private IManagedForm managedForm; + private boolean dirty = false; + private boolean stale = true; + /** + * @see org.eclipse.ui.forms.IFormPart#initialize(org.eclipse.ui.forms.IManagedForm) + */ + public void initialize(IManagedForm form) { + this.managedForm = form; + } + /** + * Returns the form that manages this part. + * + * @return the managed form + */ + public IManagedForm getManagedForm() { + return managedForm; + } + /** + * Disposes the part. Subclasses should override to release any system + * resources. + */ + public void dispose() { + } + /** + * Commits the part. Subclasses should call 'super' when overriding. + * + * @param onSave + * true if the request to commit has arrived as a + * result of the 'save' action. + */ + public void commit(boolean onSave) { + dirty = false; + } + /** + * Sets the overall form input. Subclases may elect to override the method + * and adjust according to the form input. + * + * @param input + * the form input object + * @return false + */ + public boolean setFormInput(Object input) { + return false; + } + /** + * Instructs the part to grab keyboard focus. + */ + public void setFocus() { + } + /** + * Refreshes the section after becoming stale (falling behind data in the + * model). Subclasses must call 'super' when overriding this method. + */ + public void refresh() { + stale = false; + // since we have refreshed, any changes we had in the + // part are gone and we are not dirty + dirty = false; + } + /** + * Marks the part dirty. Subclasses should call this method as a result of + * user interaction with the widgets in the section. + */ + public void markDirty() { + dirty = true; + managedForm.dirtyStateChanged(); + } + /** + * Tests whether the part is dirty i.e. its widgets have state that is + * newer than the data in the model. + * + * @return true if the part is dirty, false + * otherwise. + */ + public boolean isDirty() { + return dirty; + } + /** + * Tests whether the part is stale i.e. its widgets have state that is + * older than the data in the model. + * + * @return true if the part is stale, false + * otherwise. + */ + public boolean isStale() { + return stale; + } + /** + * Marks the part stale. Subclasses should call this method as a result of + * model notification that indicates that the content of the section is no + * longer in sync with the model. + */ + public void markStale() { + stale = true; + managedForm.staleStateChanged(); + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormColors.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormColors.java new file mode 100644 index 000000000..32b031b86 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormColors.java @@ -0,0 +1,730 @@ +package org.argeo.cms.ui.eclipse.forms; + +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.resource.LocalResourceManager; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.RGB; +//import org.eclipse.swt.internal.graphics.Graphics; +import org.eclipse.swt.widgets.Display; + +/** + * Manages colors that will be applied to forms and form widgets. The colors are + * chosen to make the widgets look correct in the editor area. If a different + * set of colors is needed, subclass this class and override 'initialize' and/or + * 'initializeColors'. + * + * @since 1.0 + */ +public class FormColors { + /** + * Key for the form title foreground color. + * + * @deprecated use IFormColors.TITLE. + */ + public static final String TITLE = IFormColors.TITLE; + + /** + * Key for the tree/table border color. + * + * @deprecated use IFormColors.BORDER + */ + public static final String BORDER = IFormColors.BORDER; + + /** + * Key for the section separator color. + * + * @deprecated use IFormColors.SEPARATOR. + */ + public static final String SEPARATOR = IFormColors.SEPARATOR; + + /** + * Key for the section title bar background. + * + * @deprecated use IFormColors.TB_BG + */ + public static final String TB_BG = IFormColors.TB_BG; + + /** + * Key for the section title bar foreground. + * + * @deprecated use IFormColors.TB_FG + */ + public static final String TB_FG = IFormColors.TB_FG; + + /** + * Key for the section title bar gradient. + * + * @deprecated use IFormColors.TB_GBG + */ + public static final String TB_GBG = IFormColors.TB_GBG; + + /** + * Key for the section title bar border. + * + * @deprecated use IFormColors.TB_BORDER. + */ + public static final String TB_BORDER = IFormColors.TB_BORDER; + + /** + * Key for the section toggle color. Since 3.1, this color is used for all + * section styles. + * + * @deprecated use IFormColors.TB_TOGGLE. + */ + public static final String TB_TOGGLE = IFormColors.TB_TOGGLE; + + /** + * Key for the section toggle hover color. + * + * @deprecated use IFormColors.TB_TOGGLE_HOVER. + */ + public static final String TB_TOGGLE_HOVER = IFormColors.TB_TOGGLE_HOVER; + + protected Map colorRegistry = new HashMap(10); + + private LocalResourceManager resources; + + protected Color background; + + protected Color foreground; + + private boolean shared; + + protected Display display; + + protected Color border; + + /** + * Creates form colors using the provided display. + * + * @param display + * the display to use + */ + public FormColors(Display display) { + this.display = display; + initialize(); + } + + /** + * Returns the display used to create colors. + * + * @return the display + */ + public Display getDisplay() { + return display; + } + + /** + * Initializes the colors. Subclasses can override this method to change the + * way colors are created. Alternatively, only the color table can be + * modified by overriding initializeColorTable(). + * + * @see #initializeColorTable + */ + protected void initialize() { + background = display.getSystemColor(SWT.COLOR_LIST_BACKGROUND); + foreground = display.getSystemColor(SWT.COLOR_LIST_FOREGROUND); + initializeColorTable(); + updateBorderColor(); + } + + /** + * Allocates colors for the following keys: BORDER, SEPARATOR and + * TITLE. Subclasses can override to allocate these colors differently. + */ + protected void initializeColorTable() { + createTitleColor(); + createColor(IFormColors.SEPARATOR, getColor(IFormColors.TITLE).getRGB()); + RGB black = getSystemColor(SWT.COLOR_BLACK); + RGB borderRGB = getSystemColor(SWT.COLOR_TITLE_INACTIVE_BACKGROUND_GRADIENT); + createColor(IFormColors.BORDER, blend(borderRGB, black, 80)); + } + + /** + * Allocates colors for the section tool bar (all the keys that start with + * TB). Since these colors are only needed when TITLE_BAR style is used with + * the Section widget, they are not needed all the time and are allocated on + * demand. Consequently, this method will do nothing if the colors have been + * already initialized. Call this method prior to using colors with the TB + * keys to ensure they are available. + */ + public void initializeSectionToolBarColors() { + if (colorRegistry.containsKey(IFormColors.TB_BG)) + return; + createTitleBarGradientColors(); + createTitleBarOutlineColors(); + createTwistieColors(); + } + + /** + * Allocates additional colors for the form header, namely background + * gradients, bottom separator keylines and DND highlights. Since these + * colors are only needed for clients that want to use these particular + * style of header rendering, they are not needed all the time and are + * allocated on demand. Consequently, this method will do nothing if the + * colors have been already initialized. Call this method prior to using + * color keys with the H_ prefix to ensure they are available. + */ + protected void initializeFormHeaderColors() { + if (colorRegistry.containsKey(IFormColors.H_BOTTOM_KEYLINE2)) + return; + createFormHeaderColors(); + } + + /** + * Returns the RGB value of the system color represented by the code + * argument, as defined in SWT class. + * + * @param code + * the system color constant as defined in SWT + * class. + * @return the RGB value of the system color + */ + public RGB getSystemColor(int code) { + return getDisplay().getSystemColor(code).getRGB(); + } + + /** + * Creates the color for the specified key using the provided RGB object. + * The color object will be returned and also put into the registry. When + * the class is disposed, the color will be disposed with it. + * + * @param key + * the unique color key + * @param rgb + * the RGB object + * @return the allocated color object + */ + public Color createColor(String key, RGB rgb) { + // RAP [rh] changes due to missing Color constructor +// Color c = getResourceManager().createColor(rgb); +// Color prevC = (Color) colorRegistry.get(key); +// if (prevC != null && !prevC.isDisposed()) +// getResourceManager().destroyColor(prevC.getRGB()); +// Color c = Graphics.getColor(rgb); + Color c = new Color(display, rgb); + colorRegistry.put(key, c); + return c; + } + + /** + * Creates a color that can be used for areas of the form that is inactive. + * These areas can contain images, links, controls and other content but are + * considered auxilliary to the main content area. + * + *

+ * The color should not be disposed because it is managed by this class. + * + * @return the inactive form color + */ + public Color getInactiveBackground() { + String key = "__ncbg__"; //$NON-NLS-1$ + Color color = getColor(key); + if (color == null) { + RGB sel = getSystemColor(SWT.COLOR_LIST_SELECTION); + // a blend of 95% white and 5% list selection system color + RGB ncbg = blend(sel, getSystemColor(SWT.COLOR_WHITE), 5); + color = createColor(key, ncbg); + } + return color; + } + + /** + * Creates the color for the specified key using the provided RGB values. + * The color object will be returned and also put into the registry. If + * there is already another color object under the same key in the registry, + * the existing object will be disposed. When the class is disposed, the + * color will be disposed with it. + * + * @param key + * the unique color key + * @param r + * red value + * @param g + * green value + * @param b + * blue value + * @return the allocated color object + */ + public Color createColor(String key, int r, int g, int b) { + return createColor(key, new RGB(r,g,b)); + } + + /** + * Computes the border color relative to the background. Allocated border + * color is designed to work well with white. Otherwise, stanard widget + * background color will be used. + */ + protected void updateBorderColor() { + if (isWhiteBackground()) + border = getColor(IFormColors.BORDER); + else { + border = display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND); + Color bg = getImpliedBackground(); + if (border.getRed() == bg.getRed() + && border.getGreen() == bg.getGreen() + && border.getBlue() == bg.getBlue()) + border = display.getSystemColor(SWT.COLOR_WIDGET_DARK_SHADOW); + } + } + + /** + * Sets the background color. All the toolkits that use this class will + * share the same background. + * + * @param bg + * background color + */ + public void setBackground(Color bg) { + this.background = bg; + updateBorderColor(); + updateFormHeaderColors(); + } + + /** + * Sets the foreground color. All the toolkits that use this class will + * share the same foreground. + * + * @param fg + * foreground color + */ + public void setForeground(Color fg) { + this.foreground = fg; + } + + /** + * Returns the current background color. + * + * @return the background color + */ + public Color getBackground() { + return background; + } + + /** + * Returns the current foreground color. + * + * @return the foreground color + */ + public Color getForeground() { + return foreground; + } + + /** + * Returns the computed border color. Border color depends on the background + * and is recomputed whenever the background changes. + * + * @return the current border color + */ + public Color getBorderColor() { + return border; + } + + /** + * Tests if the background is white. White background has RGB value + * 255,255,255. + * + * @return true if background is white, false + * otherwise. + */ + public boolean isWhiteBackground() { + Color bg = getImpliedBackground(); + return bg.getRed() == 255 && bg.getGreen() == 255 + && bg.getBlue() == 255; + } + + /** + * Returns the color object for the provided key or null if + * not in the registry. + * + * @param key + * the color key + * @return color object if found, or null if not. + */ + public Color getColor(String key) { + if (key.startsWith(IFormColors.TB_PREFIX)) + initializeSectionToolBarColors(); + else if (key.startsWith(IFormColors.H_PREFIX)) + initializeFormHeaderColors(); + return (Color) colorRegistry.get(key); + } + + /** + * Disposes all the colors in the registry. + */ + public void dispose() { + if (resources != null) + resources.dispose(); + resources = null; + colorRegistry = null; + } + + /** + * Marks the colors shared. This prevents toolkits that share this object + * from disposing it. + */ + public void markShared() { + this.shared = true; + } + + /** + * Tests if the colors are shared. + * + * @return true if shared, false otherwise. + */ + public boolean isShared() { + return shared; + } + + /** + * Blends c1 and c2 based in the provided ratio. + * + * @param c1 + * first color + * @param c2 + * second color + * @param ratio + * percentage of the first color in the blend (0-100) + * @return the RGB value of the blended color + */ + public static RGB blend(RGB c1, RGB c2, int ratio) { + int r = blend(c1.red, c2.red, ratio); + int g = blend(c1.green, c2.green, ratio); + int b = blend(c1.blue, c2.blue, ratio); + return new RGB(r, g, b); + } + + /** + * Tests the source RGB for range. + * + * @param rgb + * the tested RGB + * @param from + * range start (excluding the value itself) + * @param to + * range end (excluding the value itself) + * @return true if at least one of the primary colors in the + * source RGB are within the provided range, false + * otherwise. + */ + public static boolean testAnyPrimaryColor(RGB rgb, int from, int to) { + if (testPrimaryColor(rgb.red, from, to)) + return true; + if (testPrimaryColor(rgb.green, from, to)) + return true; + if (testPrimaryColor(rgb.blue, from, to)) + return true; + return false; + } + + /** + * Tests the source RGB for range. + * + * @param rgb + * the tested RGB + * @param from + * range start (excluding the value itself) + * @param to + * tange end (excluding the value itself) + * @return true if at least two of the primary colors in the + * source RGB are within the provided range, false + * otherwise. + */ + public static boolean testTwoPrimaryColors(RGB rgb, int from, int to) { + int total = 0; + if (testPrimaryColor(rgb.red, from, to)) + total++; + if (testPrimaryColor(rgb.green, from, to)) + total++; + if (testPrimaryColor(rgb.blue, from, to)) + total++; + return total >= 2; + } + + /** + * Blends two primary color components based on the provided ratio. + * + * @param v1 + * first component + * @param v2 + * second component + * @param ratio + * percentage of the first component in the blend + * @return + */ + private static int blend(int v1, int v2, int ratio) { + int b = (ratio * v1 + (100 - ratio) * v2) / 100; + return Math.min(255, b); + } + + private Color getImpliedBackground() { + if (getBackground() != null) + return getBackground(); + return getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND); + } + + private static boolean testPrimaryColor(int value, int from, int to) { + return value > from && value < to; + } + + private void createTitleColor() { + /* + * RGB rgb = getSystemColor(SWT.COLOR_LIST_SELECTION); // test too light + * if (testTwoPrimaryColors(rgb, 120, 151)) rgb = blend(rgb, BLACK, 80); + * else if (testTwoPrimaryColors(rgb, 150, 256)) rgb = blend(rgb, BLACK, + * 50); createColor(TITLE, rgb); + */ + RGB bg = getImpliedBackground().getRGB(); + RGB listSelection = getSystemColor(SWT.COLOR_LIST_SELECTION); + RGB listForeground = getSystemColor(SWT.COLOR_LIST_FOREGROUND); + RGB rgb = listSelection; + + // Group 1 + // Rule: If at least 2 of the LIST_SELECTION RGB values are equal to or + // between 0 and 120, then use 100% LIST_SELECTION as it is (no + // additions) + // Examples: XP Default, Win Classic Standard, Win High Con White, Win + // Classic Marine + if (testTwoPrimaryColors(listSelection, -1, 121)) + rgb = listSelection; + // Group 2 + // When LIST_BACKGROUND = white (255, 255, 255) or not black, text + // colour = LIST_SELECTION @ 100% Opacity + 50% LIST_FOREGROUND over + // LIST_BACKGROUND + // Rule: If at least 2 of the LIST_SELECTION RGB values are equal to or + // between 121 and 255, then add 50% LIST_FOREGROUND to LIST_SELECTION + // foreground colour + // Examples: Win Vista, XP Silver, XP Olive , Win Classic Plum, OSX + // Aqua, OSX Graphite, Linux GTK + else if (testTwoPrimaryColors(listSelection, 120, 256) + || (bg.red == 0 && bg.green == 0 && bg.blue == 0)) + rgb = blend(listSelection, listForeground, 50); + // Group 3 + // When LIST_BACKGROUND = black (0, 0, 0), text colour = LIST_SELECTION + // @ 100% Opacity + 50% LIST_FOREGROUND over LIST_BACKGROUND + // Rule: If LIST_BACKGROUND = 0, 0, 0, then add 50% LIST_FOREGROUND to + // LIST_SELECTION foreground colour + // Examples: Win High Con Black, Win High Con #1, Win High Con #2 + // (covered in the second part of the OR clause above) + createColor(IFormColors.TITLE, rgb); + } + + private void createTwistieColors() { + RGB rgb = getColor(IFormColors.TITLE).getRGB(); + RGB white = getSystemColor(SWT.COLOR_WHITE); + createColor(TB_TOGGLE, rgb); + rgb = blend(rgb, white, 60); + createColor(TB_TOGGLE_HOVER, rgb); + } + + private void createTitleBarGradientColors() { + RGB tbBg = getSystemColor(SWT.COLOR_TITLE_BACKGROUND); + RGB bg = getImpliedBackground().getRGB(); + + // Group 1 + // Rule: If at least 2 of the RGB values are equal to or between 180 and + // 255, then apply specified opacity for Group 1 + // Examples: Vista, XP Silver, Wn High Con #2 + // Gradient Bottom = TITLE_BACKGROUND @ 30% Opacity over LIST_BACKGROUND + // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND + if (testTwoPrimaryColors(tbBg, 179, 256)) + tbBg = blend(tbBg, bg, 30); + + // Group 2 + // Rule: If at least 2 of the RGB values are equal to or between 121 and + // 179, then apply specified opacity for Group 2 + // Examples: XP Olive, OSX Graphite, Linux GTK, Wn High Con Black + // Gradient Bottom = TITLE_BACKGROUND @ 20% Opacity over LIST_BACKGROUND + // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND + else if (testTwoPrimaryColors(tbBg, 120, 180)) + tbBg = blend(tbBg, bg, 20); + + // Group 3 + // Rule: Everything else + // Examples: XP Default, Wn Classic Standard, Wn Marine, Wn Plum, OSX + // Aqua, Wn High Con White, Wn High Con #1 + // Gradient Bottom = TITLE_BACKGROUND @ 10% Opacity over LIST_BACKGROUND + // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND + else { + tbBg = blend(tbBg, bg, 10); + } + + createColor(IFormColors.TB_BG, tbBg); + + // for backward compatibility + createColor(TB_GBG, tbBg); + } + + private void createTitleBarOutlineColors() { + // title bar outline - border color + RGB tbBorder = getSystemColor(SWT.COLOR_TITLE_BACKGROUND); + RGB bg = getImpliedBackground().getRGB(); + // Group 1 + // Rule: If at least 2 of the RGB values are equal to or between 180 and + // 255, then apply specified opacity for Group 1 + // Examples: Vista, XP Silver, Wn High Con #2 + // Keyline = TITLE_BACKGROUND @ 70% Opacity over LIST_BACKGROUND + if (testTwoPrimaryColors(tbBorder, 179, 256)) + tbBorder = blend(tbBorder, bg, 70); + + // Group 2 + // Rule: If at least 2 of the RGB values are equal to or between 121 and + // 179, then apply specified opacity for Group 2 + // Examples: XP Olive, OSX Graphite, Linux GTK, Wn High Con Black + + // Keyline = TITLE_BACKGROUND @ 50% Opacity over LIST_BACKGROUND + else if (testTwoPrimaryColors(tbBorder, 120, 180)) + tbBorder = blend(tbBorder, bg, 50); + + // Group 3 + // Rule: Everything else + // Examples: XP Default, Wn Classic Standard, Wn Marine, Wn Plum, OSX + // Aqua, Wn High Con White, Wn High Con #1 + + // Keyline = TITLE_BACKGROUND @ 30% Opacity over LIST_BACKGROUND + else { + tbBorder = blend(tbBorder, bg, 30); + } + createColor(FormColors.TB_BORDER, tbBorder); + } + + private void updateFormHeaderColors() { + if (colorRegistry.containsKey(IFormColors.H_GRADIENT_END)) { + disposeIfFound(IFormColors.H_GRADIENT_END); + disposeIfFound(IFormColors.H_GRADIENT_START); + disposeIfFound(IFormColors.H_BOTTOM_KEYLINE1); + disposeIfFound(IFormColors.H_BOTTOM_KEYLINE2); + disposeIfFound(IFormColors.H_HOVER_LIGHT); + disposeIfFound(IFormColors.H_HOVER_FULL); + initializeFormHeaderColors(); + } + } + + private void disposeIfFound(String key) { + Color color = getColor(key); + if (color != null) { + colorRegistry.remove(key); + // RAP [rh] changes due to missing Color#dispose() +// color.dispose(); + } + } + + private void createFormHeaderColors() { + createFormHeaderGradientColors(); + createFormHeaderKeylineColors(); + createFormHeaderDNDColors(); + } + + private void createFormHeaderGradientColors() { + RGB titleBg = getSystemColor(SWT.COLOR_TITLE_BACKGROUND); + Color bgColor = getImpliedBackground(); + RGB bg = bgColor.getRGB(); + RGB bottom, top; + // Group 1 + // Rule: If at least 2 of the RGB values are equal to or between 180 and + // 255, then apply specified opacity for Group 1 + // Examples: Vista, XP Silver, Wn High Con #2 + // Gradient Bottom = TITLE_BACKGROUND @ 30% Opacity over LIST_BACKGROUND + // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND + if (testTwoPrimaryColors(titleBg, 179, 256)) { + bottom = blend(titleBg, bg, 30); + top = bg; + } + + // Group 2 + // Rule: If at least 2 of the RGB values are equal to or between 121 and + // 179, then apply specified opacity for Group 2 + // Examples: XP Olive, OSX Graphite, Linux GTK, Wn High Con Black + // Gradient Bottom = TITLE_BACKGROUND @ 20% Opacity over LIST_BACKGROUND + // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND + else if (testTwoPrimaryColors(titleBg, 120, 180)) { + bottom = blend(titleBg, bg, 20); + top = bg; + } + + // Group 3 + // Rule: If at least 2 of the RGB values are equal to or between 0 and + // 120, then apply specified opacity for Group 3 + // Examples: XP Default, Wn Classic Standard, Wn Marine, Wn Plum, OSX + // Aqua, Wn High Con White, Wn High Con #1 + // Gradient Bottom = TITLE_BACKGROUND @ 10% Opacity over LIST_BACKGROUND + // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND + else { + bottom = blend(titleBg, bg, 10); + top = bg; + } + createColor(IFormColors.H_GRADIENT_END, top); + createColor(IFormColors.H_GRADIENT_START, bottom); + } + + private void createFormHeaderKeylineColors() { + RGB titleBg = getSystemColor(SWT.COLOR_TITLE_BACKGROUND); + Color bgColor = getImpliedBackground(); + RGB bg = bgColor.getRGB(); + RGB keyline2; + // H_BOTTOM_KEYLINE1 + createColor(IFormColors.H_BOTTOM_KEYLINE1, new RGB(255, 255, 255)); + + // H_BOTTOM_KEYLINE2 + // Group 1 + // Rule: If at least 2 of the RGB values are equal to or between 180 and + // 255, then apply specified opacity for Group 1 + // Examples: Vista, XP Silver, Wn High Con #2 + // Keyline = TITLE_BACKGROUND @ 70% Opacity over LIST_BACKGROUND + if (testTwoPrimaryColors(titleBg, 179, 256)) + keyline2 = blend(titleBg, bg, 70); + + // Group 2 + // Rule: If at least 2 of the RGB values are equal to or between 121 and + // 179, then apply specified opacity for Group 2 + // Examples: XP Olive, OSX Graphite, Linux GTK, Wn High Con Black + // Keyline = TITLE_BACKGROUND @ 50% Opacity over LIST_BACKGROUND + else if (testTwoPrimaryColors(titleBg, 120, 180)) + keyline2 = blend(titleBg, bg, 50); + + // Group 3 + // Rule: If at least 2 of the RGB values are equal to or between 0 and + // 120, then apply specified opacity for Group 3 + // Examples: XP Default, Wn Classic Standard, Wn Marine, Wn Plum, OSX + // Aqua, Wn High Con White, Wn High Con #1 + + // Keyline = TITLE_BACKGROUND @ 30% Opacity over LIST_BACKGROUND + else + keyline2 = blend(titleBg, bg, 30); + // H_BOTTOM_KEYLINE2 + createColor(IFormColors.H_BOTTOM_KEYLINE2, keyline2); + } + + private void createFormHeaderDNDColors() { + RGB titleBg = getSystemColor(SWT.COLOR_TITLE_BACKGROUND_GRADIENT); + Color bgColor = getImpliedBackground(); + RGB bg = bgColor.getRGB(); + RGB light, full; + // ALL Themes + // + // Light Highlight + // When *near* the 'hot' area + // Rule: If near the title in the 'hot' area, show background highlight + // TITLE_BACKGROUND_GRADIENT @ 40% + light = blend(titleBg, bg, 40); + // Full Highlight + // When *on* the title area (regions 1 and 2) + // Rule: If near the title in the 'hot' area, show background highlight + // TITLE_BACKGROUND_GRADIENT @ 60% + full = blend(titleBg, bg, 60); + // H_DND_LIGHT + // H_DND_FULL + createColor(IFormColors.H_HOVER_LIGHT, light); + createColor(IFormColors.H_HOVER_FULL, full); + } + + private LocalResourceManager getResourceManager() { + if (resources == null) + resources = new LocalResourceManager(JFaceResources.getResources()); + return resources; + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormFonts.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormFonts.java new file mode 100644 index 000000000..9e931ba50 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormFonts.java @@ -0,0 +1,122 @@ +package org.argeo.cms.ui.eclipse.forms; + +import java.util.HashMap; + +import org.eclipse.jface.resource.DeviceResourceException; +import org.eclipse.jface.resource.FontDescriptor; +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.resource.LocalResourceManager; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Device; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.FontData; +//import org.eclipse.swt.internal.graphics.Graphics; +import org.eclipse.swt.widgets.Display; + +public class FormFonts { + private static FormFonts instance; + + public static FormFonts getInstance() { + if (instance == null) + instance = new FormFonts(); + return instance; + } + + private LocalResourceManager resources; + private HashMap descriptors; + + private FormFonts() { + } + + private class BoldFontDescriptor extends FontDescriptor { + private FontData[] fFontData; + + BoldFontDescriptor(Font font) { + // RAP [if] Changes due to different way of creating fonts + // fFontData = font.getFontData(); + // for (int i = 0; i < fFontData.length; i++) { + // fFontData[i].setStyle(fFontData[i].getStyle() | SWT.BOLD); + // } + FontData fontData = font.getFontData()[0]; + // Font boldFont = Graphics.getFont( fontData.getName(), + // fontData.getHeight(), + // fontData.getStyle() | SWT.BOLD ); + Font boldFont = new Font(Display.getCurrent(), fontData.getName(), fontData.getHeight(), + fontData.getStyle() | SWT.BOLD); + fFontData = boldFont.getFontData(); + } + + public boolean equals(Object obj) { + if (obj instanceof BoldFontDescriptor) { + BoldFontDescriptor desc = (BoldFontDescriptor) obj; + if (desc.fFontData.length != fFontData.length) + return false; + for (int i = 0; i < fFontData.length; i++) + if (!fFontData[i].equals(desc.fFontData[i])) + return false; + return true; + } + return false; + } + + public int hashCode() { + int hash = 0; + for (int i = 0; i < fFontData.length; i++) + hash = hash * 7 + fFontData[i].hashCode(); + return hash; + } + + public Font createFont(Device device) throws DeviceResourceException { + // RAP [if] Changes due to different way of creating fonts + return new Font(device, fFontData[0]); + // return Graphics.getFont( fFontData[ 0 ] ); + } + + public void destroyFont(Font previouslyCreatedFont) { + // RAP [if] unnecessary + // previouslyCreatedFont.dispose(); + } + } + + public Font getBoldFont(Display display, Font font) { + checkHashMaps(); + BoldFontDescriptor desc = new BoldFontDescriptor(font); + Font result = getResourceManager().createFont(desc); + descriptors.put(result, desc); + return result; + } + + public boolean markFinished(Font boldFont) { + checkHashMaps(); + BoldFontDescriptor desc = (BoldFontDescriptor) descriptors.get(boldFont); + if (desc != null) { + getResourceManager().destroyFont(desc); + if (getResourceManager().find(desc) == null) { + descriptors.remove(boldFont); + validateHashMaps(); + } + return true; + + } + // if the image was not found, dispose of it for the caller + // RAP [if] unnecessary + // boldFont.dispose(); + return false; + } + + private LocalResourceManager getResourceManager() { + if (resources == null) + resources = new LocalResourceManager(JFaceResources.getResources()); + return resources; + } + + private void checkHashMaps() { + if (descriptors == null) + descriptors = new HashMap(); + } + + private void validateHashMaps() { + if (descriptors.size() == 0) + descriptors = null; + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormToolkit.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormToolkit.java new file mode 100644 index 000000000..99271046a --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormToolkit.java @@ -0,0 +1,913 @@ +package org.argeo.cms.ui.eclipse.forms; + +import org.eclipse.jface.resource.JFaceResources; +import org.eclipse.jface.window.Window; +import org.eclipse.swt.SWT; +//import org.eclipse.swt.custom.CCombo; +import org.eclipse.swt.custom.ScrolledComposite; +import org.eclipse.swt.events.FocusAdapter; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.KeyAdapter; +import org.eclipse.swt.events.KeyEvent; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +// RAP [rh] Paint events missing +//import org.eclipse.swt.events.PaintEvent; +//import org.eclipse.swt.events.PaintListener; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Font; +//RAP [rh] GC missing +//import org.eclipse.swt.graphics.GC; +import org.eclipse.swt.graphics.Point; +//import org.eclipse.swt.graphics.RGB; +//import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +//import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Label; +//import org.eclipse.swt.widgets.Listener; +import org.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.Text; +import org.eclipse.swt.widgets.Tree; +import org.eclipse.swt.widgets.Widget; +//import org.eclipse.ui.forms.FormColors; +//import org.eclipse.ui.forms.HyperlinkGroup; +//import org.eclipse.ui.forms.IFormColors; +//import org.eclipse.ui.internal.forms.widgets.FormFonts; +//import org.eclipse.ui.internal.forms.widgets.FormUtil; + +/** + * The toolkit is responsible for creating SWT controls adapted to work in + * Eclipse forms. In addition to changing their presentation properties (fonts, + * colors etc.), various listeners are attached to make them behave correctly in + * the form context. + *

+ * In addition to being the control factory, the toolkit is also responsible for + * painting flat borders for select controls, managing hyperlink groups and + * control colors. + *

+ * The toolkit creates some of the most common controls used to populate Eclipse + * forms. Controls that must be created using their constructors, + * adapt() method is available to change its properties in the + * same way as with the supported toolkit controls. + *

+ * Typically, one toolkit object is created per workbench part (for example, an + * editor or a form wizard). The toolkit is disposed when the part is disposed. + * To conserve resources, it is possible to create one color object for the + * entire plug-in and share it between several toolkits. The plug-in is + * responsible for disposing the colors (disposing the toolkit that uses shared + * color object will not dispose the colors). + *

+ * FormToolkit is normally instantiated, but can also be subclassed if some of + * the methods needs to be modified. In those cases, super must + * be called to preserve normal behaviour. + * + * @since 1.0 + */ +public class FormToolkit { + public static final String KEY_DRAW_BORDER = "FormWidgetFactory.drawBorder"; //$NON-NLS-1$ + + public static final String TREE_BORDER = "treeBorder"; //$NON-NLS-1$ + + public static final String TEXT_BORDER = "textBorder"; //$NON-NLS-1$ + + private int borderStyle = SWT.NULL; + + private FormColors colors; + + private int orientation = Window.getDefaultOrientation(); + + // private KeyListener deleteListener; + // RAP [rh] Paint events missing +// private BorderPainter borderPainter; + + private BoldFontHolder boldFontHolder; + +// private HyperlinkGroup hyperlinkGroup; + + private boolean isDisposed = false; + + /* default */ + VisibilityHandler visibilityHandler; + + /* default */ + KeyboardHandler keyboardHandler; + + // RAP [rh] Paint events missing +// private class BorderPainter implements PaintListener { +// public void paintControl(PaintEvent event) { +// Composite composite = (Composite) event.widget; +// Control[] children = composite.getChildren(); +// for (int i = 0; i < children.length; i++) { +// Control c = children[i]; +// boolean inactiveBorder = false; +// boolean textBorder = false; +// if (!c.isVisible()) +// continue; +// /* +// * if (c.getEnabled() == false && !(c instanceof CCombo)) +// * continue; +// */ +// if (c instanceof Hyperlink) +// continue; +// Object flag = c.getData(KEY_DRAW_BORDER); +// if (flag != null) { +// if (flag.equals(Boolean.FALSE)) +// continue; +// if (flag.equals(TREE_BORDER)) +// inactiveBorder = true; +// else if (flag.equals(TEXT_BORDER)) +// textBorder = true; +// } +// if (getBorderStyle() == SWT.BORDER) { +// if (!inactiveBorder && !textBorder) { +// continue; +// } +// if (c instanceof Text || c instanceof Table +// || c instanceof Tree) +// continue; +// } +// if (!inactiveBorder +// && (c instanceof Text || c instanceof CCombo || textBorder)) { +// Rectangle b = c.getBounds(); +// GC gc = event.gc; +// gc.setForeground(c.getBackground()); +// gc.drawRectangle(b.x - 1, b.y - 1, b.width + 1, +// b.height + 1); +// // gc.setForeground(getBorderStyle() == SWT.BORDER ? colors +// // .getBorderColor() : colors.getForeground()); +// gc.setForeground(colors.getBorderColor()); +// if (c instanceof CCombo) +// gc.drawRectangle(b.x - 1, b.y - 1, b.width + 1, +// b.height + 1); +// else +// gc.drawRectangle(b.x - 1, b.y - 2, b.width + 1, +// b.height + 3); +// } else if (inactiveBorder || c instanceof Table +// || c instanceof Tree) { +// Rectangle b = c.getBounds(); +// GC gc = event.gc; +// gc.setForeground(colors.getBorderColor()); +// gc.drawRectangle(b.x - 1, b.y - 1, b.width + 1, +// b.height + 1); +// } +// } +// } +// } + + private static class VisibilityHandler extends FocusAdapter { + public void focusGained(FocusEvent e) { + Widget w = e.widget; + if (w instanceof Control) { + FormUtil.ensureVisible((Control) w); + } + } + } + + private static class KeyboardHandler extends KeyAdapter { + public void keyPressed(KeyEvent e) { + Widget w = e.widget; + if (w instanceof Control) { + if (e.doit) + FormUtil.processKey(e.keyCode, (Control) w); + } + } + } + + private class BoldFontHolder { + private Font normalFont; + + private Font boldFont; + + public BoldFontHolder() { + } + + public Font getBoldFont(Font font) { + createBoldFont(font); + return boldFont; + } + + private void createBoldFont(Font font) { + if (normalFont == null || !normalFont.equals(font)) { + normalFont = font; + dispose(); + } + if (boldFont == null) { + boldFont = FormFonts.getInstance().getBoldFont(colors.getDisplay(), + normalFont); + } + } + + public void dispose() { + if (boldFont != null) { + FormFonts.getInstance().markFinished(boldFont); + boldFont = null; + } + } + } + + /** + * Creates a toolkit that is self-sufficient (will manage its own colors). + *

+ * Clients that call this method must call {@link #dispose()} when they + * are finished using the toolkit. + * + */ + public FormToolkit(Display display) { + this(new FormColors(display)); + } + + /** + * Creates a toolkit that will use the provided (shared) colors. The toolkit + * will dispose the colors if and only if they are not marked as + * shared via the markShared() method. + *

+ * Clients that call this method must call {@link #dispose()} when they + * are finished using the toolkit. + * + * @param colors + * the shared colors + */ + public FormToolkit(FormColors colors) { + this.colors = colors; + initialize(); + } + + /** + * Creates a button as a part of the form. + * + * @param parent + * the button parent + * @param text + * an optional text for the button (can be null) + * @param style + * the button style (for example, SWT.PUSH) + * @return the button widget + */ + public Button createButton(Composite parent, String text, int style) { + Button button = new Button(parent, style | SWT.FLAT | orientation); + if (text != null) + button.setText(text); + adapt(button, true, true); + return button; + } + + /** + * Creates the composite as a part of the form. + * + * @param parent + * the composite parent + * @return the composite widget + */ + public Composite createComposite(Composite parent) { + return createComposite(parent, SWT.NULL); + } + + /** + * Creates the composite as part of the form using the provided style. + * + * @param parent + * the composite parent + * @param style + * the composite style + * @return the composite widget + */ + public Composite createComposite(Composite parent, int style) { +// Composite composite = new LayoutComposite(parent, style | orientation); + Composite composite = new Composite(parent, style | orientation); + adapt(composite); + return composite; + } + + /** + * Creats the composite that can server as a separator between various parts + * of a form. Separator height should be controlled by setting the height + * hint on the layout data for the composite. + * + * @param parent + * the separator parent + * @return the separator widget + */ +// RAP [rh] createCompositeSeparator: currently no useful implementation possible, delete? + public Composite createCompositeSeparator(Composite parent) { + final Composite composite = new Composite(parent, orientation); +// RAP [rh] GC and paint events missing +// composite.addListener(SWT.Paint, new Listener() { +// public void handleEvent(Event e) { +// if (composite.isDisposed()) +// return; +// Rectangle bounds = composite.getBounds(); +// GC gc = e.gc; +// gc.setForeground(colors.getColor(IFormColors.SEPARATOR)); +// if (colors.getBackground() != null) +// gc.setBackground(colors.getBackground()); +// gc.fillGradientRectangle(0, 0, bounds.width, bounds.height, +// false); +// } +// }); +// if (parent instanceof Section) +// ((Section) parent).setSeparatorControl(composite); + return composite; + } + + /** + * Creates a label as a part of the form. + * + * @param parent + * the label parent + * @param text + * the label text + * @return the label widget + */ + public Label createLabel(Composite parent, String text) { + return createLabel(parent, text, SWT.NONE); + } + + /** + * Creates a label as a part of the form. + * + * @param parent + * the label parent + * @param text + * the label text + * @param style + * the label style + * @return the label widget + */ + public Label createLabel(Composite parent, String text, int style) { + Label label = new Label(parent, style | orientation); + if (text != null) + label.setText(text); + adapt(label, false, false); + return label; + } + + /** + * Creates a hyperlink as a part of the form. The hyperlink will be added to + * the hyperlink group that belongs to this toolkit. + * + * @param parent + * the hyperlink parent + * @param text + * the text of the hyperlink + * @param style + * the hyperlink style + * @return the hyperlink widget + */ +// public Hyperlink createHyperlink(Composite parent, String text, int style) { +// Hyperlink hyperlink = new Hyperlink(parent, style | orientation); +// if (text != null) +// hyperlink.setText(text); +// hyperlink.addFocusListener(visibilityHandler); +// hyperlink.addKeyListener(keyboardHandler); +// hyperlinkGroup.add(hyperlink); +// return hyperlink; +// } + + /** + * Creates an image hyperlink as a part of the form. The hyperlink will be + * added to the hyperlink group that belongs to this toolkit. + * + * @param parent + * the hyperlink parent + * @param style + * the hyperlink style + * @return the image hyperlink widget + */ +// public ImageHyperlink createImageHyperlink(Composite parent, int style) { +// ImageHyperlink hyperlink = new ImageHyperlink(parent, style +// | orientation); +// hyperlink.addFocusListener(visibilityHandler); +// hyperlink.addKeyListener(keyboardHandler); +// hyperlinkGroup.add(hyperlink); +// return hyperlink; +// } + + /** + * Creates a rich text as a part of the form. + * + * @param parent + * the rich text parent + * @param trackFocus + * if true, the toolkit will monitor focus + * transfers to ensure that the hyperlink in focus is visible in + * the form. + * @return the rich text widget + * @since 1.2 + */ +// public FormText createFormText(Composite parent, boolean trackFocus) { +// FormText engine = new FormText(parent, SWT.WRAP | orientation); +// engine.marginWidth = 1; +// engine.marginHeight = 0; +// engine.setHyperlinkSettings(getHyperlinkGroup()); +// adapt(engine, trackFocus, true); +// engine.setMenu(parent.getMenu()); +// return engine; +// } + + /** + * Adapts a control to be used in a form that is associated with this + * toolkit. This involves adjusting colors and optionally adding handlers to + * ensure focus tracking and keyboard management. + * + * @param control + * a control to adapt + * @param trackFocus + * if true, form will be scrolled horizontally + * and/or vertically if needed to ensure that the control is + * visible when it gains focus. Set it to false if + * the control is not capable of gaining focus. + * @param trackKeyboard + * if true, the control that is capable of + * gaining focus will be tracked for certain keys that are + * important to the underlying form (for example, PageUp, + * PageDown, ScrollUp, ScrollDown etc.). Set it to + * false if the control is not capable of gaining + * focus or these particular key event are already used by the + * control. + */ + public void adapt(Control control, boolean trackFocus, boolean trackKeyboard) { + control.setBackground(colors.getBackground()); + control.setForeground(colors.getForeground()); +// if (control instanceof ExpandableComposite) { +// ExpandableComposite ec = (ExpandableComposite) control; +// if (ec.toggle != null) { +// if (trackFocus) +// ec.toggle.addFocusListener(visibilityHandler); +// if (trackKeyboard) +// ec.toggle.addKeyListener(keyboardHandler); +// } +// if (ec.textLabel != null) { +// if (trackFocus) +// ec.textLabel.addFocusListener(visibilityHandler); +// if (trackKeyboard) +// ec.textLabel.addKeyListener(keyboardHandler); +// } +// return; +// } + if (trackFocus) + control.addFocusListener(visibilityHandler); + if (trackKeyboard) + control.addKeyListener(keyboardHandler); + } + + /** + * Adapts a composite to be used in a form associated with this toolkit. + * + * @param composite + * the composite to adapt + */ + public void adapt(Composite composite) { + composite.setBackground(colors.getBackground()); + composite.addMouseListener(new MouseAdapter() { + public void mouseDown(MouseEvent e) { + ((Control) e.widget).setFocus(); + } + }); + if (composite.getParent() != null) + composite.setMenu(composite.getParent().getMenu()); + } + + /** + * A helper method that ensures the provided control is visible when + * ScrolledComposite is somewhere in the parent chain. If scroll bars are + * visible and the control is clipped, the client of the scrolled composite + * will be scrolled to reveal the control. + * + * @param c + * the control to reveal + */ + public static void ensureVisible(Control c) { + FormUtil.ensureVisible(c); + } + + /** + * Creates a section as a part of the form. + * + * @param parent + * the section parent + * @param sectionStyle + * the section style + * @return the section widget + */ +// public Section createSection(Composite parent, int sectionStyle) { +// Section section = new Section(parent, orientation, sectionStyle); +// section.setMenu(parent.getMenu()); +// adapt(section, true, true); +// if (section.toggle != null) { +// section.toggle.setHoverDecorationColor(colors +// .getColor(IFormColors.TB_TOGGLE_HOVER)); +// section.toggle.setDecorationColor(colors +// .getColor(IFormColors.TB_TOGGLE)); +// } +// section.setFont(boldFontHolder.getBoldFont(parent.getFont())); +// if ((sectionStyle & Section.TITLE_BAR) != 0 +// || (sectionStyle & Section.SHORT_TITLE_BAR) != 0) { +// colors.initializeSectionToolBarColors(); +// section.setTitleBarBackground(colors.getColor(IFormColors.TB_BG)); +// section.setTitleBarBorderColor(colors +// .getColor(IFormColors.TB_BORDER)); +// } +// // call setTitleBarForeground regardless as it also sets the label color +// section.setTitleBarForeground(colors +// .getColor(IFormColors.TB_TOGGLE)); +// return section; +// } + + /** + * Creates an expandable composite as a part of the form. + * + * @param parent + * the expandable composite parent + * @param expansionStyle + * the expandable composite style + * @return the expandable composite widget + */ +// public ExpandableComposite createExpandableComposite(Composite parent, +// int expansionStyle) { +// ExpandableComposite ec = new ExpandableComposite(parent, orientation, +// expansionStyle); +// ec.setMenu(parent.getMenu()); +// adapt(ec, true, true); +// ec.setFont(boldFontHolder.getBoldFont(ec.getFont())); +// return ec; +// } + + /** + * Creates a separator label as a part of the form. + * + * @param parent + * the separator parent + * @param style + * the separator style + * @return the separator label + */ + public Label createSeparator(Composite parent, int style) { + Label label = new Label(parent, SWT.SEPARATOR | style | orientation); + label.setBackground(colors.getBackground()); + label.setForeground(colors.getBorderColor()); + return label; + } + + /** + * Creates a table as a part of the form. + * + * @param parent + * the table parent + * @param style + * the table style + * @return the table widget + */ + public Table createTable(Composite parent, int style) { + Table table = new Table(parent, style | borderStyle | orientation); + adapt(table, false, false); + // hookDeleteListener(table); + return table; + } + + /** + * Creates a text as a part of the form. + * + * @param parent + * the text parent + * @param value + * the text initial value + * @return the text widget + */ + public Text createText(Composite parent, String value) { + return createText(parent, value, SWT.SINGLE); + } + + /** + * Creates a text as a part of the form. + * + * @param parent + * the text parent + * @param value + * the text initial value + * @param style + * the text style + * @return the text widget + */ + public Text createText(Composite parent, String value, int style) { + Text text = new Text(parent, borderStyle | style | orientation); + if (value != null) + text.setText(value); + text.setForeground(colors.getForeground()); + text.setBackground(colors.getBackground()); + text.addFocusListener(visibilityHandler); + return text; + } + + /** + * Creates a tree widget as a part of the form. + * + * @param parent + * the tree parent + * @param style + * the tree style + * @return the tree widget + */ + public Tree createTree(Composite parent, int style) { + Tree tree = new Tree(parent, borderStyle | style | orientation); + adapt(tree, false, false); + // hookDeleteListener(tree); + return tree; + } + + /** + * Creates a scrolled form widget in the provided parent. If you do not + * require scrolling because there is already a scrolled composite up the + * parent chain, use 'createForm' instead. + * + * @param parent + * the scrolled form parent + * @return the form that can scroll itself + * @see #createForm + */ + public ScrolledComposite createScrolledForm(Composite parent) { + ScrolledComposite form = new ScrolledComposite(parent, SWT.V_SCROLL + | SWT.H_SCROLL | orientation); + form.setExpandHorizontal(true); + form.setExpandVertical(true); + form.setBackground(colors.getBackground()); + form.setForeground(colors.getColor(IFormColors.TITLE)); + form.setFont(JFaceResources.getHeaderFont()); + return form; + } + + /** + * Creates a form widget in the provided parent. Note that this widget does + * not scroll its content, so make sure there is a scrolled composite up the + * parent chain. If you require scrolling, use 'createScrolledForm' instead. + * + * @param parent + * the form parent + * @return the form that does not scroll + * @see #createScrolledForm + */ +// public Form createForm(Composite parent) { +// Form formContent = new Form(parent, orientation); +// formContent.setBackground(colors.getBackground()); +// formContent.setForeground(colors.getColor(IFormColors.TITLE)); +// formContent.setFont(JFaceResources.getHeaderFont()); +// return formContent; +// } + + /** + * Takes advantage of the gradients and other capabilities to decorate the + * form heading using colors computed based on the current skin and + * operating system. + * + * @param form + * the form to decorate + */ + +// public void decorateFormHeading(Form form) { +// Color top = colors.getColor(IFormColors.H_GRADIENT_END); +// Color bot = colors.getColor(IFormColors.H_GRADIENT_START); +// form.setTextBackground(new Color[] { top, bot }, new int[] { 100 }, +// true); +// form.setHeadColor(IFormColors.H_BOTTOM_KEYLINE1, colors +// .getColor(IFormColors.H_BOTTOM_KEYLINE1)); +// form.setHeadColor(IFormColors.H_BOTTOM_KEYLINE2, colors +// .getColor(IFormColors.H_BOTTOM_KEYLINE2)); +// form.setHeadColor(IFormColors.H_HOVER_LIGHT, colors +// .getColor(IFormColors.H_HOVER_LIGHT)); +// form.setHeadColor(IFormColors.H_HOVER_FULL, colors +// .getColor(IFormColors.H_HOVER_FULL)); +// form.setHeadColor(IFormColors.TB_TOGGLE, colors +// .getColor(IFormColors.TB_TOGGLE)); +// form.setHeadColor(IFormColors.TB_TOGGLE_HOVER, colors +// .getColor(IFormColors.TB_TOGGLE_HOVER)); +// form.setSeparatorVisible(true); +// } + + /** + * Creates a scrolled page book widget as a part of the form. + * + * @param parent + * the page book parent + * @param style + * the text style + * @return the scrolled page book widget + */ +// public ScrolledPageBook createPageBook(Composite parent, int style) { +// ScrolledPageBook book = new ScrolledPageBook(parent, style +// | orientation); +// adapt(book, true, true); +// book.setMenu(parent.getMenu()); +// return book; +// } + + /** + * Disposes the toolkit. + */ + public void dispose() { + if (isDisposed) { + return; + } + isDisposed = true; + if (colors.isShared() == false) { + colors.dispose(); + colors = null; + } + boldFontHolder.dispose(); + } + + /** + * Returns the hyperlink group that manages hyperlinks for this toolkit. + * + * @return the hyperlink group + */ +// public HyperlinkGroup getHyperlinkGroup() { +// return hyperlinkGroup; +// } + + /** + * Sets the background color for the entire toolkit. The method delegates + * the call to the FormColors object and also updates the hyperlink group so + * that hyperlinks and other objects are in sync. + * + * @param bg + * the new background color + */ + public void setBackground(Color bg) { +// hyperlinkGroup.setBackground(bg); + colors.setBackground(bg); + } + + /** + * Refreshes the hyperlink colors by loading from JFace settings. + */ +// public void refreshHyperlinkColors() { +// hyperlinkGroup.initializeDefaultForegrounds(colors.getDisplay()); +// } + +// RAP [rh] paintBordersFor not useful as no GC to actually paint borders +// /** +// * Paints flat borders for widgets created by this toolkit within the +// * provided parent. Borders will not be painted if the global border style +// * is SWT.BORDER (i.e. if native borders are used). Call this method during +// * creation of a form composite to get the borders of its children painted. +// * Care should be taken when selection layout margins. At least one pixel +// * pargin width and height must be chosen to allow the toolkit to paint the +// * border on the parent around the widgets. +// *

+// * Borders are painted for some controls that are selected by the toolkit by +// * default. If a control needs a border but is not on its list, it is +// * possible to force border in the following way: +// * +// *

+//	 *
+//	 *
+//	 *
+//	 *             widget.setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TREE_BORDER);
+//	 *
+//	 *             or
+//	 *
+//	 *             widget.setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TEXT_BORDER);
+//	 *
+//	 *
+//	 *
+//	 * 
+// * +// * @param parent +// * the parent that owns the children for which the border needs +// * to be painted. +// */ +// public void paintBordersFor(Composite parent) { +// // if (borderStyle == SWT.BORDER) +// // return; +// if (borderPainter == null) +// borderPainter = new BorderPainter(); +// parent.addPaintListener(borderPainter); +// } + + /** + * Returns the colors used by this toolkit. + * + * @return the color object + */ + public FormColors getColors() { + return colors; + } + + /** + * Returns the border style used for various widgets created by this + * toolkit. The intent of the toolkit is to create controls with styles that + * yield a 'flat' appearance. On systems where the native borders are + * already flat, we set the style to SWT.BORDER and don't paint the borders + * ourselves. Otherwise, the style is set to SWT.NULL, and borders are + * painted by the toolkit. + * + * @return the global border style + */ + public int getBorderStyle() { + return borderStyle; + } + + /** + * Returns the margin required around the children whose border is being + * painted by the toolkit using {@link #paintBordersFor(Composite)}. Since + * the border is painted around the controls on the parent, a number of + * pixels needs to be reserved for this border. For windowing systems where + * the native border is used, this margin is 0. + * + * @return the margin in the parent when children have their border painted + */ + public int getBorderMargin() { + return getBorderStyle() == SWT.BORDER ? 0 : 2; + } + + /** + * Sets the border style to be used when creating widgets. The toolkit + * chooses the correct style based on the platform but this value can be + * changed using this method. + * + * @param style + * SWT.BORDER or SWT.NULL + * @see #getBorderStyle + */ + public void setBorderStyle(int style) { + this.borderStyle = style; + } + + /** + * A utility method that ensures that the control is visible in the scrolled + * composite. The prerequisite for this method is that the control has a + * class that extends ScrolledComposite somewhere in the parent chain. If + * the control is partially or fully clipped, the composite is scrolled to + * set by setting the origin to the control origin. + * + * @param c + * the control to make visible + * @param verticalOnly + * if true, the scrolled composite will be + * scrolled only vertically if needed. Otherwise, the scrolled + * composite origin will be set to the control origin. + */ + public static void setControlVisible(Control c, boolean verticalOnly) { + ScrolledComposite scomp = FormUtil.getScrolledComposite(c); + if (scomp == null) + return; + Point location = FormUtil.getControlLocation(scomp, c); + scomp.setOrigin(location); + } + + private void initialize() { + initializeBorderStyle(); +// hyperlinkGroup = new HyperlinkGroup(colors.getDisplay()); +// hyperlinkGroup.setBackground(colors.getBackground()); + visibilityHandler = new VisibilityHandler(); + keyboardHandler = new KeyboardHandler(); + boldFontHolder = new BoldFontHolder(); + } + +// RAP [rh] revise detection of border style: can't ask OS here + private void initializeBorderStyle() { +// String osname = System.getProperty("os.name"); //$NON-NLS-1$ +// String osversion = System.getProperty("os.version"); //$NON-NLS-1$ +// if (osname.startsWith("Windows") && "5.1".compareTo(osversion) <= 0) { //$NON-NLS-1$ //$NON-NLS-2$ +// // Skinned widgets used on newer Windows (e.g. XP (5.1), Vista +// // (6.0)) +// // Check for Windows Classic. If not used, set the style to BORDER +// RGB rgb = colors.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND); +// if (rgb.red != 212 || rgb.green != 208 || rgb.blue != 200) +// borderStyle = SWT.BORDER; +// } else if (osname.startsWith("Mac")) //$NON-NLS-1$ +// borderStyle = SWT.BORDER; + + borderStyle = SWT.BORDER; + } + + /** + * Returns the orientation that all the widgets created by this toolkit will + * inherit, if set. Can be SWT.NULL, + * SWT.LEFT_TO_RIGHT and SWT.RIGHT_TO_LEFT. + * + * @return orientation style for this toolkit, or SWT.NULL if + * not set. The default orientation is inherited from the Window + * default orientation. + * @see org.eclipse.jface.window.Window#getDefaultOrientation() + */ + + public int getOrientation() { + return orientation; + } + + /** + * Sets the orientation that all the widgets created by this toolkit will + * inherit. Can be SWT.NULL, SWT.LEFT_TO_RIGHT + * and SWT.RIGHT_TO_LEFT. + * + * @param orientation + * style for this toolkit. + */ + + public void setOrientation(int orientation) { + this.orientation = orientation; + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormUtil.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormUtil.java new file mode 100644 index 000000000..76e3f1104 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormUtil.java @@ -0,0 +1,522 @@ +package org.argeo.cms.ui.eclipse.forms; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.ScrolledComposite; +import org.eclipse.swt.events.MouseEvent; +//import org.eclipse.swt.graphics.Device; +import org.eclipse.swt.graphics.FontMetrics; +import org.eclipse.swt.graphics.GC; +//import org.eclipse.swt.graphics.Image; +//import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Combo; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Layout; +//import org.eclipse.swt.widgets.ScrollBar; +import org.eclipse.swt.widgets.Text; +//import org.eclipse.ui.forms.widgets.ColumnLayout; +//import org.eclipse.ui.forms.widgets.Form; +//import org.eclipse.ui.forms.widgets.FormText; +//import org.eclipse.ui.forms.widgets.FormToolkit; +//import org.eclipse.ui.forms.widgets.ILayoutExtension; +// +//import com.ibm.icu.text.BreakIterator; + +public class FormUtil { + public static final String PLUGIN_ID = "org.eclipse.ui.forms"; //$NON-NLS-1$ + + static final int H_SCROLL_INCREMENT = 5; + + static final int V_SCROLL_INCREMENT = 64; + + public static final String DEBUG = PLUGIN_ID + "/debug"; //$NON-NLS-1$ + + public static final String DEBUG_TEXT = DEBUG + "/text"; //$NON-NLS-1$ + public static final String DEBUG_TEXTSIZE = DEBUG + "/textsize"; //$NON-NLS-1$ + + public static final String DEBUG_FOCUS = DEBUG + "/focus"; //$NON-NLS-1$ + + public static final String FOCUS_SCROLLING = "focusScrolling"; //$NON-NLS-1$ + + public static final String IGNORE_BODY = "__ignore_body__"; //$NON-NLS-1$ + + public static Text createText(Composite parent, String label, + FormToolkit factory) { + return createText(parent, label, factory, 1); + } + + public static Text createText(Composite parent, String label, + FormToolkit factory, int span) { + factory.createLabel(parent, label); + Text text = factory.createText(parent, ""); //$NON-NLS-1$ + int hfill = span == 1 ? GridData.FILL_HORIZONTAL + : GridData.HORIZONTAL_ALIGN_FILL; + GridData gd = new GridData(hfill | GridData.VERTICAL_ALIGN_CENTER); + gd.horizontalSpan = span; + text.setLayoutData(gd); + return text; + } + + public static Text createText(Composite parent, String label, + FormToolkit factory, int span, int style) { + Label l = factory.createLabel(parent, label); + if ((style & SWT.MULTI) != 0) { + GridData gd = new GridData(GridData.VERTICAL_ALIGN_BEGINNING); + l.setLayoutData(gd); + } + Text text = factory.createText(parent, "", style); //$NON-NLS-1$ + int hfill = span == 1 ? GridData.FILL_HORIZONTAL + : GridData.HORIZONTAL_ALIGN_FILL; + GridData gd = new GridData(hfill | GridData.VERTICAL_ALIGN_CENTER); + gd.horizontalSpan = span; + text.setLayoutData(gd); + return text; + } + + public static Text createText(Composite parent, FormToolkit factory, + int span) { + Text text = factory.createText(parent, ""); //$NON-NLS-1$ + int hfill = span == 1 ? GridData.FILL_HORIZONTAL + : GridData.HORIZONTAL_ALIGN_FILL; + GridData gd = new GridData(hfill | GridData.VERTICAL_ALIGN_CENTER); + gd.horizontalSpan = span; + text.setLayoutData(gd); + return text; + } + + public static int computeMinimumWidth(GC gc, String text) { +// BreakIterator wb = BreakIterator.getWordInstance(); +// wb.setText(text); +// int last = 0; +// +// int width = 0; +// +// for (int loc = wb.first(); loc != BreakIterator.DONE; loc = wb.next()) { +// String word = text.substring(last, loc); +// Point extent = gc.textExtent(word); +// width = Math.max(width, extent.x); +// last = loc; +// } +// String lastWord = text.substring(last); +// Point extent = gc.textExtent(lastWord); +// width = Math.max(width, extent.x); +// return width; + return 0; + } + + public static Point computeWrapSize(GC gc, String text, int wHint) { +// BreakIterator wb = BreakIterator.getWordInstance(); +// wb.setText(text); + FontMetrics fm = gc.getFontMetrics(); + int lineHeight = fm.getHeight(); + + int saved = 0; + int last = 0; + int height = lineHeight; + int maxWidth = 0; +// for (int loc = wb.first(); loc != BreakIterator.DONE; loc = wb.next()) { +// String word = text.substring(saved, loc); +// Point extent = gc.textExtent(word); +// if (extent.x > wHint) { +// // overflow +// saved = last; +// height += extent.y; +// // switch to current word so maxWidth will accommodate very long single words +// word = text.substring(last, loc); +// extent = gc.textExtent(word); +// } +// maxWidth = Math.max(maxWidth, extent.x); +// last = loc; +// } + /* + * Correct the height attribute in case it was calculated wrong due to wHint being less than maxWidth. + * The recursive call proved to be the only thing that worked in all cases. Some attempts can be made + * to estimate the height, but the algorithm needs to be run again to be sure. + */ + if (maxWidth > wHint) + return computeWrapSize(gc, text, maxWidth); + return new Point(maxWidth, height); + } + +// RAP [rh] paintWrapText unnecessary +// public static void paintWrapText(GC gc, String text, Rectangle bounds) { +// paintWrapText(gc, text, bounds, false); +// } + +// RAP [rh] paintWrapText unnecessary +// public static void paintWrapText(GC gc, String text, Rectangle bounds, +// boolean underline) { +// BreakIterator wb = BreakIterator.getWordInstance(); +// wb.setText(text); +// FontMetrics fm = gc.getFontMetrics(); +// int lineHeight = fm.getHeight(); +// int descent = fm.getDescent(); +// +// int saved = 0; +// int last = 0; +// int y = bounds.y; +// int width = bounds.width; +// +// for (int loc = wb.first(); loc != BreakIterator.DONE; loc = wb.next()) { +// String line = text.substring(saved, loc); +// Point extent = gc.textExtent(line); +// +// if (extent.x > width) { +// // overflow +// String prevLine = text.substring(saved, last); +// gc.drawText(prevLine, bounds.x, y, true); +// if (underline) { +// Point prevExtent = gc.textExtent(prevLine); +// int lineY = y + lineHeight - descent + 1; +// gc +// .drawLine(bounds.x, lineY, bounds.x + prevExtent.x, +// lineY); +// } +// +// saved = last; +// y += lineHeight; +// } +// last = loc; +// } +// // paint the last line +// String lastLine = text.substring(saved, last); +// gc.drawText(lastLine, bounds.x, y, true); +// if (underline) { +// int lineY = y + lineHeight - descent + 1; +// Point lastExtent = gc.textExtent(lastLine); +// gc.drawLine(bounds.x, lineY, bounds.x + lastExtent.x, lineY); +// } +// } + + public static ScrolledComposite getScrolledComposite(Control c) { + Composite parent = c.getParent(); + + while (parent != null) { + if (parent instanceof ScrolledComposite) { + return (ScrolledComposite) parent; + } + parent = parent.getParent(); + } + return null; + } + + public static void ensureVisible(Control c) { + ScrolledComposite scomp = getScrolledComposite(c); + if (scomp != null) { + Object data = scomp.getData(FOCUS_SCROLLING); + if (data == null || !data.equals(Boolean.FALSE)) + FormUtil.ensureVisible(scomp, c); + } + } + + public static void ensureVisible(ScrolledComposite scomp, Control control) { + // if the control is a FormText we do not need to scroll since it will + // ensure visibility of its segments as necessary +// if (control instanceof FormText) +// return; + Point controlSize = control.getSize(); + Point controlOrigin = getControlLocation(scomp, control); + ensureVisible(scomp, controlOrigin, controlSize); + } + + public static void ensureVisible(ScrolledComposite scomp, + Point controlOrigin, Point controlSize) { + Rectangle area = scomp.getClientArea(); + Point scompOrigin = scomp.getOrigin(); + + int x = scompOrigin.x; + int y = scompOrigin.y; + + // horizontal right, but only if the control is smaller + // than the client area + if (controlSize.x < area.width + && (controlOrigin.x + controlSize.x > scompOrigin.x + + area.width)) { + x = controlOrigin.x + controlSize.x - area.width; + } + // horizontal left - make sure the left edge of + // the control is showing + if (controlOrigin.x < x) { + if (controlSize.x < area.width) + x = controlOrigin.x + controlSize.x - area.width; + else + x = controlOrigin.x; + } + // vertical bottom + if (controlSize.y < area.height + && (controlOrigin.y + controlSize.y > scompOrigin.y + + area.height)) { + y = controlOrigin.y + controlSize.y - area.height; + } + // vertical top - make sure the top of + // the control is showing + if (controlOrigin.y < y) { + if (controlSize.y < area.height) + y = controlOrigin.y + controlSize.y - area.height; + else + y = controlOrigin.y; + } + + if (scompOrigin.x != x || scompOrigin.y != y) { + // scroll to reveal + scomp.setOrigin(x, y); + } + } + + public static void ensureVisible(ScrolledComposite scomp, Control control, + MouseEvent e) { + Point controlOrigin = getControlLocation(scomp, control); + int rX = controlOrigin.x + e.x; + int rY = controlOrigin.y + e.y; + Rectangle area = scomp.getClientArea(); + Point scompOrigin = scomp.getOrigin(); + + int x = scompOrigin.x; + int y = scompOrigin.y; + // System.out.println("Ensure: area="+area+", origin="+scompOrigin+", + // cloc="+controlOrigin+", csize="+controlSize+", x="+x+", y="+y); + + // horizontal right + if (rX > scompOrigin.x + area.width) { + x = rX - area.width; + } + // horizontal left + else if (rX < x) { + x = rX; + } + // vertical bottom + if (rY > scompOrigin.y + area.height) { + y = rY - area.height; + } + // vertical top + else if (rY < y) { + y = rY; + } + + if (scompOrigin.x != x || scompOrigin.y != y) { + // scroll to reveal + scomp.setOrigin(x, y); + } + } + + public static Point getControlLocation(ScrolledComposite scomp, + Control control) { + int x = 0; + int y = 0; + Control content = scomp.getContent(); + Control currentControl = control; + for (;;) { + if (currentControl == content) + break; + Point location = currentControl.getLocation(); + // if (location.x > 0) + // x += location.x; + // if (location.y > 0) + // y += location.y; + x += location.x; + y += location.y; + currentControl = currentControl.getParent(); + } + return new Point(x, y); + } + + static void scrollVertical(ScrolledComposite scomp, boolean up) { + scroll(scomp, 0, up ? -V_SCROLL_INCREMENT : V_SCROLL_INCREMENT); + } + + static void scrollHorizontal(ScrolledComposite scomp, boolean left) { + scroll(scomp, left ? -H_SCROLL_INCREMENT : H_SCROLL_INCREMENT, 0); + } + + static void scrollPage(ScrolledComposite scomp, boolean up) { + Rectangle clientArea = scomp.getClientArea(); + int increment = up ? -clientArea.height : clientArea.height; + scroll(scomp, 0, increment); + } + + static void scroll(ScrolledComposite scomp, int xoffset, int yoffset) { + Point origin = scomp.getOrigin(); + Point contentSize = scomp.getContent().getSize(); + int xorigin = origin.x + xoffset; + int yorigin = origin.y + yoffset; + xorigin = Math.max(xorigin, 0); + xorigin = Math.min(xorigin, contentSize.x - 1); + yorigin = Math.max(yorigin, 0); + yorigin = Math.min(yorigin, contentSize.y - 1); + scomp.setOrigin(xorigin, yorigin); + } + +// RAP [rh] FormUtil#updatePageIncrement: empty implementation + public static void updatePageIncrement(ScrolledComposite scomp) { +// ScrollBar vbar = scomp.getVerticalBar(); +// if (vbar != null) { +// Rectangle clientArea = scomp.getClientArea(); +// int increment = clientArea.height - 5; +// vbar.setPageIncrement(increment); +// } +// ScrollBar hbar = scomp.getHorizontalBar(); +// if (hbar != null) { +// Rectangle clientArea = scomp.getClientArea(); +// int increment = clientArea.width - 5; +// hbar.setPageIncrement(increment); +// } + } + + public static void processKey(int keyCode, Control c) { + if (c.isDisposed()) { + return; + } + ScrolledComposite scomp = FormUtil.getScrolledComposite(c); + if (scomp != null) { + if (c instanceof Combo) + return; + switch (keyCode) { + case SWT.ARROW_DOWN: + if (scomp.getData("novarrows") == null) //$NON-NLS-1$ + FormUtil.scrollVertical(scomp, false); + break; + case SWT.ARROW_UP: + if (scomp.getData("novarrows") == null) //$NON-NLS-1$ + FormUtil.scrollVertical(scomp, true); + break; + case SWT.ARROW_LEFT: + FormUtil.scrollHorizontal(scomp, true); + break; + case SWT.ARROW_RIGHT: + FormUtil.scrollHorizontal(scomp, false); + break; + case SWT.PAGE_UP: + FormUtil.scrollPage(scomp, true); + break; + case SWT.PAGE_DOWN: + FormUtil.scrollPage(scomp, false); + break; + } + } + } + + public static boolean isWrapControl(Control c) { + if ((c.getStyle() & SWT.WRAP) != 0) + return true; + if (c instanceof Composite) { + return false; +// return ((Composite) c).getLayout() instanceof ILayoutExtension; + } + return false; + } + + public static int getWidthHint(int wHint, Control c) { + boolean wrap = isWrapControl(c); + return wrap ? wHint : SWT.DEFAULT; + } + + public static int getHeightHint(int hHint, Control c) { + if (c instanceof Composite) { + Layout layout = ((Composite) c).getLayout(); +// if (layout instanceof ColumnLayout) +// return hHint; + } + return SWT.DEFAULT; + } + + public static int computeMinimumWidth(Control c, boolean changed) { + if (c instanceof Composite) { + Layout layout = ((Composite) c).getLayout(); +// if (layout instanceof ILayoutExtension) +// return ((ILayoutExtension) layout).computeMinimumWidth( +// (Composite) c, changed); + } + return c.computeSize(FormUtil.getWidthHint(5, c), SWT.DEFAULT, changed).x; + } + + public static int computeMaximumWidth(Control c, boolean changed) { + if (c instanceof Composite) { + Layout layout = ((Composite) c).getLayout(); +// if (layout instanceof ILayoutExtension) +// return ((ILayoutExtension) layout).computeMaximumWidth( +// (Composite) c, changed); + } + return c.computeSize(SWT.DEFAULT, SWT.DEFAULT, changed).x; + } + +// public static Form getForm(Control c) { +// Composite parent = c.getParent(); +// while (parent != null) { +// if (parent instanceof Form) { +// return (Form) parent; +// } +// parent = parent.getParent(); +// } +// return null; +// } + +// RAP [rh] FormUtil#createAlphaMashImage unnecessary +// public static Image createAlphaMashImage(Device device, Image srcImage) { +// Rectangle bounds = srcImage.getBounds(); +// int alpha = 0; +// int calpha = 0; +// ImageData data = srcImage.getImageData(); +// // Create a new image with alpha values alternating +// // between fully transparent (0) and fully opaque (255). +// // This image will show the background through the +// // transparent pixels. +// for (int i = 0; i < bounds.height; i++) { +// // scan line +// alpha = calpha; +// for (int j = 0; j < bounds.width; j++) { +// // column +// data.setAlpha(j, i, alpha); +// alpha = alpha == 255 ? 0 : 255; +// } +// calpha = calpha == 255 ? 0 : 255; +// } +// return new Image(device, data); +// } + + public static boolean mnemonicMatch(String text, char key) { + char mnemonic = findMnemonic(text); + if (mnemonic == '\0') + return false; + return Character.toUpperCase(key) == Character.toUpperCase(mnemonic); + } + + private static char findMnemonic(String string) { + int index = 0; + int length = string.length(); + do { + while (index < length && string.charAt(index) != '&') + index++; + if (++index >= length) + return '\0'; + if (string.charAt(index) != '&') + return string.charAt(index); + index++; + } while (index < length); + return '\0'; + } + + public static void setFocusScrollingEnabled(Control c, boolean enabled) { + ScrolledComposite scomp = null; + + if (c instanceof ScrolledComposite) + scomp = (ScrolledComposite)c; + else + scomp = getScrolledComposite(c); + if (scomp!=null) + scomp.setData(FormUtil.FOCUS_SCROLLING, enabled?null:Boolean.FALSE); + } + + // RAP [rh] FormUtil#setAntialias unnecessary +// public static void setAntialias(GC gc, int style) { +// if (!gc.getAdvanced()) { +// gc.setAdvanced(true); +// if (!gc.getAdvanced()) +// return; +// } +// gc.setAntialias(style); +// } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IFormColors.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IFormColors.java new file mode 100644 index 000000000..cf0e5d35e --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IFormColors.java @@ -0,0 +1,102 @@ +package org.argeo.cms.ui.eclipse.forms; + +/** + * A place to hold all the color constants used in the forms package. + * + * @since 1.0 + */ + +public interface IFormColors { + /** + * A prefix for all the keys. + */ + String PREFIX = "org.eclipse.ui.forms."; //$NON-NLS-1$ + /** + * Key for the form title foreground color. + */ + String TITLE = PREFIX + "TITLE"; //$NON-NLS-1$ + + /** + * A prefix for the header color constants. + */ + String H_PREFIX = PREFIX + "H_"; //$NON-NLS-1$ + /* + * A prefix for the section title bar color constants. + */ + String TB_PREFIX = PREFIX + "TB_"; //$NON-NLS-1$ + /** + * Key for the form header background gradient ending color. + */ + String H_GRADIENT_END = H_PREFIX + "GRADIENT_END"; //$NON-NLS-1$ + + /** + * Key for the form header background gradient starting color. + * + */ + String H_GRADIENT_START = H_PREFIX + "GRADIENT_START"; //$NON-NLS-1$ + /** + * Key for the form header bottom keyline 1 color. + * + */ + String H_BOTTOM_KEYLINE1 = H_PREFIX + "BOTTOM_KEYLINE1"; //$NON-NLS-1$ + /** + * Key for the form header bottom keyline 2 color. + * + */ + String H_BOTTOM_KEYLINE2 = H_PREFIX + "BOTTOM_KEYLINE2"; //$NON-NLS-1$ + /** + * Key for the form header light hover color. + * + */ + String H_HOVER_LIGHT = H_PREFIX + "H_HOVER_LIGHT"; //$NON-NLS-1$ + /** + * Key for the form header full hover color. + * + */ + String H_HOVER_FULL = H_PREFIX + "H_HOVER_FULL"; //$NON-NLS-1$ + + /** + * Key for the tree/table border color. + */ + String BORDER = PREFIX + "BORDER"; //$NON-NLS-1$ + + /** + * Key for the section separator color. + */ + String SEPARATOR = PREFIX + "SEPARATOR"; //$NON-NLS-1$ + + /** + * Key for the section title bar background. + */ + String TB_BG = TB_PREFIX + "BG"; //$NON-NLS-1$ + + /** + * Key for the section title bar foreground. + */ + String TB_FG = TB_PREFIX + "FG"; //$NON-NLS-1$ + + /** + * Key for the section title bar gradient. + * @deprecated Since 3.3, this color is not used any more. The + * tool bar gradient is created starting from {@link #TB_BG} to + * the section background color. + */ + String TB_GBG = TB_BG; + + /** + * Key for the section title bar border. + */ + String TB_BORDER = TB_PREFIX + "BORDER"; //$NON-NLS-1$ + + /** + * Key for the section toggle color. Since 3.1, this color is used for all + * section styles. + */ + String TB_TOGGLE = TB_PREFIX + "TOGGLE"; //$NON-NLS-1$ + + /** + * Key for the section toggle hover color. + * + */ + String TB_TOGGLE_HOVER = TB_PREFIX + "TOGGLE_HOVER"; //$NON-NLS-1$ +} \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IFormPart.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IFormPart.java new file mode 100644 index 000000000..954cc0372 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IFormPart.java @@ -0,0 +1,108 @@ +package org.argeo.cms.ui.eclipse.forms; + +/** + * Classes that implement this interface can be added to the managed form and + * take part in the form life cycle. The part is initialized with the form and + * will be asked to accept focus. The part can receive form input and can elect + * to do something according to it (for example, select an object that matches + * the input). + *

+ * The form part has two 'out of sync' states in respect to the model(s) that + * feed the form: dirty and stale. When a part is dirty, it + * means that the user interacted with it and now its widgets contain state that + * is newer than the model. In order to sync up with the model, 'commit' needs + * to be called. In contrast, the model can change 'under' the form (as a result + * of some actions outside the form), resulting in data in the model being + * 'newer' than the content presented in the form. A 'stale' form part is + * brought in sync with the model by calling 'refresh'. The part is responsible + * for notifying the form when one of these states change in the part. The form + * reserves the right to handle this notification in the most appropriate way + * for the situation (for example, if the form is in a page of the multi-page + * editor, it may do nothing for stale parts if the page is currently not + * showing). + *

+ * When the form is disposed, each registered part is disposed as well. Parts + * are responsible for releasing any system resources they created and for + * removing themselves as listeners from all event providers. + * + * @see IManagedForm + * @since 1.0 + * + */ +public interface IFormPart { + /** + * Initializes the part. + * + * @param form + * the managed form that manages the part + */ + void initialize(IManagedForm form); + + /** + * Disposes the part allowing it to release allocated resources. + */ + void dispose(); + + /** + * Returns true if the part has been modified with respect to the data + * loaded from the model. + * + * @return true if the part has been modified with respect to the data + * loaded from the model + */ + boolean isDirty(); + + /** + * If part is displaying information loaded from a model, this method + * instructs it to commit the new (modified) data back into the model. + * + * @param onSave + * indicates if commit is called during 'save' operation or for + * some other reason (for example, if form is contained in a + * wizard or a multi-page editor and the user is about to leave + * the page). + */ + void commit(boolean onSave); + + /** + * Notifies the part that an object has been set as overall form's input. + * The part can elect to react by revealing or selecting the object, or do + * nothing if not applicable. + * + * @return true if the part has selected and revealed the + * input object, false otherwise. + */ + boolean setFormInput(Object input); + + /** + * Instructs form part to transfer focus to the widget that should has focus + * in that part. The method can do nothing (if it has no widgets capable of + * accepting focus). + */ + void setFocus(); + + /** + * Tests whether the form part is stale and needs refreshing. Parts can + * receive notification from models that will make their content stale, but + * may need to delay refreshing to improve performance (for example, there + * is no need to immediately refresh a part on a form that is current on a + * hidden page). + *

+ * It is important to differentiate 'stale' and 'dirty' states. Part is + * 'dirty' if user interacted with its editable widgets and changed the + * values. In contrast, part is 'stale' when the data it presents in the + * widgets has been changed in the model without direct user interaction. + * + * @return true if the part needs refreshing, + * false otherwise. + */ + boolean isStale(); + + /** + * Refreshes the part completely from the information freshly obtained from + * the model. The method will not be called if the part is not stale. + * Otherwise, the part is responsible for clearing the 'stale' flag after + * refreshing itself. + */ + void refresh(); +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IManagedForm.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IManagedForm.java new file mode 100644 index 000000000..490d3a303 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IManagedForm.java @@ -0,0 +1,175 @@ +package org.argeo.cms.ui.eclipse.forms; + +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.swt.custom.ScrolledComposite; +//import org.eclipse.ui.forms.widgets.FormToolkit; +//import org.eclipse.ui.forms.widgets.ScrolledForm; + +/** + * Managed form wraps a form widget and adds life cycle methods for form parts. + * A form part is a portion of the form that participates in form life cycle + * events. + *

+ * There is no 1/1 mapping between widgets and form parts. A widget like Section + * can be a part by itself, but a number of widgets can gather around one form + * part. + *

+ * This interface should not be extended or implemented. New form instances + * should be created using ManagedForm. + * + * @see ManagedForm + * @since 1.0 + * @noimplement This interface is not intended to be implemented by clients. + * @noextend This interface is not intended to be extended by clients. + */ +public interface IManagedForm { + /** + * Initializes the form by looping through the managed parts and + * initializing them. Has no effect if already called once. + */ + public void initialize(); + + /** + * Returns the toolkit used by this form. + * + * @return the toolkit + */ + public FormToolkit getToolkit(); + + /** + * Returns the form widget managed by this form. + * + * @return the form widget + */ + public ScrolledComposite getForm(); + + /** + * Reflows the form as a result of the layout change. + * + * @param changed + * if true, discard cached layout information + */ + public void reflow(boolean changed); + + /** + * A part can use this method to notify other parts that implement + * IPartSelectionListener about selection changes. + * + * @param part + * the part that broadcasts the selection + * @param selection + * the selection in the part + */ + public void fireSelectionChanged(IFormPart part, ISelection selection); + + /** + * Returns all the parts currently managed by this form. + * + * @return the managed parts + */ + IFormPart[] getParts(); + + /** + * Adds the new part to the form. + * + * @param part + * the part to add + */ + void addPart(IFormPart part); + + /** + * Removes the part from the form. + * + * @param part + * the part to remove + */ + void removePart(IFormPart part); + + /** + * Sets the input of this page to the provided object. + * + * @param input + * the new page input + * @return true if the form contains this object, + * false otherwise. + */ + boolean setInput(Object input); + + /** + * Returns the current page input. + * + * @return page input object or null if not applicable. + */ + Object getInput(); + + /** + * Tests if form is dirty. A managed form is dirty if at least one managed + * part is dirty. + * + * @return true if at least one managed part is dirty, + * false otherwise. + */ + boolean isDirty(); + + /** + * Notifies the form that the dirty state of one of its parts has changed. + * The global dirty state of the form can be obtained by calling 'isDirty'. + * + * @see #isDirty + */ + void dirtyStateChanged(); + + /** + * Commits the dirty form. All pending changes in the widgets are flushed + * into the model. + * + * @param onSave + */ + void commit(boolean onSave); + + /** + * Tests if form is stale. A managed form is stale if at least one managed + * part is stale. This can happen when the underlying model changes, + * resulting in the presentation of the part being out of sync with the + * model and needing refreshing. + * + * @return true if the form is stale, false + * otherwise. + */ + boolean isStale(); + + /** + * Notifies the form that the stale state of one of its parts has changed. + * The global stale state of the form can be obtained by calling 'isStale'. + */ + void staleStateChanged(); + + /** + * Refreshes the form by refreshing every part that is stale. + */ + void refresh(); + + /** + * Sets the container that owns this form. Depending on the context, the + * container may be wizard, editor page, editor etc. + * + * @param container + * the container of this form + */ + void setContainer(Object container); + + /** + * Returns the container of this form. + * + * @return the form container + */ + Object getContainer(); + + /** + * Returns the message manager that will keep track of messages in this + * form. + * + * @return the message manager instance + */ +// IMessageManager getMessageManager(); +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IPartSelectionListener.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IPartSelectionListener.java new file mode 100644 index 000000000..0f557d41f --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IPartSelectionListener.java @@ -0,0 +1,23 @@ +package org.argeo.cms.ui.eclipse.forms; + +import org.eclipse.jface.viewers.ISelection; + +/** + * Form parts can implement this interface if they want to be + * notified when another part on the same form changes selection + * state. + * + * @see IFormPart + * @since 1.0 + */ +public interface IPartSelectionListener { + /** + * Called when the provided part has changed selection state. + * + * @param part + * the selection source + * @param selection + * the new selection + */ + public void selectionChanged(IFormPart part, ISelection selection); +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/ManagedForm.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/ManagedForm.java new file mode 100644 index 000000000..4140465a1 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/ManagedForm.java @@ -0,0 +1,323 @@ +package org.argeo.cms.ui.eclipse.forms; + +import java.util.Vector; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.swt.custom.ScrolledComposite; +import org.eclipse.swt.widgets.Composite; +//import org.eclipse.ui.forms.widgets.FormToolkit; +//import org.eclipse.ui.forms.widgets.ScrolledForm; + +/** + * Managed form wraps a form widget and adds life cycle methods for form parts. + * A form part is a portion of the form that participates in form life cycle + * events. + *

+ * There is requirement for 1/1 mapping between widgets and form parts. A widget + * like Section can be a part by itself, but a number of widgets can join around + * one form part. + *

+ * Note to developers: this class is left public to allow its use beyond the + * original intention (inside a multi-page editor's page). You should limit the + * use of this class to make new instances inside a form container (wizard page, + * dialog etc.). Clients that need access to the class should not do it + * directly. Instead, they should do it through IManagedForm interface as much + * as possible. + * + * @since 1.0 + */ +public class ManagedForm implements IManagedForm { + private Object input; + + private ScrolledComposite form; + + private FormToolkit toolkit; + + private Object container; + + private boolean ownsToolkit; + + private boolean initialized; + + private Vector parts = new Vector(); + + /** + * Creates a managed form in the provided parent. Form toolkit and widget + * will be created and owned by this object. + * + * @param parent + * the parent widget + */ + public ManagedForm(Composite parent) { + toolkit = new FormToolkit(parent.getDisplay()); + ownsToolkit = true; + form = toolkit.createScrolledForm(parent); + + } + + /** + * Creates a managed form that will use the provided toolkit and + * + * @param toolkit + * @param form + */ + public ManagedForm(FormToolkit toolkit, ScrolledComposite form) { + this.form = form; + this.toolkit = toolkit; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.ui.forms.IManagedForm#addPart(org.eclipse.ui.forms.IFormPart) + */ + public void addPart(IFormPart part) { + parts.add(part); + part.initialize(this); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.ui.forms.IManagedForm#removePart(org.eclipse.ui.forms.IFormPart) + */ + public void removePart(IFormPart part) { + parts.remove(part); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.ui.forms.IManagedForm#getParts() + */ + public IFormPart[] getParts() { + return (IFormPart[]) parts.toArray(new IFormPart[parts.size()]); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.ui.forms.IManagedForm#getToolkit() + */ + public FormToolkit getToolkit() { + return toolkit; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.ui.forms.IManagedForm#getForm() + */ + public ScrolledComposite getForm() { + return form; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.ui.forms.IManagedForm#reflow(boolean) + */ + public void reflow(boolean changed) { +// form.reflow(changed); + } + + /** + * A part can use this method to notify other parts that implement + * IPartSelectionListener about selection changes. + * + * @param part + * the part that broadcasts the selection + * @param selection + * the selection in the part + * @see IPartSelectionListener + */ + public void fireSelectionChanged(IFormPart part, ISelection selection) { + for (int i = 0; i < parts.size(); i++) { + IFormPart cpart = (IFormPart) parts.get(i); + if (part.equals(cpart)) + continue; +// if (cpart instanceof IPartSelectionListener) { +// ((IPartSelectionListener) cpart).selectionChanged(part, +// selection); +// } + } + } + + /** + * Initializes the form by looping through the managed parts and + * initializing them. Has no effect if already called once. + */ + public void initialize() { + if (initialized) + return; + for (int i = 0; i < parts.size(); i++) { + IFormPart part = (IFormPart) parts.get(i); + part.initialize(this); + } + initialized = true; + } + + /** + * Disposes all the parts in this form. + */ + public void dispose() { + for (int i = 0; i < parts.size(); i++) { + IFormPart part = (IFormPart) parts.get(i); + part.dispose(); + } + if (ownsToolkit) { + toolkit.dispose(); + } + } + + /** + * Refreshes the form by refreshes all the stale parts. Since 3.1, this + * method is performed on a UI thread when called from another thread so it + * is not needed to wrap the call in Display.syncExec or + * asyncExec. + */ + public void refresh() { + Thread t = Thread.currentThread(); + Thread dt = toolkit.getColors().getDisplay().getThread(); + if (t.equals(dt)) + doRefresh(); + else { + toolkit.getColors().getDisplay().asyncExec(new Runnable() { + public void run() { + doRefresh(); + } + }); + } + } + + private void doRefresh() { + int nrefreshed = 0; + for (int i = 0; i < parts.size(); i++) { + IFormPart part = (IFormPart) parts.get(i); + if (part.isStale()) { + part.refresh(); + nrefreshed++; + } + } +// if (nrefreshed > 0) +// form.reflow(true); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.ui.forms.IManagedForm#commit(boolean) + */ + public void commit(boolean onSave) { + for (int i = 0; i < parts.size(); i++) { + IFormPart part = (IFormPart) parts.get(i); + if (part.isDirty()) + part.commit(onSave); + } + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.ui.forms.IManagedForm#setInput(java.lang.Object) + */ + public boolean setInput(Object input) { + boolean pageResult = false; + + this.input = input; + for (int i = 0; i < parts.size(); i++) { + IFormPart part = (IFormPart) parts.get(i); + boolean result = part.setFormInput(input); + if (result) + pageResult = true; + } + return pageResult; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.ui.forms.IManagedForm#getInput() + */ + public Object getInput() { + return input; + } + + /** + * Transfers the focus to the first form part. + */ + public void setFocus() { + if (parts.size() > 0) { + IFormPart part = (IFormPart) parts.get(0); + part.setFocus(); + } + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.ui.forms.IManagedForm#isDirty() + */ + public boolean isDirty() { + for (int i = 0; i < parts.size(); i++) { + IFormPart part = (IFormPart) parts.get(i); + if (part.isDirty()) + return true; + } + return false; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.ui.forms.IManagedForm#isStale() + */ + public boolean isStale() { + for (int i = 0; i < parts.size(); i++) { + IFormPart part = (IFormPart) parts.get(i); + if (part.isStale()) + return true; + } + return false; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.ui.forms.IManagedForm#dirtyStateChanged() + */ + public void dirtyStateChanged() { + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.ui.forms.IManagedForm#staleStateChanged() + */ + public void staleStateChanged() { + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.ui.forms.IManagedForm#getContainer() + */ + public Object getContainer() { + return container; + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.ui.forms.IManagedForm#setContainer(java.lang.Object) + */ + public void setContainer(Object container) { + this.container = container; + } + + /* (non-Javadoc) + * @see org.eclipse.ui.forms.IManagedForm#getMessageManager() + */ +// public IMessageManager getMessageManager() { +// return form.getMessageManager(); +// } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/FormEditor.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/FormEditor.java new file mode 100644 index 000000000..7fa00d9c2 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/FormEditor.java @@ -0,0 +1,89 @@ +package org.argeo.cms.ui.eclipse.forms.editor; + +import org.argeo.cms.ui.eclipse.forms.FormToolkit; +import org.eclipse.core.runtime.ListenerList; +import org.eclipse.jface.dialogs.IPageChangeProvider; +import org.eclipse.jface.dialogs.IPageChangedListener; +import org.eclipse.jface.dialogs.PageChangedEvent; +import org.eclipse.jface.util.SafeRunnable; + +/** + * This class forms a base of multi-page form editors that typically use one or + * more pages with forms and one page for raw source of the editor input. + *

+ * Pages are added 'lazily' i.e. adding a page reserves a tab for it but does + * not cause the page control to be created. Page control is created when an + * attempt is made to select the page in question. This allows editors with + * several tabs and complex pages to open quickly. + *

+ * Subclasses should extend this class and implement addPages + * method. One of the two addPage methods should be called to + * contribute pages to the editor. One adds complete (standalone) editors as + * nested tabs. These editors will be created right away and will be hooked so + * that key bindings, selection service etc. is compatible with the one for the + * standalone case. The other method adds classes that implement + * IFormPage interface. These pages will be created lazily and + * they will share the common key binding and selection service. Since 3.1, + * FormEditor is a page change provider. It allows listeners to attach to it and + * get notified when pages are changed. This new API in JFace allows dynamic + * help to update on page changes. + * + * @since 1.0 + */ +// RAP [if] As RAP is still using workbench 3.4, the implementation of +// IPageChangeProvider is missing from MultiPageEditorPart. Remove this code +// with the adoption of workbench > 3.5 +//public abstract class FormEditor extends MultiPageEditorPart { +public abstract class FormEditor implements + IPageChangeProvider { + private FormToolkit formToolkit; + + +public FormToolkit getToolkit() { + return formToolkit; + } + +public void editorDirtyStateChanged() { + +} + +public FormPage getActivePageInstance() { + return null; +} + + // RAP [if] As RAP is still using workbench 3.4, the implementation of +// IPageChangeProvider is missing from MultiPageEditorPart. Remove this code +// with the adoption of workbench > 3.5 + private ListenerList pageListeners = new ListenerList(); + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.dialogs.IPageChangeProvider#addPageChangedListener(org.eclipse.jface.dialogs.IPageChangedListener) + */ + public void addPageChangedListener(IPageChangedListener listener) { + pageListeners.add(listener); + } + + /* + * (non-Javadoc) + * + * @see org.eclipse.jface.dialogs.IPageChangeProvider#removePageChangedListener(org.eclipse.jface.dialogs.IPageChangedListener) + */ + public void removePageChangedListener(IPageChangedListener listener) { + pageListeners.remove(listener); + } + + private void firePageChanged(final PageChangedEvent event) { + Object[] listeners = pageListeners.getListeners(); + for (int i = 0; i < listeners.length; ++i) { + final IPageChangedListener l = (IPageChangedListener) listeners[i]; + SafeRunnable.run(new SafeRunnable() { + public void run() { + l.pageChanged(event); + } + }); + } + } +// RAPEND [if] +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/FormPage.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/FormPage.java new file mode 100644 index 000000000..1511cf38c --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/FormPage.java @@ -0,0 +1,277 @@ +package org.argeo.cms.ui.eclipse.forms.editor; +import org.argeo.cms.ui.eclipse.forms.IManagedForm; +import org.argeo.cms.ui.eclipse.forms.ManagedForm; +import org.eclipse.core.runtime.IProgressMonitor; +import org.eclipse.swt.custom.BusyIndicator; +import org.eclipse.swt.custom.ScrolledComposite; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +/** + * A base class that all pages that should be added to FormEditor must subclass. + * Form page has an instance of PageForm that extends managed form. Subclasses + * should override method 'createFormContent(ManagedForm)' to fill the form with + * content. Note that page itself can be loaded lazily (on first open). + * Consequently, the call to create the form content can come after the editor + * has been opened for a while (in fact, it is possible to open and close the + * editor and never create the form because no attempt has been made to show the + * page). + * + * @since 1.0 + */ +public class FormPage implements IFormPage { + private FormEditor editor; + private PageForm mform; + private int index; + private String id; + + private String partName; + + + + public void setPartName(String partName) { + this.partName = partName; + } + private static class PageForm extends ManagedForm { + public PageForm(FormPage page, ScrolledComposite form) { + super(page.getEditor().getToolkit(), form); + setContainer(page); + } + + public FormPage getPage() { + return (FormPage)getContainer(); + } + public void dirtyStateChanged() { + getPage().getEditor().editorDirtyStateChanged(); + } + public void staleStateChanged() { + if (getPage().isActive()) + refresh(); + } + } + /** + * A constructor that creates the page and initializes it with the editor. + * + * @param editor + * the parent editor + * @param id + * the unique identifier + * @param title + * the page title + */ + public FormPage(FormEditor editor, String id, String title) { + this(id, title); + initialize(editor); + } + /** + * The constructor. The parent editor need to be passed in the + * initialize method if this constructor is used. + * + * @param id + * a unique page identifier + * @param title + * a user-friendly page title + */ + public FormPage(String id, String title) { + this.id = id; + setPartName(title); + } + /** + * Initializes the form page. + * + * @see IEditorPart#init + */ +// public void init(IEditorSite site, IEditorInput input) { +// setSite(site); +// setInput(input); +// } + /** + * Primes the form page with the parent editor instance. + * + * @param editor + * the parent editor + */ + public void initialize(FormEditor editor) { + this.editor = editor; + } + /** + * Returns the parent editor. + * + * @return parent editor instance + */ + public FormEditor getEditor() { + return editor; + } + /** + * Returns the managed form owned by this page. + * + * @return the managed form + */ + public IManagedForm getManagedForm() { + return mform; + } + /** + * Implements the required method by refreshing the form when set active. + * Subclasses must call super when overriding this method. + */ + public void setActive(boolean active) { + if (active) { + // We are switching to this page - refresh it + // if needed. + if (mform != null) + mform.refresh(); + } + } + /** + * Tests if the page is active by asking the parent editor if this page is + * the currently active page. + * + * @return true if the page is currently active, + * false otherwise. + */ + public boolean isActive() { + return this.equals(editor.getActivePageInstance()); + } + /** + * Creates the part control by creating the managed form using the parent + * editor's toolkit. Subclasses should override + * createFormContent(IManagedForm) to populate the form with + * content. + * + * @param parent + * the page parent composite + */ + public void createPartControl(Composite parent) { + ScrolledComposite form = editor.getToolkit().createScrolledForm(parent); + mform = new PageForm(this, form); + BusyIndicator.showWhile(parent.getDisplay(), new Runnable() { + public void run() { + createFormContent(mform); + } + }); + } + /** + * Subclasses should override this method to create content in the form + * hosted in this page. + * + * @param managedForm + * the form hosted in this page. + */ + protected void createFormContent(IManagedForm managedForm) { + } + /** + * Returns the form page control. + * + * @return managed form's control + */ + public Control getPartControl() { + return mform != null ? mform.getForm() : null; + } + /** + * Disposes the managed form. + */ + public void dispose() { + if (mform != null) + mform.dispose(); + } + /** + * Returns the unique identifier that can be used to reference this page. + * + * @return the unique page identifier + */ + public String getId() { + return id; + } + /** + * Returns null- form page has no title image. Subclasses + * may override. + * + * @return null + */ + public Image getTitleImage() { + return null; + } + /** + * Sets the focus by delegating to the managed form. + */ + public void setFocus() { + if (mform != null) + mform.setFocus(); + } + /** + * @see org.eclipse.ui.ISaveablePart#doSave(org.eclipse.core.runtime.IProgressMonitor) + */ + public void doSave(IProgressMonitor monitor) { + if (mform != null) + mform.commit(true); + } + /** + * @see org.eclipse.ui.ISaveablePart#doSaveAs() + */ + public void doSaveAs() { + } + /** + * @see org.eclipse.ui.ISaveablePart#isSaveAsAllowed() + */ + public boolean isSaveAsAllowed() { + return false; + } + /** + * Implemented by testing if the managed form is dirty. + * + * @return true if the managed form is dirty, + * false otherwise. + * + * @see org.eclipse.ui.ISaveablePart#isDirty() + */ + public boolean isDirty() { + return mform != null ? mform.isDirty() : false; + } + /** + * Preserves the page index. + * + * @param index + * the assigned page index + */ + public void setIndex(int index) { + this.index = index; + } + /** + * Returns the saved page index. + * + * @return the page index + */ + public int getIndex() { + return index; + } + /** + * Form pages are not editors. + * + * @return false + */ + public boolean isEditor() { + return false; + } + /** + * Attempts to select and reveal the given object by passing the request to + * the managed form. + * + * @param object + * the object to select and reveal in the page if possible. + * @return true if the page has been successfully selected + * and revealed by one of the managed form parts, false + * otherwise. + */ + public boolean selectReveal(Object object) { + if (mform != null) + return mform.setInput(object); + return false; + } + /** + * By default, editor will be allowed to flip the page. + * @return true + */ + public boolean canLeaveThePage() { + return true; + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/IFormPage.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/IFormPage.java new file mode 100644 index 000000000..eb08cb59d --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/IFormPage.java @@ -0,0 +1,119 @@ +package org.argeo.cms.ui.eclipse.forms.editor; +import org.argeo.cms.ui.eclipse.forms.IManagedForm; +import org.eclipse.swt.widgets.Control; +/** + * Interface that all GUI pages need to implement in order + * to be added to FormEditor part. The interface makes + * several assumptions: + *

    + *
  • The form page has a managed form
  • + *
  • The form page has a unique id
  • + *
  • The form page can be GUI but can also wrap a complete + * editor class (in that case, it should return true + * from isEditor() method).
  • + *
  • The form page is lazy i.e. understands that + * its part control will be created at the last possible + * moment.
  • . + *
+ *

Existing editors can be wrapped by implementing + * this interface. In this case, 'isEditor' should return true. + * A common editor to wrap in TextEditor that is + * often added to show the raw source code of the file open into + * the multi-page editor. + * + * @since 1.0 + */ +public interface IFormPage { + /** + * @param editor + * the form editor that this page belongs to + */ + void initialize(FormEditor editor); + /** + * Returns the editor this page belongs to. + * + * @return the form editor + */ + FormEditor getEditor(); + /** + * Returns the managed form of this page, unless this is a source page. + * + * @return the managed form or null if this is a source page. + */ + IManagedForm getManagedForm(); + /** + * Indicates whether the page has become the active in the editor. Classes + * that implement this interface may use this method to commit the page (on + * false) or lazily create and/or populate the content on + * true. + * + * @param active + * true if page should be visible, false + * otherwise. + */ + void setActive(boolean active); + /** + * Returns true if page is currently active, false if not. + * + * @return true for active page. + */ + boolean isActive(); + /** + * Tests if the content of the page is in a state that allows the + * editor to flip to another page. Typically, pages that contain + * raw source with syntax errors should not allow editors to + * leave them until errors are corrected. + * @return true if the editor can flip to another page, + * false otherwise. + */ + boolean canLeaveThePage(); + /** + * Returns the control associated with this page. + * + * @return the control of this page if created or null if the + * page has not been shown yet. + */ + Control getPartControl(); + /** + * Page must have a unique id that can be used to show it without knowing + * its relative position in the editor. + * + * @return the unique page identifier + */ + String getId(); + /** + * Returns the position of the page in the editor. + * + * @return the zero-based index of the page in the editor. + */ + int getIndex(); + /** + * Sets the position of the page in the editor. + * + * @param index + * the zero-based index of the page in the editor. + */ + void setIndex(int index); + /** + * Tests whether this page wraps a complete editor that + * can be registered on its own, or represents a page + * that cannot exist outside the multi-page editor context. + * + * @return true if the page wraps an editor, + * false if this is a form page. + */ + boolean isEditor(); + /** + * A hint to bring the provided object into focus. If the object is in a + * tree or table control, select it. If it is shown on a scrollable page, + * ensure that it is visible. If the object is not presented in + * the page, false should be returned to allow another + * page to try. + * + * @param object + * object to select and reveal + * @return true if the request was successful, false + * otherwise. + */ + boolean selectReveal(Object object); +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditableLink.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditableLink.java new file mode 100644 index 000000000..e74de5ee8 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditableLink.java @@ -0,0 +1,75 @@ +package org.argeo.cms.ui.forms; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.cms.ui.viewers.EditablePart; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +/** Editable String that displays a browsable link when read-only */ +public class EditableLink extends EditablePropertyString implements + EditablePart { + private static final long serialVersionUID = 5055000749992803591L; + + private String type; + private String message; + private boolean readOnly; + + public EditableLink(Composite parent, int style, Node node, + String propertyName, String type, String message) + throws RepositoryException { + super(parent, style, node, propertyName, message); + this.message = message; + this.type = type; + + readOnly = SWT.READ_ONLY == (style & SWT.READ_ONLY); + if (node.hasProperty(propertyName)) { + this.setStyle(FormStyle.propertyText.style()); + this.setText(node.getProperty(propertyName).getString()); + } else { + this.setStyle(FormStyle.propertyMessage.style()); + this.setText(""); + } + } + + public void setText(String text) { + Control child = getControl(); + if (child instanceof Label) { + Label lbl = (Label) child; + if (EclipseUiUtils.isEmpty(text)) + lbl.setText(message); + else if (readOnly) + setLinkValue(lbl, text); + else + // if canEdit() we put only the value with no link + // to avoid glitches of the edition life cycle + lbl.setText(text); + } else if (child instanceof Text) { + Text txt = (Text) child; + if (EclipseUiUtils.isEmpty(text)) { + txt.setText(""); + txt.setMessage(message); + } else + txt.setText(text); + } + } + + private void setLinkValue(Label lbl, String text) { + if (FormStyle.email.style().equals(type)) + lbl.setText(FormUtils.getMailLink(text)); + else if (FormStyle.phone.style().equals(type)) + lbl.setText(FormUtils.getPhoneLink(text)); + else if (FormStyle.website.style().equals(type)) + lbl.setText(FormUtils.getUrlLink(text)); + else if (FormStyle.facebook.style().equals(type) + || FormStyle.instagram.style().equals(type) + || FormStyle.linkedIn.style().equals(type) + || FormStyle.twitter.style().equals(type)) + lbl.setText(FormUtils.getUrlLink(text)); + } +} \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditableMultiStringProperty.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditableMultiStringProperty.java new file mode 100644 index 000000000..fd3f48e3a --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditableMultiStringProperty.java @@ -0,0 +1,261 @@ +package org.argeo.cms.ui.forms; + +import java.util.List; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.ui.viewers.EditablePart; +import org.argeo.cms.ui.widgets.StyledControl; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.events.TraverseEvent; +import org.eclipse.swt.events.TraverseListener; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.layout.RowLayout; +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.eclipse.swt.widgets.Text; + +/** Display, add or remove values from a list in a CMS context */ +public class EditableMultiStringProperty extends StyledControl implements EditablePart { + private static final long serialVersionUID = -7044614381252178595L; + + private String propertyName; + private String message; + // TODO implement the ability to provide a list of possible values +// private String[] possibleValues; + private boolean canEdit; + private SelectionListener removeValueSL; + private List values; + + // TODO manage within the CSS + private int rowSpacing = 5; + private int rowMarging = 0; + private int oneValueMargingRight = 5; + private int btnWidth = 16; + private int btnHeight = 16; + private int btnHorizontalIndent = 3; + + public EditableMultiStringProperty(Composite parent, int style, Node node, String propertyName, List values, + String[] possibleValues, String addValueMsg, SelectionListener removeValueSelectionListener) + throws RepositoryException { + super(parent, style, node, true); + + this.propertyName = propertyName; + this.values = values; +// this.possibleValues = new String[] { "Un", "Deux", "Trois" }; + this.message = addValueMsg; + this.canEdit = removeValueSelectionListener != null; + this.removeValueSL = removeValueSelectionListener; + } + + public List getValues() { + return values; + } + + public void setValues(List values) { + this.values = values; + } + + // Row layout items do not need explicit layout data + protected void setControlLayoutData(Control control) { + } + + /** To be overridden */ + protected void setContainerLayoutData(Composite composite) { + composite.setLayoutData(CmsSwtUtils.fillWidth()); + } + + @Override + public Control getControl() { + return super.getControl(); + } + + @Override + protected Control createControl(Composite box, String style) { + Composite row = new Composite(box, SWT.NO_FOCUS); + row.setLayoutData(EclipseUiUtils.fillAll()); + + RowLayout rl = new RowLayout(SWT.HORIZONTAL); + rl.wrap = true; + rl.spacing = rowSpacing; + rl.marginRight = rl.marginLeft = rl.marginBottom = rl.marginTop = rowMarging; + row.setLayout(rl); + + if (values != null) { + for (final String value : values) { + if (canEdit) + createRemovableValue(row, SWT.SINGLE, value); + else + createValueLabel(row, SWT.SINGLE, value); + } + } + + if (!canEdit) + return row; + else if (isEditing()) + return createText(row, style); + else + return createLabel(row, style); + } + + /** + * Override to provide specific layout for the existing values, typically adding + * a pound (#) char for tags or anchor info for browsable links. We assume the + * parent composite already has a layout and it is the caller responsibility to + * apply corresponding layout data + */ + protected Label createValueLabel(Composite parent, int style, String value) { + Label label = new Label(parent, style); + label.setText("#" + value); + CmsSwtUtils.markup(label); + CmsSwtUtils.style(label, FormStyle.propertyText.style()); + return label; + } + + private Composite createRemovableValue(Composite parent, int style, String value) { + Composite valCmp = new Composite(parent, SWT.NO_FOCUS); + GridLayout gl = EclipseUiUtils.noSpaceGridLayout(new GridLayout(2, false)); + gl.marginRight = oneValueMargingRight; + valCmp.setLayout(gl); + + createValueLabel(valCmp, SWT.WRAP, value); + + Button deleteBtn = new Button(valCmp, SWT.FLAT); + deleteBtn.setData(FormConstants.LINKED_VALUE, value); + deleteBtn.addSelectionListener(removeValueSL); + CmsSwtUtils.style(deleteBtn, FormStyle.delete.style() + FormStyle.BUTTON_SUFFIX); + GridData gd = new GridData(); + gd.heightHint = btnHeight; + gd.widthHint = btnWidth; + gd.horizontalIndent = btnHorizontalIndent; + deleteBtn.setLayoutData(gd); + + return valCmp; + } + + protected Text createText(Composite box, String style) { + final Text text = new Text(box, getStyle()); + // The "add new value" text is not meant to change, so we can set it on + // creation + text.setMessage(message); + CmsSwtUtils.style(text, style); + text.setFocus(); + + text.addTraverseListener(new TraverseListener() { + private static final long serialVersionUID = 1L; + + public void keyTraversed(TraverseEvent e) { + if (e.keyCode == SWT.CR) { + addValue(text); + e.doit = false; + } + } + }); + + // The OK button does not work with the focusOut listener + // because focus out is called before the OK button is pressed + + // // we must call layout() now so that the row data can compute the + // height + // // of the other controls. + // text.getParent().layout(); + // int height = text.getSize().y; + // + // Button okBtn = new Button(box, SWT.BORDER | SWT.PUSH | SWT.BOTTOM); + // okBtn.setText("OK"); + // RowData rd = new RowData(SWT.DEFAULT, height - 2); + // okBtn.setLayoutData(rd); + // + // okBtn.addSelectionListener(new SelectionAdapter() { + // private static final long serialVersionUID = 2780819012423622369L; + // + // @Override + // public void widgetSelected(SelectionEvent e) { + // addValue(text); + // } + // }); + + return text; + } + + /** Performs the real addition, overwrite to make further sanity checks */ + protected void addValue(Text text) { + String value = text.getText(); + String errMsg = null; + + if (EclipseUiUtils.isEmpty(value)) + return; + + if (values.contains(value)) + errMsg = "Dupplicated value: " + value + ", please correct and try again"; + if (errMsg != null) + MessageDialog.openError(this.getShell(), "Addition not allowed", errMsg); + else { + values.add(value); + Composite newCmp = createRemovableValue(text.getParent(), SWT.SINGLE, value); + newCmp.moveAbove(text); + text.setText(""); + newCmp.getParent().layout(); + } + } + + protected Label createLabel(Composite box, String style) { + if (canEdit) { + Label lbl = new Label(box, getStyle()); + lbl.setText(message); + CmsSwtUtils.style(lbl, style); + CmsSwtUtils.markup(lbl); + if (mouseListener != null) + lbl.addMouseListener(mouseListener); + return lbl; + } + return null; + } + + protected void clear(boolean deep) { + Control child = getControl(); + if (deep) + super.clear(deep); + else { + child.getParent().dispose(); + } + } + + public void setText(String text) { + Control child = getControl(); + if (child instanceof Label) { + Label lbl = (Label) child; + if (canEdit) + lbl.setText(text); + else + lbl.setText(""); + } else if (child instanceof Text) { + Text txt = (Text) child; + txt.setText(text); + } + } + + public synchronized void startEditing() { + CmsSwtUtils.style(getControl(), FormStyle.propertyText.style()); +// getControl().setData(STYLE, FormStyle.propertyText.style()); + super.startEditing(); + } + + public synchronized void stopEditing() { + CmsSwtUtils.style(getControl(), FormStyle.propertyMessage.style()); +// getControl().setData(STYLE, FormStyle.propertyMessage.style()); + super.stopEditing(); + } + + public String getPropertyName() { + return propertyName; + } +} \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditablePropertyDate.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditablePropertyDate.java new file mode 100644 index 000000000..8591a925f --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditablePropertyDate.java @@ -0,0 +1,298 @@ +package org.argeo.cms.ui.forms; + +import java.text.DateFormat; +import java.util.Calendar; +import java.util.GregorianCalendar; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.ui.viewers.EditablePart; +import org.argeo.cms.ui.widgets.StyledControl; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.ShellAdapter; +import org.eclipse.swt.events.ShellEvent; +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.DateTime; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; +import org.eclipse.swt.widgets.Text; + +/** CMS form part to display and edit a date */ +public class EditablePropertyDate extends StyledControl implements EditablePart { + private static final long serialVersionUID = 2500215515778162468L; + + // Context + private String propertyName; + private String message; + private DateFormat dateFormat; + + // UI Objects + private Text dateTxt; + private Button openCalBtn; + + // TODO manage within the CSS + private int fieldBtnSpacing = 5; + + /** + * + * @param parent + * @param style + * @param node + * @param propertyName + * @param message + * @param dateFormat provide a {@link DateFormat} as contract to be able to + * read/write dates as strings + * @throws RepositoryException + */ + public EditablePropertyDate(Composite parent, int style, Node node, String propertyName, String message, + DateFormat dateFormat) throws RepositoryException { + super(parent, style, node, false); + + this.propertyName = propertyName; + this.message = message; + this.dateFormat = dateFormat; + + if (node.hasProperty(propertyName)) { + this.setStyle(FormStyle.propertyText.style()); + this.setText(dateFormat.format(node.getProperty(propertyName).getDate().getTime())); + } else { + this.setStyle(FormStyle.propertyMessage.style()); + this.setText(message); + } + } + + public void setText(String text) { + Control child = getControl(); + if (child instanceof Label) { + Label lbl = (Label) child; + if (EclipseUiUtils.isEmpty(text)) + lbl.setText(message); + else + lbl.setText(text); + } else if (child instanceof Text) { + Text txt = (Text) child; + if (EclipseUiUtils.isEmpty(text)) { + txt.setText(""); + } else + txt.setText(text); + } + } + + public synchronized void startEditing() { + // if (dateTxt != null && !dateTxt.isDisposed()) + CmsSwtUtils.style(getControl(), FormStyle.propertyText); +// getControl().setData(STYLE, FormStyle.propertyText.style()); + super.startEditing(); + } + + public synchronized void stopEditing() { + if (EclipseUiUtils.isEmpty(dateTxt.getText())) + CmsSwtUtils.style(getControl(), FormStyle.propertyMessage); +// getControl().setData(STYLE, FormStyle.propertyMessage.style()); + else + CmsSwtUtils.style(getControl(), FormStyle.propertyText); +// getControl().setData(STYLE, FormStyle.propertyText.style()); + super.stopEditing(); + } + + public String getPropertyName() { + return propertyName; + } + + @Override + protected Control createControl(Composite box, String style) { + if (isEditing()) { + return createCustomEditableControl(box, style); + } else + return createLabel(box, style); + } + + protected Label createLabel(Composite box, String style) { + Label lbl = new Label(box, getStyle() | SWT.WRAP); + lbl.setLayoutData(CmsSwtUtils.fillWidth()); + CmsSwtUtils.style(lbl, style); + CmsSwtUtils.markup(lbl); + if (mouseListener != null) + lbl.addMouseListener(mouseListener); + return lbl; + } + + private Control createCustomEditableControl(Composite box, String style) { + box.setLayoutData(CmsSwtUtils.fillWidth()); + Composite dateComposite = new Composite(box, SWT.NONE); + GridLayout gl = EclipseUiUtils.noSpaceGridLayout(new GridLayout(2, false)); + gl.horizontalSpacing = fieldBtnSpacing; + dateComposite.setLayout(gl); + dateTxt = new Text(dateComposite, SWT.BORDER); + CmsSwtUtils.style(dateTxt, style); + dateTxt.setLayoutData(new GridData(120, SWT.DEFAULT)); + dateTxt.setToolTipText( + "Enter a date with form \"" + FormUtils.DEFAULT_SHORT_DATE_FORMAT + "\" or use the calendar"); + openCalBtn = new Button(dateComposite, SWT.FLAT); + CmsSwtUtils.style(openCalBtn, FormStyle.calendar.style() + FormStyle.BUTTON_SUFFIX); + GridData gd = new GridData(SWT.CENTER, SWT.CENTER, false, false); + gd.heightHint = 17; + openCalBtn.setLayoutData(gd); + // openCalBtn.setImage(PeopleRapImages.CALENDAR_BTN); + + openCalBtn.addSelectionListener(new SelectionAdapter() { + private static final long serialVersionUID = 1L; + + public void widgetSelected(SelectionEvent event) { + CalendarPopup popup = new CalendarPopup(dateTxt); + popup.open(); + } + }); + + // dateTxt.addFocusListener(new FocusListener() { + // private static final long serialVersionUID = 1L; + // + // @Override + // public void focusLost(FocusEvent event) { + // String newVal = dateTxt.getText(); + // // Enable reset of the field + // if (FormUtils.notNull(newVal)) + // calendar = null; + // else { + // try { + // Calendar newCal = parseDate(newVal); + // // DateText.this.setText(newCal); + // calendar = newCal; + // } catch (ParseException pe) { + // // Silent. Manage error popup? + // if (calendar != null) + // EditablePropertyDate.this.setText(calendar); + // } + // } + // } + // + // @Override + // public void focusGained(FocusEvent event) { + // } + // }); + return dateTxt; + } + + protected void clear(boolean deep) { + Control child = getControl(); + if (deep || child instanceof Label) + super.clear(deep); + else { + child.getParent().dispose(); + } + } + + /** Enable setting a custom tooltip on the underlying text */ + @Deprecated + public void setToolTipText(String toolTipText) { + dateTxt.setToolTipText(toolTipText); + } + + @Deprecated + /** Enable setting a custom message on the underlying text */ + public void setMessage(String message) { + dateTxt.setMessage(message); + } + + @Deprecated + public void setText(Calendar cal) { + String newValueStr = ""; + if (cal != null) + newValueStr = dateFormat.format(cal.getTime()); + if (!newValueStr.equals(dateTxt.getText())) + dateTxt.setText(newValueStr); + } + + // UTILITIES TO MANAGE THE CALENDAR POPUP + // TODO manage the popup shell in a cleaner way + private class CalendarPopup extends Shell { + private static final long serialVersionUID = 1L; + private DateTime dateTimeCtl; + + public CalendarPopup(Control source) { + super(source.getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP); + populate(); + // Add border and shadow style + CmsSwtUtils.markup(CalendarPopup.this); + CmsSwtUtils.style(CalendarPopup.this, FormStyle.popupCalendar.style()); + pack(); + layout(); + setLocation(source.toDisplay((source.getLocation().x - 2), (source.getSize().y) + 3)); + + addShellListener(new ShellAdapter() { + private static final long serialVersionUID = 5178980294808435833L; + + @Override + public void shellDeactivated(ShellEvent e) { + close(); + dispose(); + } + }); + open(); + } + + private void setProperty() { + // Direct set does not seems to work. investigate + // cal.set(dateTimeCtl.getYear(), dateTimeCtl.getMonth(), + // dateTimeCtl.getDay(), 12, 0); + Calendar cal = new GregorianCalendar(); + cal.set(Calendar.YEAR, dateTimeCtl.getYear()); + cal.set(Calendar.MONTH, dateTimeCtl.getMonth()); + cal.set(Calendar.DAY_OF_MONTH, dateTimeCtl.getDay()); + String dateStr = dateFormat.format(cal.getTime()); + dateTxt.setText(dateStr); + } + + protected void populate() { + setLayout(EclipseUiUtils.noSpaceGridLayout()); + + dateTimeCtl = new DateTime(this, SWT.CALENDAR); + dateTimeCtl.setLayoutData(EclipseUiUtils.fillAll()); + + Calendar calendar = FormUtils.parseDate(dateFormat, dateTxt.getText()); + + if (calendar != null) + dateTimeCtl.setDate(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), + calendar.get(Calendar.DAY_OF_MONTH)); + + dateTimeCtl.addSelectionListener(new SelectionAdapter() { + private static final long serialVersionUID = -8414377364434281112L; + + @Override + public void widgetSelected(SelectionEvent e) { + setProperty(); + } + }); + + dateTimeCtl.addMouseListener(new MouseListener() { + private static final long serialVersionUID = 1L; + + @Override + public void mouseUp(MouseEvent e) { + } + + @Override + public void mouseDown(MouseEvent e) { + } + + @Override + public void mouseDoubleClick(MouseEvent e) { + setProperty(); + close(); + dispose(); + } + }); + } + } +} \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditablePropertyString.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditablePropertyString.java new file mode 100644 index 000000000..092009355 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditablePropertyString.java @@ -0,0 +1,80 @@ +package org.argeo.cms.ui.forms; + +import static org.argeo.cms.ui.forms.FormStyle.propertyMessage; +import static org.argeo.cms.ui.forms.FormStyle.propertyText; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.ui.viewers.EditablePart; +import org.argeo.cms.ui.widgets.EditableText; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +/** Editable String in a CMS context */ +public class EditablePropertyString extends EditableText implements EditablePart { + private static final long serialVersionUID = 5055000749992803591L; + + private String propertyName; + private String message; + + // encode the '&' character in rap + private final static String AMPERSAND = "&"; + private final static String AMPERSAND_REGEX = "&(?![#a-zA-Z0-9]+;)"; + + public EditablePropertyString(Composite parent, int style, Node node, String propertyName, String message) + throws RepositoryException { + super(parent, style, node, true); + //setUseTextAsLabel(true); + this.propertyName = propertyName; + this.message = message; + + if (node.hasProperty(propertyName)) { + this.setStyle(propertyText.style()); + this.setText(node.getProperty(propertyName).getString()); + } else { + this.setStyle(propertyMessage.style()); + this.setText(message + " "); + } + } + + public void setText(String text) { + Control child = getControl(); + if (child instanceof Label) { + Label lbl = (Label) child; + if (EclipseUiUtils.isEmpty(text)) + lbl.setText(message + " "); + else + // TODO enhance this + lbl.setText(text.replaceAll(AMPERSAND_REGEX, AMPERSAND)); + } else if (child instanceof Text) { + Text txt = (Text) child; + if (EclipseUiUtils.isEmpty(text)) { + txt.setText(""); + txt.setMessage(message + " "); + } else + txt.setText(text.replaceAll("
", "\n")); + } + } + + public synchronized void startEditing() { + CmsSwtUtils.style(getControl(), FormStyle.propertyText); + super.startEditing(); + } + + public synchronized void stopEditing() { + if (EclipseUiUtils.isEmpty(((Text) getControl()).getText())) + CmsSwtUtils.style(getControl(), FormStyle.propertyMessage); + else + CmsSwtUtils.style(getControl(), FormStyle.propertyText); + super.stopEditing(); + } + + public String getPropertyName() { + return propertyName; + } +} \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormConstants.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormConstants.java new file mode 100644 index 000000000..fe9f7e7d7 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormConstants.java @@ -0,0 +1,7 @@ +package org.argeo.cms.ui.forms; + +/** Constants used in the various CMS Forms */ +public interface FormConstants { + // DATAKEYS + public final static String LINKED_VALUE = "LinkedValue"; +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormEditorHeader.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormEditorHeader.java new file mode 100644 index 000000000..f3a56f7b9 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormEditorHeader.java @@ -0,0 +1,114 @@ +package org.argeo.cms.ui.forms; + +import java.util.Observable; +import java.util.Observer; + +import javax.jcr.Node; + +import org.argeo.api.cms.CmsEditable; +import org.argeo.cms.swt.CmsSwtUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; + +/** Add life cycle management abilities to an editable form page */ +public class FormEditorHeader implements SelectionListener, Observer { + private static final long serialVersionUID = 7392898696542484282L; + + // private final Node context; + private final CmsEditable cmsEditable; + private Button publishBtn; + + // Should we provide here the ability to switch from read only to edition + // mode? + // private Button editBtn; + // private boolean readOnly; + + // TODO add information about the current node status, typically if it is + // dirty or not + + private Composite parent; + private Composite display; + private Object layoutData; + + public FormEditorHeader(Composite parent, int style, Node context, + CmsEditable cmsEditable) { + this.cmsEditable = cmsEditable; + this.parent = parent; + // readOnly = SWT.READ_ONLY == (style & SWT.READ_ONLY); + // this.context = context; + if (this.cmsEditable instanceof Observable) + ((Observable) this.cmsEditable).addObserver(this); + refresh(); + } + + public void setLayoutData(Object layoutData) { + this.layoutData = layoutData; + if (display != null && !display.isDisposed()) + display.setLayoutData(layoutData); + } + + protected void refresh() { + if (display != null && !display.isDisposed()) + display.dispose(); + + display = new Composite(parent, SWT.NONE); + display.setLayoutData(layoutData); + + CmsSwtUtils.style(display, FormStyle.header.style()); + display.setBackgroundMode(SWT.INHERIT_FORCE); + + display.setLayout(CmsSwtUtils.noSpaceGridLayout()); + + publishBtn = createSimpleBtn(display, getPublishButtonLabel()); + display.moveAbove(null); + parent.layout(); + } + + private Button createSimpleBtn(Composite parent, String label) { + Button button = new Button(parent, SWT.FLAT | SWT.PUSH); + button.setText(label); + CmsSwtUtils.style(button, FormStyle.header.style()); + button.addSelectionListener(this); + return button; + } + + private String getPublishButtonLabel() { + // Rather check if the current node differs from what has been + // previously committed + // For the time being, we always reach here, the underlying CmsEditable + // is always editing. + if (cmsEditable.isEditing()) + return " Publish "; + else + return " Edit "; + } + + @Override + public void widgetSelected(SelectionEvent e) { + if (e.getSource() == publishBtn) { + // For the time being, the underlying CmsEditable + // is always editing when we reach this point + if (cmsEditable.isEditing()) { + // we always leave the node in a check outed state + cmsEditable.stopEditing(); + cmsEditable.startEditing(); + } else { + cmsEditable.startEditing(); + } + } + } + + @Override + public void widgetDefaultSelected(SelectionEvent e) { + } + + @Override + public void update(Observable o, Object arg) { + if (o == cmsEditable) { + refresh(); + } + } +} \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormPageViewer.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormPageViewer.java new file mode 100644 index 000000000..cc732d49d --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormPageViewer.java @@ -0,0 +1,608 @@ +package org.argeo.cms.ui.forms; + +import java.io.IOException; +import java.io.InputStream; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.Value; +import javax.jcr.ValueFormatException; + +import org.argeo.api.cms.Cms2DSize; +import org.argeo.api.cms.CmsEditable; +import org.argeo.api.cms.CmsImageManager; +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.ui.viewers.AbstractPageViewer; +import org.argeo.cms.ui.viewers.EditablePart; +import org.argeo.cms.ui.viewers.Section; +import org.argeo.cms.ui.viewers.SectionPart; +import org.argeo.cms.ui.widgets.EditableImage; +import org.argeo.cms.ui.widgets.Img; +import org.argeo.cms.ui.widgets.StyledControl; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.argeo.jcr.JcrException; +import org.argeo.jcr.JcrUtils; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.rap.fileupload.FileDetails; +import org.eclipse.rap.fileupload.FileUploadEvent; +import org.eclipse.rap.fileupload.FileUploadHandler; +import org.eclipse.rap.fileupload.FileUploadListener; +import org.eclipse.rap.fileupload.FileUploadReceiver; +import org.eclipse.rap.rwt.service.ServerPushSession; +import org.eclipse.rap.rwt.widgets.FileUpload; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.SelectionListener; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.FormAttachment; +import org.eclipse.swt.layout.FormData; +import org.eclipse.swt.layout.FormLayout; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.layout.RowLayout; +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.eclipse.swt.widgets.Text; + +/** Manage life cycle of a form page that is linked to a given node */ +public class FormPageViewer extends AbstractPageViewer { + private final static CmsLog log = CmsLog.getLog(FormPageViewer.class); + private static final long serialVersionUID = 5277789504209413500L; + + private final Section mainSection; + + // TODO manage within the CSS + private Integer labelColWidth = null; + private int rowLayoutHSpacing = 8; + + // Context cached in the viewer + // The reference to translate from text to calendar and reverse + private DateFormat dateFormat = new SimpleDateFormat(FormUtils.DEFAULT_SHORT_DATE_FORMAT); + private CmsImageManager imageManager; + private FileUploadListener fileUploadListener; + + public FormPageViewer(Section mainSection, int style, CmsEditable cmsEditable) throws RepositoryException { + super(mainSection, style, cmsEditable); + this.mainSection = mainSection; + + if (getCmsEditable().canEdit()) { + fileUploadListener = new FUL(); + } + } + + @Override + protected void prepare(EditablePart part, Object caretPosition) { + if (part instanceof Img) { + ((Img) part).setFileUploadListener(fileUploadListener); + } + } + + /** To be overridden.Save the edited part. */ + protected void save(EditablePart part) throws RepositoryException { + Node node = null; + if (part instanceof EditableMultiStringProperty) { + EditableMultiStringProperty ept = (EditableMultiStringProperty) part; + // SWT : View + List values = ept.getValues(); + // JCR : Model + node = ept.getNode(); + String propName = ept.getPropertyName(); + if (values.isEmpty()) { + if (node.hasProperty(propName)) + node.getProperty(propName).remove(); + } else { + node.setProperty(propName, values.toArray(new String[0])); + } + // => Viewer : Controller + } else if (part instanceof EditablePropertyString) { + EditablePropertyString ept = (EditablePropertyString) part; + // SWT : View + String txt = ((Text) ept.getControl()).getText(); + // JCR : Model + node = ept.getNode(); + String propName = ept.getPropertyName(); + if (EclipseUiUtils.isEmpty(txt)) { + if (node.hasProperty(propName)) + node.getProperty(propName).remove(); + } else { + setPropertySilently(node, propName, txt); + // node.setProperty(propName, txt); + } + // node.getSession().save(); + // => Viewer : Controller + } else if (part instanceof EditablePropertyDate) { + EditablePropertyDate ept = (EditablePropertyDate) part; + Calendar cal = FormUtils.parseDate(dateFormat, ((Text) ept.getControl()).getText()); + node = ept.getNode(); + String propName = ept.getPropertyName(); + if (cal == null) { + if (node.hasProperty(propName)) + node.getProperty(propName).remove(); + } else { + node.setProperty(propName, cal); + } + // node.getSession().save(); + // => Viewer : Controller + } + // TODO: make this configurable, sometimes we do not want to save the + // current session at this stage + if (node != null && node.getSession().hasPendingChanges()) { + JcrUtils.updateLastModified(node, true); + node.getSession().save(); + } + } + + @Override + protected void updateContent(EditablePart part) throws RepositoryException { + if (part instanceof EditableMultiStringProperty) { + EditableMultiStringProperty ept = (EditableMultiStringProperty) part; + // SWT : View + Node node = ept.getNode(); + String propName = ept.getPropertyName(); + List valStrings = new ArrayList(); + if (node.hasProperty(propName)) { + Value[] values = node.getProperty(propName).getValues(); + for (Value val : values) + valStrings.add(val.getString()); + } + ept.setValues(valStrings); + } else if (part instanceof EditablePropertyString) { + // || part instanceof EditableLink + EditablePropertyString ept = (EditablePropertyString) part; + // JCR : Model + Node node = ept.getNode(); + String propName = ept.getPropertyName(); + if (node.hasProperty(propName)) { + String value = node.getProperty(propName).getString(); + ept.setText(value); + } else + ept.setText(""); + // => Viewer : Controller + } else if (part instanceof EditablePropertyDate) { + EditablePropertyDate ept = (EditablePropertyDate) part; + // JCR : Model + Node node = ept.getNode(); + String propName = ept.getPropertyName(); + if (node.hasProperty(propName)) + ept.setText(dateFormat.format(node.getProperty(propName).getDate().getTime())); + else + ept.setText(""); + } else if (part instanceof SectionPart) { + SectionPart sectionPart = (SectionPart) part; + Node partNode = sectionPart.getNode(); + // use control AFTER setting style, since it may have been reset + if (part instanceof EditableImage) { + EditableImage editableImage = (EditableImage) part; + imageManager().load(partNode, part.getControl(), editableImage.getPreferredImageSize()); + } + } + } + + // FILE UPLOAD LISTENER + protected class FUL implements FileUploadListener { + + public FUL() { + } + + public void uploadProgress(FileUploadEvent event) { + // TODO Monitor upload progress + } + + public void uploadFailed(FileUploadEvent event) { + throw new IllegalStateException("Upload failed " + event, event.getException()); + } + + public void uploadFinished(FileUploadEvent event) { + for (FileDetails file : event.getFileDetails()) { + if (log.isDebugEnabled()) + log.debug("Received: " + file.getFileName()); + } + mainSection.getDisplay().syncExec(new Runnable() { + @Override + public void run() { + saveEdit(); + } + }); + FileUploadHandler uploadHandler = (FileUploadHandler) event.getSource(); + uploadHandler.dispose(); + } + } + + // FOCUS OUT LISTENER + protected FocusListener createFocusListener() { + return new FocusOutListener(); + } + + private class FocusOutListener implements FocusListener { + private static final long serialVersionUID = -6069205786732354186L; + + @Override + public void focusLost(FocusEvent event) { + saveEdit(); + } + + @Override + public void focusGained(FocusEvent event) { + // does nothing; + } + } + + // MOUSE LISTENER + @Override + protected MouseListener createMouseListener() { + return new ML(); + } + + private class ML extends MouseAdapter { + private static final long serialVersionUID = 8526890859876770905L; + + @Override + public void mouseDoubleClick(MouseEvent e) { + if (e.button == 1) { + Control source = (Control) e.getSource(); + if (getCmsEditable().canEdit()) { + if (getCmsEditable().isEditing() && !(getEdited() instanceof Img)) { + if (source == mainSection) + return; + EditablePart part = findDataParent(source); + upload(part); + } else { + getCmsEditable().startEditing(); + } + } + } + } + + @Override + public void mouseDown(MouseEvent e) { + if (getCmsEditable().isEditing()) { + if (e.button == 1) { + Control source = (Control) e.getSource(); + EditablePart composite = findDataParent(source); + Point point = new Point(e.x, e.y); + if (!(composite instanceof Img)) + edit(composite, source.toDisplay(point)); + } else if (e.button == 3) { + // EditablePart composite = findDataParent((Control) e + // .getSource()); + // if (styledTools != null) + // styledTools.show(composite, new Point(e.x, e.y)); + } + } + } + + protected synchronized void upload(EditablePart part) { + if (part instanceof SectionPart) { + if (part instanceof Img) { + if (getEdited() == part) + return; + edit(part, null); + layout(part.getControl()); + } + } + } + } + + @Override + public Control getControl() { + return mainSection; + } + + protected CmsImageManager imageManager() { + if (imageManager == null) + imageManager = (CmsImageManager) CmsSwtUtils.getCmsView(mainSection).getImageManager(); + return imageManager; + } + + // LOCAL UI HELPERS + protected Section createSectionIfNeeded(Composite body, Node node) throws RepositoryException { + Section section = null; + if (node != null) { + section = new Section(body, SWT.NO_FOCUS, node); + section.setLayoutData(CmsSwtUtils.fillWidth()); + section.setLayout(CmsSwtUtils.noSpaceGridLayout()); + } + return section; + } + + protected void createSimpleLT(Composite bodyRow, Node node, String propName, String label, String msg) + throws RepositoryException { + if (getCmsEditable().canEdit() || node.hasProperty(propName)) { + createPropertyLbl(bodyRow, label); + EditablePropertyString eps = new EditablePropertyString(bodyRow, SWT.WRAP | SWT.LEFT, node, propName, msg); + eps.setMouseListener(getMouseListener()); + eps.setFocusListener(getFocusListener()); + eps.setLayoutData(CmsSwtUtils.fillWidth()); + } + } + + protected void createMultiStringLT(Composite bodyRow, Node node, String propName, String label, String msg) + throws RepositoryException { + boolean canEdit = getCmsEditable().canEdit(); + if (canEdit || node.hasProperty(propName)) { + createPropertyLbl(bodyRow, label); + + List valueStrings = new ArrayList(); + + if (node.hasProperty(propName)) { + Value[] values = node.getProperty(propName).getValues(); + for (Value value : values) + valueStrings.add(value.getString()); + } + + // TODO use a drop down to display possible values to the end user + EditableMultiStringProperty emsp = new EditableMultiStringProperty(bodyRow, SWT.SINGLE | SWT.LEAD, node, + propName, valueStrings, new String[] { "Implement this" }, msg, + canEdit ? getRemoveValueSelListener() : null); + addListeners(emsp); + // emsp.setMouseListener(getMouseListener()); + emsp.setStyle(FormStyle.propertyMessage.style()); + emsp.setLayoutData(CmsSwtUtils.fillWidth()); + } + } + + protected Label createPropertyLbl(Composite parent, String value) { + return createPropertyLbl(parent, value, SWT.NONE); + } + + protected Label createPropertyLbl(Composite parent, String value, int vAlign) { + // boolean isSmall = CmsView.getCmsView(parent).getUxContext().isSmall(); + Label label = new Label(parent, SWT.LEAD | SWT.WRAP); + label.setText(value + " "); + CmsSwtUtils.style(label, FormStyle.propertyLabel.style()); + GridData gd = new GridData(SWT.LEAD, vAlign, false, false); + if (labelColWidth != null) + gd.widthHint = labelColWidth; + label.setLayoutData(gd); + return label; + } + + protected Label newStyledLabel(Composite parent, String style, String value) { + Label label = new Label(parent, SWT.NONE); + label.setText(value); + CmsSwtUtils.style(label, style); + return label; + } + + protected Composite createRowLayoutComposite(Composite parent) throws RepositoryException { + Composite bodyRow = new Composite(parent, SWT.NO_FOCUS); + bodyRow.setLayoutData(CmsSwtUtils.fillWidth()); + RowLayout rl = new RowLayout(SWT.WRAP); + rl.type = SWT.HORIZONTAL; + rl.spacing = rowLayoutHSpacing; + rl.marginHeight = rl.marginWidth = 0; + rl.marginTop = rl.marginBottom = rl.marginLeft = rl.marginRight = 0; + bodyRow.setLayout(rl); + return bodyRow; + } + + protected Composite createAddImgComposite(final Section section, Composite parent, final Node parentNode) + throws RepositoryException { + + Composite body = new Composite(parent, SWT.NO_FOCUS); + body.setLayout(new GridLayout()); + + FormFileUploadReceiver receiver = new FormFileUploadReceiver(section, parentNode, null); + final FileUploadHandler currentUploadHandler = new FileUploadHandler(receiver); + if (fileUploadListener != null) + currentUploadHandler.addUploadListener(fileUploadListener); + + // Button creation + final FileUpload fileUpload = new FileUpload(body, SWT.BORDER); + fileUpload.setText("Import an image"); + fileUpload.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true)); + fileUpload.addSelectionListener(new SelectionAdapter() { + private static final long serialVersionUID = 4869523412991968759L; + + @Override + public void widgetSelected(SelectionEvent e) { + ServerPushSession pushSession = new ServerPushSession(); + pushSession.start(); + String uploadURL = currentUploadHandler.getUploadUrl(); + fileUpload.submit(uploadURL); + } + }); + + return body; + } + + protected class FormFileUploadReceiver extends FileUploadReceiver { + + private Node context; + private Section section; + private String name; + + public FormFileUploadReceiver(Section section, Node context, String name) { + this.context = context; + this.section = section; + this.name = name; + } + + @Override + public void receive(InputStream stream, FileDetails details) throws IOException { + + if (name == null) + name = details.getFileName(); + + // TODO clean image name more carefully + String cleanedName = name.replaceAll("[^a-zA-Z0-9-.]", ""); + // We add a unique prefix to workaround the cache issue: when + // deleting and re-adding a new image with same name, the end user + // browser will use the cache and the image will remain unchanged + // for a while + cleanedName = System.currentTimeMillis() % 100000 + "_" + cleanedName; + + imageManager().uploadImage(context, context, cleanedName, stream, details.getContentType()); + // TODO clean refresh strategy + section.getDisplay().asyncExec(new Runnable() { + @Override + public void run() { + try { + FormPageViewer.this.refresh(section); + section.layout(); + section.getParent().layout(); + } catch (RepositoryException re) { + throw new JcrException("Unable to refresh " + "image section for " + context, re); + } + } + }); + } + } + + protected void addListeners(StyledControl control) { + control.setMouseListener(getMouseListener()); + control.setFocusListener(getFocusListener()); + } + + protected Img createImgComposite(Composite parent, Node node, Point preferredSize) throws RepositoryException { + Img img = new Img(parent, SWT.NONE, node, new Cms2DSize(preferredSize.x, preferredSize.y)) { + private static final long serialVersionUID = 1297900641952417540L; + + @Override + protected void setContainerLayoutData(Composite composite) { + composite.setLayoutData(CmsSwtUtils.grabWidth(SWT.CENTER, SWT.DEFAULT)); + } + + @Override + protected void setControlLayoutData(Control control) { + control.setLayoutData(CmsSwtUtils.grabWidth(SWT.CENTER, SWT.DEFAULT)); + } + }; + img.setLayoutData(CmsSwtUtils.grabWidth(SWT.CENTER, SWT.DEFAULT)); + updateContent(img); + addListeners(img); + return img; + } + + protected Composite addDeleteAbility(final Section section, final Node sessionNode, int topWeight, + int rightWeight) { + Composite comp = new Composite(section, SWT.NONE); + comp.setLayoutData(CmsSwtUtils.fillAll()); + comp.setLayout(new FormLayout()); + + // The body to be populated + Composite body = new Composite(comp, SWT.NO_FOCUS); + body.setLayoutData(EclipseUiUtils.fillFormData()); + + if (getCmsEditable().canEdit()) { + // the delete button + Button deleteBtn = new Button(comp, SWT.FLAT); + CmsSwtUtils.style(deleteBtn, FormStyle.deleteOverlay.style()); + FormData formData = new FormData(); + formData.right = new FormAttachment(rightWeight, 0); + formData.top = new FormAttachment(topWeight, 0); + deleteBtn.setLayoutData(formData); + deleteBtn.moveAbove(body); + + deleteBtn.addSelectionListener(new SelectionAdapter() { + private static final long serialVersionUID = 4304223543657238462L; + + @Override + public void widgetSelected(SelectionEvent e) { + super.widgetSelected(e); + if (MessageDialog.openConfirm(section.getShell(), "Confirm deletion", + "Are you really you want to remove this?")) { + Session session; + try { + session = sessionNode.getSession(); + Section parSection = section.getParentSection(); + sessionNode.remove(); + session.save(); + refresh(parSection); + layout(parSection); + } catch (RepositoryException re) { + throw new JcrException("Unable to delete " + sessionNode, re); + } + + } + + } + }); + } + return body; + } + +// // LOCAL HELPERS FOR NODE MANAGEMENT +// private Node getOrCreateNode(Node parent, String nodeName, String nodeType) throws RepositoryException { +// Node node = null; +// if (getCmsEditable().canEdit() && !parent.hasNode(nodeName)) { +// node = JcrUtils.mkdirs(parent, nodeName, nodeType); +// parent.getSession().save(); +// } +// +// if (getCmsEditable().canEdit() || parent.hasNode(nodeName)) +// node = parent.getNode(nodeName); +// +// return node; +// } + + private SelectionListener getRemoveValueSelListener() { + return new SelectionAdapter() { + private static final long serialVersionUID = 9022259089907445195L; + + @Override + public void widgetSelected(SelectionEvent e) { + Object source = e.getSource(); + if (source instanceof Button) { + Button btn = (Button) source; + Object obj = btn.getData(FormConstants.LINKED_VALUE); + EditablePart ep = findDataParent(btn); + if (ep != null && ep instanceof EditableMultiStringProperty) { + EditableMultiStringProperty emsp = (EditableMultiStringProperty) ep; + List values = emsp.getValues(); + if (values.contains(obj)) { + values.remove(values.indexOf(obj)); + emsp.setValues(values); + try { + save(emsp); + // TODO workaround to force refresh + edit(emsp, 0); + cancelEdit(); + } catch (RepositoryException e1) { + throw new JcrException("Unable to remove value " + obj, e1); + } + layout(emsp); + } + } + } + } + }; + } + + protected void setPropertySilently(Node node, String propName, String value) throws RepositoryException { + try { + // TODO Clean this: + // Format strings to replace \n + value = value.replaceAll("\n", "
"); + // Do not make the update if validation fails + try { + MarkupValidatorCopy.getInstance().validate(value); + } catch (Exception e) { + log.warn("Cannot set [" + value + "] on prop " + propName + "of " + node + + ", String cannot be validated - " + e.getMessage()); + return; + } + // TODO check if the newly created property is of the correct type, + // otherwise the property will be silently created with a STRING + // property type. + node.setProperty(propName, value); + } catch (ValueFormatException vfe) { + log.warn("Cannot set [" + value + "] on prop " + propName + "of " + node + " - " + vfe.getMessage()); + } + } +} \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormStyle.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormStyle.java new file mode 100644 index 000000000..24067eaaa --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormStyle.java @@ -0,0 +1,29 @@ +package org.argeo.cms.ui.forms; + +import org.argeo.api.cms.CmsStyle; + +/** Syles used */ +public enum FormStyle implements CmsStyle { + // Main + form, title, + // main part + header, headerBtn, headerCombo, section, sectionHeader, + // Property fields + propertyLabel, propertyText, propertyMessage, errorMessage, + // Date + popupCalendar, + // Buttons + starred, unstarred, starOverlay, editOverlay, deleteOverlay, updateOverlay, deleteOverlaySmall, calendar, delete, + // Contacts + email, address, phone, website, + // Social Media + facebook, twitter, linkedIn, instagram; + + @Override + public String getClassPrefix() { + return "argeo-form"; + } + + // TODO clean button style management + public final static String BUTTON_SUFFIX = "_btn"; +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormUtils.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormUtils.java new file mode 100644 index 000000000..1a445bd76 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormUtils.java @@ -0,0 +1,196 @@ +package org.argeo.cms.ui.forms; + +import java.text.DateFormat; +import java.text.ParseException; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.api.cms.CmsView; +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.CmsException; +import org.argeo.cms.ui.util.CmsUiUtils; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.eclipse.jface.fieldassist.ControlDecoration; +import org.eclipse.jface.fieldassist.FieldDecorationRegistry; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IDoubleClickListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +/** Utilitary methods to ease implementation of CMS forms */ +public class FormUtils { + private final static CmsLog log = CmsLog.getLog(FormUtils.class); + + public final static String DEFAULT_SHORT_DATE_FORMAT = "dd/MM/yyyy"; + + /** Best effort to convert a String to a calendar. Fails silently */ + public static Calendar parseDate(DateFormat dateFormat, String calStr) { + Calendar cal = null; + if (EclipseUiUtils.notEmpty(calStr)) { + try { + Date date = dateFormat.parse(calStr); + cal = new GregorianCalendar(); + cal.setTime(date); + } catch (ParseException pe) { + // Silent + log.warn("Unable to parse date: " + calStr + " - msg: " + + pe.getMessage()); + } + } + return cal; + } + + /** Add a double click listener on tables that display a JCR node list */ + public static void addCanonicalDoubleClickListener(final TableViewer v) { + v.addDoubleClickListener(new IDoubleClickListener() { + + @Override + public void doubleClick(DoubleClickEvent event) { + CmsView cmsView = CmsUiUtils.getCmsView(); + Node node = (Node) ((IStructuredSelection) event.getSelection()) + .getFirstElement(); + try { + cmsView.navigateTo(node.getPath()); + } catch (RepositoryException e) { + throw new CmsException("Unable to get path for node " + + node + " before calling navigateTo(path)", e); + } + } + }); + } + + // MANAGE ERROR DECORATION + + public static ControlDecoration addDecoration(final Text text) { + final ControlDecoration dynDecoration = new ControlDecoration(text, + SWT.LEFT); + Image icon = getDecorationImage(FieldDecorationRegistry.DEC_ERROR); + dynDecoration.setImage(icon); + dynDecoration.setMarginWidth(3); + dynDecoration.hide(); + return dynDecoration; + } + + public static void refreshDecoration(Text text, ControlDecoration deco, + boolean isValid, boolean clean) { + if (isValid || clean) { + text.setBackground(null); + deco.hide(); + } else { + text.setBackground(new Color(text.getDisplay(), 250, 200, 150)); + deco.show(); + } + } + + public static Image getDecorationImage(String image) { + FieldDecorationRegistry registry = FieldDecorationRegistry.getDefault(); + return registry.getFieldDecoration(image).getImage(); + } + + public static void addCompulsoryDecoration(Label label) { + final ControlDecoration dynDecoration = new ControlDecoration(label, + SWT.RIGHT | SWT.TOP); + Image icon = getDecorationImage(FieldDecorationRegistry.DEC_REQUIRED); + dynDecoration.setImage(icon); + dynDecoration.setMarginWidth(3); + } + + // TODO the read only generation of read only links for various contact type + // should be factorised in the cms Utils. + /** + * Creates the read-only HTML snippet to display in a label with styling + * enabled in order to provide a click-able phone number + */ + public static String getPhoneLink(String value) { + return getPhoneLink(value, value); + } + + /** + * Creates the read-only HTML snippet to display in a label with styling + * enabled in order to provide a click-able phone number + * + * @param value + * @param label + * a potentially distinct label + * @return + */ + public static String getPhoneLink(String value, String label) { + StringBuilder builder = new StringBuilder(); + builder.append("").append(label) + .append(""); + return builder.toString(); + } + + /** + * Creates the read-only HTML snippet to display in a label with styling + * enabled in order to provide a click-able mail + */ + public static String getMailLink(String value) { + return getMailLink(value, value); + } + + /** + * Creates the read-only HTML snippet to display in a label with styling + * enabled in order to provide a click-able mail + * + * @param value + * @param label + * a potentially distinct label + * @return + */ + public static String getMailLink(String value, String label) { + StringBuilder builder = new StringBuilder(); + value = replaceAmpersand(value); + builder.append("").append(label).append(""); + return builder.toString(); + } + + /** + * Creates the read-only HTML snippet to display in a label with styling + * enabled in order to provide a click-able link + */ + public static String getUrlLink(String value) { + return getUrlLink(value, value); + } + + /** + * Creates the read-only HTML snippet to display in a label with styling + * enabled in order to provide a click-able link + */ + public static String getUrlLink(String value, String label) { + StringBuilder builder = new StringBuilder(); + value = replaceAmpersand(value); + label = replaceAmpersand(label); + if (!(value.startsWith("http://") || value.startsWith("https://"))) + value = "http://" + value; + builder.append("" + label + ""); + return builder.toString(); + } + + private static String AMPERSAND = "&"; + + /** + * Cleans a String by replacing any '&' by its HTML encoding '&#38;' to + * avoid SAXParseException while rendering HTML with RWT + */ + public static String replaceAmpersand(String value) { + value = value.replaceAll("&(?![#a-zA-Z0-9]+;)", AMPERSAND); + return value; + } + + // Prevents instantiation + private FormUtils() { + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/MarkupValidatorCopy.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/MarkupValidatorCopy.java new file mode 100644 index 000000000..3f588d1ea --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/MarkupValidatorCopy.java @@ -0,0 +1,169 @@ +package org.argeo.cms.ui.forms; + +import java.io.StringReader; +import java.text.MessageFormat; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; + +import org.eclipse.rap.rwt.SingletonUtil; +import org.eclipse.swt.widgets.Widget; +import org.xml.sax.Attributes; +import org.xml.sax.InputSource; +import org.xml.sax.helpers.DefaultHandler; + +/** + * Copy of RAP v2.3 since it is in an internal package. + */ +class MarkupValidatorCopy { + + // Used by Eclipse Scout project + public static final String MARKUP_VALIDATION_DISABLED = "org.eclipse.rap.rwt.markupValidationDisabled"; + + private static final String DTD = createDTD(); + private static final Map SUPPORTED_ELEMENTS = createSupportedElementsMap(); + private final SAXParser saxParser; + + public static MarkupValidatorCopy getInstance() { + return SingletonUtil.getSessionInstance(MarkupValidatorCopy.class); + } + + public MarkupValidatorCopy() { + saxParser = createSAXParser(); + } + + public void validate(String text) { + StringBuilder markup = new StringBuilder(); + markup.append(DTD); + markup.append(""); + markup.append(text); + markup.append(""); + InputSource inputSource = new InputSource(new StringReader(markup.toString())); + try { + saxParser.parse(inputSource, new MarkupHandler()); + } catch (RuntimeException exception) { + throw exception; + } catch (Exception exception) { + throw new IllegalArgumentException("Failed to parse markup text", exception); + } + } + + public static boolean isValidationDisabledFor(Widget widget) { + return Boolean.TRUE.equals(widget.getData(MARKUP_VALIDATION_DISABLED)); + } + + private static SAXParser createSAXParser() { + SAXParser result = null; + SAXParserFactory parserFactory = SAXParserFactory.newInstance(); + try { + result = parserFactory.newSAXParser(); + } catch (Exception exception) { + throw new RuntimeException("Failed to create SAX parser", exception); + } + return result; + } + + private static String createDTD() { + StringBuilder result = new StringBuilder(); + result.append(""); + result.append(""); + result.append(""); + result.append(""); + result.append(""); + result.append(""); + result.append(""); + result.append(""); + result.append(""); + result.append(""); + result.append("]>"); + return result.toString(); + } + + private static Map createSupportedElementsMap() { + Map result = new HashMap(); + result.put("html", new String[0]); + result.put("br", new String[0]); + result.put("b", new String[] { "style" }); + result.put("strong", new String[] { "style" }); + result.put("i", new String[] { "style" }); + result.put("em", new String[] { "style" }); + result.put("sub", new String[] { "style" }); + result.put("sup", new String[] { "style" }); + result.put("big", new String[] { "style" }); + result.put("small", new String[] { "style" }); + result.put("del", new String[] { "style" }); + result.put("ins", new String[] { "style" }); + result.put("code", new String[] { "style" }); + result.put("samp", new String[] { "style" }); + result.put("kbd", new String[] { "style" }); + result.put("var", new String[] { "style" }); + result.put("cite", new String[] { "style" }); + result.put("dfn", new String[] { "style" }); + result.put("q", new String[] { "style" }); + result.put("abbr", new String[] { "style", "title" }); + result.put("span", new String[] { "style" }); + result.put("img", new String[] { "style", "src", "width", "height", "title", "alt" }); + result.put("a", new String[] { "style", "href", "target", "title" }); + return result; + } + + private static class MarkupHandler extends DefaultHandler { + + @Override + public void startElement(String uri, String localName, String name, Attributes attributes) { + checkSupportedElements(name, attributes); + checkSupportedAttributes(name, attributes); + checkMandatoryAttributes(name, attributes); + } + + private static void checkSupportedElements(String elementName, Attributes attributes) { + if (!SUPPORTED_ELEMENTS.containsKey(elementName)) { + throw new IllegalArgumentException("Unsupported element in markup text: " + elementName); + } + } + + private static void checkSupportedAttributes(String elementName, Attributes attributes) { + if (attributes.getLength() > 0) { + List supportedAttributes = Arrays.asList(SUPPORTED_ELEMENTS.get(elementName)); + int index = 0; + String attributeName = attributes.getQName(index); + while (attributeName != null) { + if (!supportedAttributes.contains(attributeName)) { + String message = "Unsupported attribute \"{0}\" for element \"{1}\" in markup text"; + message = MessageFormat.format(message, new Object[] { attributeName, elementName }); + throw new IllegalArgumentException(message); + } + index++; + attributeName = attributes.getQName(index); + } + } + } + + private static void checkMandatoryAttributes(String elementName, Attributes attributes) { + checkIntAttribute(elementName, attributes, "img", "width"); + checkIntAttribute(elementName, attributes, "img", "height"); + } + + private static void checkIntAttribute(String elementName, Attributes attributes, String checkedElementName, + String checkedAttributeName) { + if (checkedElementName.equals(elementName)) { + String attribute = attributes.getValue(checkedAttributeName); + try { + Integer.parseInt(attribute); + } catch (NumberFormatException exception) { + String message = "Mandatory attribute \"{0}\" for element \"{1}\" is missing or not a valid integer"; + Object[] arguments = new Object[] { checkedAttributeName, checkedElementName }; + message = MessageFormat.format(message, arguments); + throw new IllegalArgumentException(message); + } + } + } + + } + +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/package-info.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/package-info.java new file mode 100644 index 000000000..5f954c1c4 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/package-info.java @@ -0,0 +1,2 @@ +/** Argeo CMS forms, based on SWT/JFace. */ +package org.argeo.cms.ui.forms; \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/CmsFsBrowser.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/CmsFsBrowser.java new file mode 100644 index 000000000..5a5ecdb8b --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/CmsFsBrowser.java @@ -0,0 +1,524 @@ +package org.argeo.cms.ui.fs; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.nio.file.DirectoryStream; +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.spi.FileSystemProvider; +import java.security.PrivilegedActionException; +import java.security.PrivilegedExceptionAction; +import java.util.ArrayList; +import java.util.List; + +import javax.jcr.Node; +import javax.jcr.Repository; +import javax.jcr.Session; + +import org.argeo.cms.CmsException; +import org.argeo.cms.auth.CurrentUser; +import org.argeo.cms.jcr.CmsJcrUtils; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.eclipse.ui.ColumnDefinition; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.argeo.eclipse.ui.fs.FileIconNameLabelProvider; +import org.argeo.eclipse.ui.fs.FsTableViewer; +import org.argeo.eclipse.ui.fs.FsUiConstants; +import org.argeo.eclipse.ui.fs.FsUiUtils; +import org.argeo.eclipse.ui.fs.NioFileLabelProvider; +import org.argeo.jcr.JcrUtils; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IDoubleClickListener; +import org.eclipse.jface.viewers.ISelectionChangedListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.SelectionChangedEvent; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.SashForm; +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.events.MouseAdapter; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.layout.RowData; +import org.eclipse.swt.layout.RowLayout; +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.eclipse.swt.widgets.Table; +import org.eclipse.swt.widgets.Text; + +/** + * Default CMS browser composite: a sashForm layout with bookmarks at the left + * hand side, a simple table in the middle and an overview at right hand side. + */ +public class CmsFsBrowser extends Composite { + // private final static Log log = LogFactory.getLog(CmsFsBrowser.class); + private static final long serialVersionUID = -40347919096946585L; + + private final FileSystemProvider nodeFileSystemProvider; + private final Node currentBaseContext; + + // UI Parts for the browser + private Composite leftPannelCmp; + private Composite filterCmp; + private Text filterTxt; + private FsTableViewer directoryDisplayViewer; + private Composite rightPannelCmp; + + private FsContextMenu contextMenu; + + // Local context (this composite is state full) + private Path initialPath; + private Path currDisplayedFolder; + private Path currSelected; + + // local variables (to be cleaned) + private int bookmarkColWith = 500; + + /* + * WARNING: unfinalised implementation of the mechanism to retrieve base + * paths + */ + + private final static String NODE_PREFIX = "node://"; + + private String getCurrentHomePath() { + Session session = null; + try { + Repository repo = currentBaseContext.getSession().getRepository(); + session = CurrentUser.tryAs(() -> repo.login()); + String homepath = CmsJcrUtils.getUserHome(session).getPath(); + return homepath; + } catch (Exception e) { + throw new CmsException("Cannot retrieve Current User Home Path", e); + } finally { + JcrUtils.logoutQuietly(session); + } + } + + protected Path[] getMyFilesPath() { + // return Paths.get(System.getProperty("user.dir")); + String currHomeUriStr = NODE_PREFIX + getCurrentHomePath(); + try { + URI uri = new URI(currHomeUriStr); + FileSystem fileSystem = nodeFileSystemProvider.getFileSystem(uri); + if (fileSystem == null) { + PrivilegedExceptionAction pea = new PrivilegedExceptionAction() { + @Override + public FileSystem run() throws Exception { + return nodeFileSystemProvider.newFileSystem(uri, null); + } + + }; + fileSystem = CurrentUser.tryAs(pea); + } + Path[] paths = { fileSystem.getPath(getCurrentHomePath()), fileSystem.getPath("/") }; + return paths; + } catch (URISyntaxException | PrivilegedActionException e) { + throw new RuntimeException("unable to initialise home file system for " + currHomeUriStr, e); + } + } + + private Path[] getMyGroupsFilesPath() { + // TODO + Path[] paths = { Paths.get(System.getProperty("user.dir")), Paths.get("/tmp") }; + return paths; + } + + private Path[] getMyBookmarks() { + // TODO + Path[] paths = { Paths.get(System.getProperty("user.dir")), Paths.get("/tmp"), Paths.get("/opt") }; + return paths; + } + + /* End of warning */ + + public CmsFsBrowser(Composite parent, int style, Node context, FileSystemProvider fileSystemProvider) { + super(parent, style); + this.nodeFileSystemProvider = fileSystemProvider; + this.currentBaseContext = context; + + this.setLayout(EclipseUiUtils.noSpaceGridLayout()); + + SashForm form = new SashForm(this, SWT.HORIZONTAL); + + leftPannelCmp = new Composite(form, SWT.NO_FOCUS); + // Bookmarks are still static + populateBookmarks(leftPannelCmp); + + Composite centerCmp = new Composite(form, SWT.BORDER | SWT.NO_FOCUS); + createDisplay(centerCmp); + + rightPannelCmp = new Composite(form, SWT.NO_FOCUS); + + form.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + form.setWeights(new int[] { 15, 40, 20 }); + } + + void refresh() { + modifyFilter(false); + // also refresh bookmarks and groups + } + + private void createDisplay(final Composite parent) { + parent.setLayout(EclipseUiUtils.noSpaceGridLayout()); + + // top filter + filterCmp = new Composite(parent, SWT.NO_FOCUS); + filterCmp.setLayoutData(EclipseUiUtils.fillWidth()); + addFilterPanel(filterCmp); + + // Main display + directoryDisplayViewer = new FsTableViewer(parent, SWT.MULTI); + List colDefs = new ArrayList<>(); + colDefs.add(new ColumnDefinition(new FileIconNameLabelProvider(), "Name", 250)); + colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_SIZE), "Size", 100)); + colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_TYPE), "Type", 150)); + colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_LAST_MODIFIED), + "Last modified", 400)); + final Table table = directoryDisplayViewer.configureDefaultTable(colDefs); + table.setLayoutData(EclipseUiUtils.fillAll()); + + // table.addKeyListener(new KeyListener() { + // private static final long serialVersionUID = -8083424284436715709L; + // + // @Override + // public void keyReleased(KeyEvent e) { + // } + // + // @Override + // public void keyPressed(KeyEvent e) { + // if (log.isDebugEnabled()) + // log.debug("Key event received: " + e.keyCode); + // IStructuredSelection selection = (IStructuredSelection) + // directoryDisplayViewer.getSelection(); + // Path selected = null; + // if (!selection.isEmpty()) + // selected = ((Path) selection.getFirstElement()); + // if (e.keyCode == SWT.CR) { + // if (!Files.isDirectory(selected)) + // return; + // if (selected != null) { + // currDisplayedFolder = selected; + // directoryDisplayViewer.setInput(currDisplayedFolder, "*"); + // } + // } else if (e.keyCode == SWT.BS) { + // currDisplayedFolder = currDisplayedFolder.getParent(); + // directoryDisplayViewer.setInput(currDisplayedFolder, "*"); + // directoryDisplayViewer.getTable().setFocus(); + // } + // } + // }); + + directoryDisplayViewer.addSelectionChangedListener(new ISelectionChangedListener() { + + @Override + public void selectionChanged(SelectionChangedEvent event) { + IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection(); + Path selected = null; + if (selection.isEmpty()) + setSelected(null); + else + selected = ((Path) selection.getFirstElement()); + if (selected != null) { + // TODO manage multiple selection + setSelected(selected); + } + } + }); + + directoryDisplayViewer.addDoubleClickListener(new IDoubleClickListener() { + @Override + public void doubleClick(DoubleClickEvent event) { + IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection(); + Path selected = null; + if (!selection.isEmpty()) + selected = ((Path) selection.getFirstElement()); + if (selected != null) { + if (!Files.isDirectory(selected)) + return; + setInput(selected); + } + } + }); + + // The context menu + contextMenu = new FsContextMenu(this); + + table.addMouseListener(new MouseAdapter() { + private static final long serialVersionUID = 6737579410648595940L; + + @Override + public void mouseDown(MouseEvent e) { + if (e.button == 3) { + // contextMenu.setCurrFolderPath(currDisplayedFolder); + contextMenu.show(table, new Point(e.x, e.y), currDisplayedFolder); + } + } + }); + } + + private void addPathElementBtn(Path path) { + Button elemBtn = new Button(filterCmp, SWT.PUSH); + String nameStr; + if (path.toString().equals("/")) + nameStr = "[jcr:root]"; + else + nameStr = path.getFileName().toString(); + elemBtn.setText(nameStr + " >> "); + CmsSwtUtils.style(elemBtn, FsStyles.BREAD_CRUMB_BTN); + elemBtn.addSelectionListener(new SelectionAdapter() { + private static final long serialVersionUID = -4103695476023480651L; + + @Override + public void widgetSelected(SelectionEvent e) { + setInput(path); + } + }); + } + + public void setInput(Path path) { + if (path.equals(currDisplayedFolder)) + return; + currDisplayedFolder = path; + + Path diff = initialPath.relativize(currDisplayedFolder); + + for (Control child : filterCmp.getChildren()) + if (!child.equals(filterTxt)) + child.dispose(); + + addPathElementBtn(initialPath); + Path currTarget = initialPath; + if (!diff.toString().equals("")) + for (Path pathElem : diff) { + currTarget = currTarget.resolve(pathElem); + addPathElementBtn(currTarget); + } + + filterTxt.setText(""); + filterTxt.moveBelow(null); + setSelected(null); + filterCmp.getParent().layout(true, true); + } + + private void setSelected(Path path) { + currSelected = path; + setOverviewInput(path); + } + + public Viewer getViewer() { + return directoryDisplayViewer; + } + + private void populateBookmarks(Composite parent) { + CmsSwtUtils.clear(parent); + parent.setLayout(new GridLayout()); + ISelectionChangedListener selList = new BookmarksSelChangeListener(); + + FsTableViewer homeViewer = new FsTableViewer(parent, SWT.SINGLE | SWT.NO_SCROLL); + Table table = homeViewer.configureDefaultSingleColumnTable(bookmarkColWith); + GridData gd = EclipseUiUtils.fillWidth(); + gd.horizontalIndent = 10; + table.setLayoutData(gd); + homeViewer.addSelectionChangedListener(selList); + homeViewer.setPathsInput(getMyFilesPath()); + + appendTitle(parent, "Shared files"); + FsTableViewer groupsViewer = new FsTableViewer(parent, SWT.SINGLE | SWT.NO_SCROLL); + table = groupsViewer.configureDefaultSingleColumnTable(bookmarkColWith); + gd = EclipseUiUtils.fillWidth(); + gd.horizontalIndent = 10; + table.setLayoutData(gd); + groupsViewer.addSelectionChangedListener(selList); + groupsViewer.setPathsInput(getMyGroupsFilesPath()); + + appendTitle(parent, "My bookmarks"); + FsTableViewer bookmarksViewer = new FsTableViewer(parent, SWT.SINGLE | SWT.NO_SCROLL); + table = bookmarksViewer.configureDefaultSingleColumnTable(bookmarkColWith); + gd = EclipseUiUtils.fillWidth(); + gd.horizontalIndent = 10; + table.setLayoutData(gd); + bookmarksViewer.addSelectionChangedListener(selList); + bookmarksViewer.setPathsInput(getMyBookmarks()); + } + + /** + * Recreates the content of the box that displays information about the + * current selected Path. + */ + private void setOverviewInput(Path path) { + try { + EclipseUiUtils.clear(rightPannelCmp); + rightPannelCmp.setLayout(new GridLayout()); + if (path != null) { + // if (isImg(context)) { + // EditableImage image = new Img(parent, RIGHT, context, + // imageWidth); + // image.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, + // true, false, + // 2, 1)); + // } + + Label contextL = new Label(rightPannelCmp, SWT.NONE); + contextL.setText(path.getFileName().toString()); + contextL.setFont(EclipseUiUtils.getBoldFont(rightPannelCmp)); + addProperty(rightPannelCmp, "Last modified", Files.getLastModifiedTime(path).toString()); + // addProperty(rightPannelCmp, "Owner", + // Files.getOwner(path).getName()); + if (Files.isDirectory(path)) { + addProperty(rightPannelCmp, "Type", "Folder"); + } else { + String mimeType = Files.probeContentType(path); + if (EclipseUiUtils.isEmpty(mimeType)) + mimeType = "Unknown"; + addProperty(rightPannelCmp, "Type", mimeType); + addProperty(rightPannelCmp, "Size", FsUiUtils.humanReadableByteCount(Files.size(path), false)); + } + } + rightPannelCmp.layout(true, true); + } catch (IOException e) { + throw new CmsException("Cannot display details for " + path.toString(), e); + } + } + + private void addFilterPanel(Composite parent) { + RowLayout rl = new RowLayout(SWT.HORIZONTAL); + rl.wrap = true; + parent.setLayout(rl); + // parent.setLayout(EclipseUiUtils.noSpaceGridLayout(new GridLayout(2, + // false))); + + filterTxt = new Text(parent, SWT.SEARCH | SWT.ICON_CANCEL); + filterTxt.setMessage("Search current folder"); + filterTxt.setLayoutData(new RowData(250, SWT.DEFAULT)); + filterTxt.addModifyListener(new ModifyListener() { + private static final long serialVersionUID = 1L; + + public void modifyText(ModifyEvent event) { + modifyFilter(false); + } + }); + filterTxt.addKeyListener(new KeyListener() { + private static final long serialVersionUID = 2533535233583035527L; + + @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; + // FilterEntitiesVirtualTable currTable = null; + // if (currEdited != null) { + // FilterEntitiesVirtualTable table = + // browserCols.get(currEdited); + // if (table != null && !table.isDisposed()) + // currTable = table; + // } + // + // if (e.keyCode == SWT.ARROW_DOWN) + // currTable.setFocus(); + // else if (e.keyCode == SWT.BS) { + // if (filterTxt.getText().equals("") + // && !(currEdited.getNameCount() == 1 || + // currEdited.equals(initialPath))) { + // Path oldEdited = currEdited; + // Path parentPath = currEdited.getParent(); + // setEdited(parentPath); + // if (browserCols.containsKey(parentPath)) + // browserCols.get(parentPath).setSelected(oldEdited); + // filterTxt.setFocus(); + // e.doit = false; + // } + // } else if (e.keyCode == SWT.TAB && !shiftPressed) { + // Path uniqueChild = getOnlyChild(currEdited, + // filterTxt.getText()); + // if (uniqueChild != null) { + // // Highlight the unique chosen child + // currTable.setSelected(uniqueChild); + // setEdited(uniqueChild); + // } + // filterTxt.setFocus(); + // e.doit = false; + // } + } + }); + } + + private Path getOnlyChild(Path parent, String filter) { + try (DirectoryStream stream = Files.newDirectoryStream(currDisplayedFolder, filter + "*")) { + Path uniqueChild = null; + boolean moreThanOne = false; + loop: for (Path entry : stream) { + if (uniqueChild == null) { + uniqueChild = entry; + } else { + moreThanOne = true; + break loop; + } + } + if (!moreThanOne) + return uniqueChild; + return null; + } catch (IOException ioe) { + throw new CmsException( + "Unable to determine unique child existence and get it under " + parent + " with filter " + filter, + ioe); + } + } + + private void modifyFilter(boolean fromOutside) { + if (!fromOutside) + if (currDisplayedFolder != null) { + String filter = filterTxt.getText() + "*"; + directoryDisplayViewer.setInput(currDisplayedFolder, filter); + } + } + + private class BookmarksSelChangeListener implements ISelectionChangedListener { + + @Override + public void selectionChanged(SelectionChangedEvent event) { + IStructuredSelection selection = (IStructuredSelection) event.getSelection(); + if (selection.isEmpty()) + return; + else { + Path newSelected = (Path) selection.getFirstElement(); + if (newSelected.equals(currDisplayedFolder) && newSelected.equals(initialPath)) + return; + initialPath = newSelected; + setInput(newSelected); + } + } + } + + // Simplify UI implementation + private void addProperty(Composite parent, String propName, String value) { + Label contextL = new Label(parent, SWT.NONE); + contextL.setText(propName + ": " + value); + } + + private Label appendTitle(Composite parent, String value) { + Label titleLbl = new Label(parent, SWT.NONE); + titleLbl.setText(value); + titleLbl.setFont(EclipseUiUtils.getBoldFont(parent)); + GridData gd = EclipseUiUtils.fillWidth(); + gd.horizontalIndent = 5; + gd.verticalIndent = 5; + titleLbl.setLayoutData(gd); + return titleLbl; + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FileDrop.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FileDrop.java new file mode 100644 index 000000000..e875b5a3d --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FileDrop.java @@ -0,0 +1,37 @@ +package org.argeo.cms.ui.fs; + +import java.io.IOException; +import java.io.InputStream; + +import org.argeo.api.cms.CmsLog; +import org.argeo.eclipse.ui.specific.FileDropAdapter; +import org.eclipse.swt.dnd.DND; +import org.eclipse.swt.dnd.DropTarget; +import org.eclipse.swt.dnd.DropTargetEvent; +import org.eclipse.swt.widgets.Control; + +/** Allows a control to receive file drops. */ +public class FileDrop { + private final static CmsLog log = CmsLog.getLog(FileDrop.class); + + public void createDropTarget(Control control) { + FileDropAdapter fileDropAdapter = new FileDropAdapter() { + @Override + protected void processUpload(InputStream in, String fileName, String contentType) throws IOException { + if (log.isDebugEnabled()) + log.debug("Process upload of " + fileName + " (" + contentType + ")"); + processFileUpload(in, fileName, contentType); + } + }; + DropTarget dropTarget = new DropTarget(control, DND.DROP_MOVE | DND.DROP_COPY); + fileDropAdapter.prepareDropTarget(control, dropTarget); + } + + public void handleFileDrop(Control control, DropTargetEvent event) { + } + + /** Executed in UI thread */ + protected void processFileUpload(InputStream in, String fileName, String contentType) throws IOException { + + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FsContextMenu.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FsContextMenu.java new file mode 100644 index 000000000..c548e2aa0 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FsContextMenu.java @@ -0,0 +1,383 @@ +package org.argeo.cms.ui.fs; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.lang.reflect.Method; +import java.net.URI; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.commons.io.IOUtils; +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.CmsException; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.eclipse.ui.EclipseUiUtils; +import org.argeo.eclipse.ui.dialogs.SingleValue; +import org.eclipse.jface.dialogs.MessageDialog; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.events.ShellEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Button; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.FileDialog; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Shell; + +/** Generic popup context menu to manage NIO Path in a Viewer. */ +public class FsContextMenu extends Shell { + private static final long serialVersionUID = -9120261153509855795L; + + private final static CmsLog log = CmsLog.getLog(FsContextMenu.class); + + // Default known actions + public final static String ACTION_ID_CREATE_FOLDER = "createFolder"; + public final static String ACTION_ID_BOOKMARK_FOLDER = "bookmarkFolder"; + public final static String ACTION_ID_SHARE_FOLDER = "shareFolder"; + public final static String ACTION_ID_DOWNLOAD_FOLDER = "downloadFolder"; + public final static String ACTION_ID_DELETE = "delete"; + public final static String ACTION_ID_UPLOAD_FILE = "uploadFiles"; + public final static String ACTION_ID_OPEN = "open"; + + // Local context + private final CmsFsBrowser browser; + // private final Viewer viewer; + private final static String KEY_ACTION_ID = "actionId"; + private final static String[] DEFAULT_ACTIONS = { ACTION_ID_CREATE_FOLDER, ACTION_ID_BOOKMARK_FOLDER, + ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, ACTION_ID_DELETE, ACTION_ID_UPLOAD_FILE, + ACTION_ID_OPEN }; + private Map actionButtons = new HashMap(); + + private Path currFolderPath; + + public FsContextMenu(CmsFsBrowser browser) { // Viewer viewer, Display + // display) { + super(browser.getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP); + this.browser = browser; + setLayout(EclipseUiUtils.noSpaceGridLayout()); + + Composite boxCmp = new Composite(this, SWT.NO_FOCUS | SWT.BORDER); + boxCmp.setLayout(EclipseUiUtils.noSpaceGridLayout()); + CmsSwtUtils.style(boxCmp, FsStyles.CONTEXT_MENU_BOX); + createContextMenu(boxCmp); + + addShellListener(new ActionsShellListener()); + } + + protected void createContextMenu(Composite boxCmp) { + ActionsSelListener asl = new ActionsSelListener(); + for (String actionId : DEFAULT_ACTIONS) { + Button btn = new Button(boxCmp, SWT.FLAT | SWT.PUSH | SWT.LEAD); + btn.setText(getLabel(actionId)); + btn.setLayoutData(EclipseUiUtils.fillWidth()); + CmsSwtUtils.markup(btn); + CmsSwtUtils.style(btn, actionId + FsStyles.BUTTON_SUFFIX); + btn.setData(KEY_ACTION_ID, actionId); + btn.addSelectionListener(asl); + actionButtons.put(actionId, btn); + } + } + + protected String getLabel(String actionId) { + switch (actionId) { + case ACTION_ID_CREATE_FOLDER: + return "Create Folder"; + case ACTION_ID_BOOKMARK_FOLDER: + return "Bookmark Folder"; + case ACTION_ID_SHARE_FOLDER: + return "Share Folder"; + case ACTION_ID_DOWNLOAD_FOLDER: + return "Download as zip archive"; + case ACTION_ID_DELETE: + return "Delete"; + case ACTION_ID_UPLOAD_FILE: + return "Upload Files"; + case ACTION_ID_OPEN: + return "Open"; + default: + throw new IllegalArgumentException("Unknown action ID " + actionId); + } + } + + protected void aboutToShow(Control source, Point location) { + IStructuredSelection selection = ((IStructuredSelection) browser.getViewer().getSelection()); + boolean emptySel = true; + boolean multiSel = false; + boolean isFolder = true; + if (selection != null && !selection.isEmpty()) { + emptySel = false; + multiSel = selection.size() > 1; + if (!multiSel && selection.getFirstElement() instanceof Path) { + isFolder = Files.isDirectory((Path) selection.getFirstElement()); + } + } + if (emptySel) { + setVisible(true, ACTION_ID_CREATE_FOLDER, ACTION_ID_UPLOAD_FILE); + setVisible(false, ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, ACTION_ID_DELETE, ACTION_ID_OPEN, + // to be implemented + ACTION_ID_BOOKMARK_FOLDER); + } else if (multiSel) { + setVisible(true, ACTION_ID_CREATE_FOLDER, ACTION_ID_UPLOAD_FILE, ACTION_ID_DELETE); + setVisible(false, ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, ACTION_ID_OPEN, + // to be implemented + ACTION_ID_BOOKMARK_FOLDER); + } else if (isFolder) { + setVisible(true, ACTION_ID_CREATE_FOLDER, ACTION_ID_UPLOAD_FILE, ACTION_ID_DELETE); + setVisible(false, ACTION_ID_OPEN, + // to be implemented + ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, ACTION_ID_BOOKMARK_FOLDER); + } else { + setVisible(true, ACTION_ID_CREATE_FOLDER, ACTION_ID_UPLOAD_FILE, ACTION_ID_OPEN, ACTION_ID_DELETE); + setVisible(false, ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, + // to be implemented + ACTION_ID_BOOKMARK_FOLDER); + } + } + + private void setVisible(boolean visible, String... buttonIds) { + for (String id : buttonIds) { + Button button = actionButtons.get(id); + button.setVisible(visible); + GridData gd = (GridData) button.getLayoutData(); + gd.heightHint = visible ? SWT.DEFAULT : 0; + } + } + + public void show(Control source, Point location, Path currFolderPath) { + if (isVisible()) + setVisible(false); + // TODO find a better way to retrieve the parent path (cannot be deduced + // from table content because it will fail on an empty folder) + this.currFolderPath = currFolderPath; + aboutToShow(source, location); + pack(); + layout(); + if (source instanceof Control) + setLocation(((Control) source).toDisplay(location.x, location.y)); + open(); + } + + class StyleButton extends Label { + private static final long serialVersionUID = 7731102609123946115L; + + public StyleButton(Composite parent, int swtStyle) { + super(parent, swtStyle); + } + + } + + // class ActionsMouseListener extends MouseAdapter { + // private static final long serialVersionUID = -1041871937815812149L; + // + // @Override + // public void mouseDown(MouseEvent e) { + // Object eventSource = e.getSource(); + // if (e.button == 1) { + // if (eventSource instanceof Button) { + // Button pressedBtn = (Button) eventSource; + // String actionId = (String) pressedBtn.getData(KEY_ACTION_ID); + // switch (actionId) { + // case ACTION_ID_CREATE_FOLDER: + // createFolder(); + // break; + // case ACTION_ID_DELETE: + // deleteItems(); + // break; + // default: + // throw new IllegalArgumentException("Unimplemented action " + actionId); + // // case ACTION_ID_SHARE_FOLDER: + // // return "Share Folder"; + // // case ACTION_ID_DOWNLOAD_FOLDER: + // // return "Download as zip archive"; + // // case ACTION_ID_UPLOAD_FILE: + // // return "Upload Files"; + // // case ACTION_ID_OPEN: + // // return "Open"; + // } + // } + // } + // viewer.getControl().setFocus(); + // // setVisible(false); + // } + // } + + class ActionsSelListener extends SelectionAdapter { + private static final long serialVersionUID = -1041871937815812149L; + + @Override + public void widgetSelected(SelectionEvent e) { + Object eventSource = e.getSource(); + if (eventSource instanceof Button) { + Button pressedBtn = (Button) eventSource; + String actionId = (String) pressedBtn.getData(KEY_ACTION_ID); + switch (actionId) { + case ACTION_ID_CREATE_FOLDER: + createFolder(); + break; + case ACTION_ID_DELETE: + deleteItems(); + break; + case ACTION_ID_OPEN: + openFile(); + break; + case ACTION_ID_UPLOAD_FILE: + uploadFiles(); + break; + default: + throw new IllegalArgumentException("Unimplemented action " + actionId); + // case ACTION_ID_SHARE_FOLDER: + // return "Share Folder"; + // case ACTION_ID_DOWNLOAD_FOLDER: + // return "Download as zip archive"; + // case ACTION_ID_OPEN: + // return "Open"; + } + } + browser.setFocus(); + // viewer.getControl().setFocus(); + // setVisible(false); + + } + } + + class ActionsShellListener extends org.eclipse.swt.events.ShellAdapter { + private static final long serialVersionUID = -5092341449523150827L; + + @Override + public void shellDeactivated(ShellEvent e) { + setVisible(false); + } + } + + private void openFile() { + log.warn("Implement single sourced, workbench independant \"Open File\" action"); + } + + private void deleteItems() { + IStructuredSelection selection = ((IStructuredSelection) browser.getViewer().getSelection()); + if (selection.isEmpty()) + return; + + StringBuilder builder = new StringBuilder(); + @SuppressWarnings("unchecked") + Iterator iterator = selection.iterator(); + List paths = new ArrayList<>(); + + while (iterator.hasNext()) { + Path path = (Path) iterator.next(); + builder.append(path.getFileName() + ", "); + paths.add(path); + } + String msg = "You are about to delete following elements: " + builder.substring(0, builder.length() - 2) + + ". Are you sure?"; + if (MessageDialog.openConfirm(this, "Confirm deletion", msg)) { + for (Path path : paths) { + try { + // Might have already been deleted if we are in a tree + Files.deleteIfExists(path); + } catch (IOException e) { + throw new CmsException("Cannot delete path " + path, e); + } + } + browser.refresh(); + } + } + + private void createFolder() { + String msg = "Please provide a name."; + String name = SingleValue.ask("Create folder", msg); + // TODO enhance check of name validity + if (EclipseUiUtils.notEmpty(name)) { + try { + Path child = currFolderPath.resolve(name); + if (Files.exists(child)) + throw new CmsException("An item with name " + name + " already exists at " + + currFolderPath.toString() + ", cannot create"); + else + Files.createDirectories(child); + browser.refresh(); + } catch (IOException e) { + throw new CmsException("Cannot create folder " + name + " at " + currFolderPath.toString(), e); + } + } + } + + private void uploadFiles() { + try { + FileDialog dialog = new FileDialog(browser.getShell(), SWT.MULTI); + dialog.setText("Choose one or more files to upload"); + + if (EclipseUiUtils.notEmpty(dialog.open())) { + String[] names = dialog.getFileNames(); + // Workaround small differences between RAP and RCP + // 1. returned names are absolute path on RAP and + // relative in RCP + // 2. in RCP we must use getFilterPath that does not + // exists on RAP + Method filterMethod = null; + Path parPath = null; + try { + filterMethod = dialog.getClass().getDeclaredMethod("getFilterPath"); + String filterPath = (String) filterMethod.invoke(dialog); + parPath = Paths.get(filterPath); + } catch (NoSuchMethodException nsme) { // RAP + } + if (names.length == 0) + return; + else { + loop: for (String name : names) { + Path tmpPath = Paths.get(name); + if (parPath != null) + tmpPath = parPath.resolve(tmpPath); + if (Files.exists(tmpPath)) { + URI uri = tmpPath.toUri(); + String uriStr = uri.toString(); + + if (Files.isDirectory(tmpPath)) { + MessageDialog.openError(browser.getShell(), "Unimplemented directory import", + "Upload of directories in the system is not yet implemented"); + continue loop; + } + Path targetPath = currFolderPath.resolve(tmpPath.getFileName().toString()); + InputStream in = null; + try { + in = new ByteArrayInputStream(Files.readAllBytes(tmpPath)); + Files.copy(in, targetPath); + Files.delete(tmpPath); + } finally { + IOUtils.closeQuietly(in); + } + if (log.isDebugEnabled()) + log.debug("copied uploaded file " + uriStr + " to " + targetPath.toString()); + } else { + String msg = "Cannot copy tmp file from " + tmpPath.toString(); + if (parPath != null) + msg += "\nPlease remember that file upload fails when choosing files from the \"Recently Used\" bookmarks on some OS"; + MessageDialog.openError(browser.getShell(), "Missing file", msg); + continue loop; + } + } + browser.refresh(); + } + } + } catch (Exception e) { + e.printStackTrace(); + MessageDialog.openError(getShell(), "Upload has failed", "Cannot import files to " + currFolderPath); + } + } + + public void setCurrFolderPath(Path currFolderPath) { + this.currFolderPath = currFolderPath; + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FsStyles.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FsStyles.java new file mode 100644 index 000000000..9ae319282 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FsStyles.java @@ -0,0 +1,8 @@ +package org.argeo.cms.ui.fs; + +/** FS Ui specific CSS styles */ +public interface FsStyles { + String BREAD_CRUMB_BTN = "breadCrumb_btn"; + String CONTEXT_MENU_BOX = "contextMenu_box"; + String BUTTON_SUFFIX = "_btn"; +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/package-info.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/package-info.java new file mode 100644 index 000000000..6a6c27286 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/package-info.java @@ -0,0 +1,2 @@ +/** SWT/JFace file system components. */ +package org.argeo.cms.ui.fs; \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/Activator.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/Activator.java new file mode 100644 index 000000000..e10da3aed --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/Activator.java @@ -0,0 +1,37 @@ +package org.argeo.cms.ui.internal; + +import org.argeo.api.cms.CmsState; +import org.osgi.framework.BundleActivator; +import org.osgi.framework.BundleContext; +import org.osgi.util.tracker.ServiceTracker; + +public class Activator implements BundleActivator { + + // avoid dependency to RWT OSGi + private final static String CONTEXT_NAME_PROP = "contextName"; + + private static ServiceTracker nodeState; + + // @Override + public void start(BundleContext bc) throws Exception { + // UI +// bc.registerService(ApplicationConfiguration.class, new MaintenanceUi(), +// LangUtils.dico(CONTEXT_NAME_PROP, "system")); +// bc.registerService(ApplicationConfiguration.class, new UserUi(), LangUtils.dico(CONTEXT_NAME_PROP, "user")); + + nodeState = new ServiceTracker<>(bc, CmsState.class, null); + nodeState.open(); + } + + @Override + public void stop(BundleContext context) throws Exception { + if (nodeState != null) { + nodeState.close(); + nodeState = null; + } + } + + public static CmsState getNodeState() { + return nodeState.getService(); + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrContentProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrContentProvider.java new file mode 100644 index 000000000..44885b1ca --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrContentProvider.java @@ -0,0 +1,81 @@ +package org.argeo.cms.ui.internal; + +import java.util.ArrayList; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.RepositoryException; + +import org.argeo.cms.CmsException; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.Viewer; + +@Deprecated +class JcrContentProvider implements ITreeContentProvider { + private static final long serialVersionUID = -1333678161322488674L; + + @Override + public void dispose() { + } + + @Override + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + if (newInput == null) + return; + if (!(newInput instanceof Node)) + throw new CmsException("Input " + newInput + " must be a node"); + } + + @Override + public Object[] getElements(Object inputElement) { + try { + Node node = (Node) inputElement; + ArrayList arr = new ArrayList(); + NodeIterator nit = node.getNodes(); + while (nit.hasNext()) { + arr.add(nit.nextNode()); + } + return arr.toArray(); + } catch (RepositoryException e) { + throw new CmsException("Cannot get elements", e); + } + } + + @Override + public Object[] getChildren(Object parentElement) { + try { + Node node = (Node) parentElement; + ArrayList arr = new ArrayList(); + NodeIterator nit = node.getNodes(); + while (nit.hasNext()) { + arr.add(nit.nextNode()); + } + return arr.toArray(); + } catch (RepositoryException e) { + throw new CmsException("Cannot get elements", e); + } + } + + @Override + public Object getParent(Object element) { + try { + Node node = (Node) element; + if (node.getName().equals("")) + return null; + else + return node.getParent(); + } catch (RepositoryException e) { + throw new CmsException("Cannot get elements", e); + } + } + + @Override + public boolean hasChildren(Object element) { + try { + Node node = (Node) element; + return node.hasNodes(); + } catch (RepositoryException e) { + throw new CmsException("Cannot get elements", e); + } + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrFileUploadReceiver.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrFileUploadReceiver.java new file mode 100644 index 000000000..c8582f0c1 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrFileUploadReceiver.java @@ -0,0 +1,74 @@ +package org.argeo.cms.ui.internal; + +import static javax.jcr.nodetype.NodeType.NT_FILE; + +import java.io.IOException; +import java.io.InputStream; + +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.RepositoryException; +import javax.jcr.nodetype.NodeType; + +import org.apache.commons.io.FilenameUtils; +import org.argeo.api.cms.CmsImageManager; +import org.argeo.cms.ui.widgets.Img; +import org.argeo.jcr.JcrException; +import org.argeo.jcr.JcrUtils; +import org.eclipse.rap.fileupload.FileDetails; +import org.eclipse.rap.fileupload.FileUploadReceiver; + +public class JcrFileUploadReceiver extends FileUploadReceiver { + private Img img; + private final Node parentNode; + private final String nodeName; + private final CmsImageManager imageManager; + + /** If nodeName is null, use the uploaded file name */ + public JcrFileUploadReceiver(Img img, Node parentNode, String nodeName, CmsImageManager imageManager) { + super(); + this.img = img; + this.parentNode = parentNode; + this.nodeName = nodeName; + this.imageManager = imageManager; + } + + @Override + public void receive(InputStream stream, FileDetails details) throws IOException { + try { + String fileName = nodeName != null ? nodeName : details.getFileName(); + String contentType = details.getContentType(); + if (isImage(details.getFileName(), contentType)) { + imageManager.uploadImage(img.getNode(),parentNode, fileName, stream, contentType); + return; + } + + Node fileNode; + if (parentNode.hasNode(fileName)) { + fileNode = parentNode.getNode(fileName); + if (!fileNode.isNodeType(NT_FILE)) + fileNode.remove(); + } + fileNode = JcrUtils.copyStreamAsFile(parentNode, fileName, stream); + + if (contentType != null) { + fileNode.addMixin(NodeType.MIX_MIMETYPE); + fileNode.setProperty(Property.JCR_MIMETYPE, contentType); + } + processNewFile(fileNode); + fileNode.getSession().save(); + } catch (RepositoryException e) { + throw new JcrException("Cannot receive " + details, e); + } + } + + protected Boolean isImage(String fileName, String contentType) { + String ext = FilenameUtils.getExtension(fileName); + return ext != null && (ext.equals("png") || ext.equalsIgnoreCase("jpg")); + } + + protected void processNewFile(Node node) { + + } + +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/SimpleEditableImage.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/SimpleEditableImage.java new file mode 100644 index 000000000..c5c1a01a2 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/SimpleEditableImage.java @@ -0,0 +1,72 @@ +package org.argeo.cms.ui.internal; + +import javax.jcr.RepositoryException; + +import org.argeo.api.cms.Cms2DSize; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.ui.util.CmsUiUtils; +import org.argeo.cms.ui.widgets.EditableImage; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Text; + +/** NOT working yet. */ +public class SimpleEditableImage extends EditableImage { + private static final long serialVersionUID = -5689145523114022890L; + + private String src; + private Cms2DSize imageSize; + + public SimpleEditableImage(Composite parent, int swtStyle) { + super(parent, swtStyle); + // load(getControl()); + getParent().layout(); + } + + public SimpleEditableImage(Composite parent, int swtStyle, String src, Cms2DSize imageSize) { + super(parent, swtStyle); + this.src = src; + this.imageSize = imageSize; + } + + @Override + protected Control createControl(Composite box, String style) { + if (isEditing()) { + return createText(box, style); + } else { + return createLabel(box, style); + } + } + + protected String createImgTag() throws RepositoryException { + String imgTag; + if (src != null) + imgTag = CmsUiUtils.img(src, imageSize); + else + imgTag = CmsUiUtils.noImg(imageSize != null ? imageSize : NO_IMAGE_SIZE); + return imgTag; + } + + protected Text createText(Composite box, String style) { + Text text = new Text(box, getStyle()); + CmsSwtUtils.style(text, style); + return text; + } + + public String getSrc() { + return src; + } + + public void setSrc(String src) { + this.src = src; + } + + public Cms2DSize getImageSize() { + return imageSize; + } + + public void setImageSize(Cms2DSize imageSize) { + this.imageSize = imageSize; + } + +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/DefaultRepositoryRegister.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/DefaultRepositoryRegister.java new file mode 100644 index 000000000..380634118 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/DefaultRepositoryRegister.java @@ -0,0 +1,75 @@ +package org.argeo.cms.ui.jcr; + +import java.util.Collections; +import java.util.Map; +import java.util.Observable; +import java.util.TreeMap; + +import javax.jcr.Repository; +import javax.jcr.RepositoryException; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.api.cms.CmsLog; + +public class DefaultRepositoryRegister extends Observable implements RepositoryRegister { + /** Key for a JCR repository alias */ + private final static String CN = CmsConstants.CN; + /** Key for a JCR repository URI */ + // public final static String JCR_REPOSITORY_URI = "argeo.jcr.repository.uri"; + private final static CmsLog log = CmsLog.getLog(DefaultRepositoryRegister.class); + + /** Read only map which will be directly exposed. */ + private Map repositories = Collections.unmodifiableMap(new TreeMap()); + + @SuppressWarnings("rawtypes") + public synchronized Repository getRepository(Map parameters) throws RepositoryException { + if (!parameters.containsKey(CN)) + throw new RepositoryException("Parameter " + CN + " has to be defined."); + String alias = parameters.get(CN).toString(); + if (!repositories.containsKey(alias)) + throw new RepositoryException("No repository registered with alias " + alias); + + return repositories.get(alias); + } + + /** Access to the read-only map */ + public synchronized Map getRepositories() { + return repositories; + } + + /** Registers a service, typically called when OSGi services are bound. */ + @SuppressWarnings("rawtypes") + public synchronized void register(Repository repository, Map properties) { + String alias; + if (properties == null || !properties.containsKey(CN)) { + log.warn("Cannot register a repository if no " + CN + " property is specified."); + return; + } + alias = properties.get(CN).toString(); + Map map = new TreeMap(repositories); + map.put(alias, repository); + repositories = Collections.unmodifiableMap(map); + setChanged(); + notifyObservers(alias); + } + + /** Unregisters a service, typically called when OSGi services are unbound. */ + @SuppressWarnings("rawtypes") + public synchronized void unregister(Repository repository, Map properties) { + // TODO: also check bean name? + if (properties == null || !properties.containsKey(CN)) { + log.warn("Cannot unregister a repository without property " + CN); + return; + } + + String alias = properties.get(CN).toString(); + Map map = new TreeMap(repositories); + if (map.remove(alias) == null) { + log.warn("No repository was registered with alias " + alias); + return; + } + repositories = Collections.unmodifiableMap(map); + setChanged(); + notifyObservers(alias); + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/FullVersioningTreeContentProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/FullVersioningTreeContentProvider.java new file mode 100644 index 000000000..0f7ee7735 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/FullVersioningTreeContentProvider.java @@ -0,0 +1,98 @@ +package org.argeo.cms.ui.jcr; + +import java.util.ArrayList; +import java.util.List; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.nodetype.NodeType; +import javax.jcr.version.Version; +import javax.jcr.version.VersionHistory; +import javax.jcr.version.VersionIterator; +import javax.jcr.version.VersionManager; + +import org.argeo.eclipse.ui.EclipseUiException; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.Viewer; + +/** + * Display some version information of a JCR full versionable node in a tree + * like structure + */ +public class FullVersioningTreeContentProvider implements ITreeContentProvider { + private static final long serialVersionUID = 8691772509491211112L; + + /** + * Sends back the first level of the Tree. input element must be a single + * node object + */ + public Object[] getElements(Object inputElement) { + try { + Node rootNode = (Node) inputElement; + String curPath = rootNode.getPath(); + VersionManager vm = rootNode.getSession().getWorkspace() + .getVersionManager(); + + VersionHistory vh = vm.getVersionHistory(curPath); + List result = new ArrayList(); + VersionIterator vi = vh.getAllLinearVersions(); + + while (vi.hasNext()) { + result.add(vi.nextVersion()); + } + return result.toArray(); + } catch (RepositoryException re) { + throw new EclipseUiException( + "Unexpected error while getting version elements", re); + } + } + + public Object[] getChildren(Object parentElement) { + try { + if (parentElement instanceof Version) { + List tmp = new ArrayList(); + tmp.add(((Version) parentElement).getFrozenNode()); + return tmp.toArray(); + } + } catch (RepositoryException re) { + throw new EclipseUiException("Unexpected error while getting child " + + "node for version element", re); + } + return null; + } + + public Object getParent(Object element) { + try { + // this will not work in a simpleVersionning environment, parent is + // not a node. + if (element instanceof Node + && ((Node) element).isNodeType(NodeType.NT_FROZEN_NODE)) { + Node node = (Node) element; + return node.getParent(); + } else + return null; + } catch (RepositoryException e) { + return null; + } + } + + public boolean hasChildren(Object element) { + try { + if (element instanceof Version) + return true; + else if (element instanceof Node) + return ((Node) element).hasNodes(); + else + return false; + } catch (RepositoryException e) { + throw new EclipseUiException("Cannot check children of " + element, e); + } + } + + public void dispose() { + } + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrBrowserUtils.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrBrowserUtils.java new file mode 100644 index 000000000..e4c5873a0 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrBrowserUtils.java @@ -0,0 +1,68 @@ +package org.argeo.cms.ui.jcr; + +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; + +import org.argeo.cms.ui.jcr.model.RepositoriesElem; +import org.argeo.cms.ui.jcr.model.RepositoryElem; +import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem; +import org.argeo.cms.ui.jcr.model.WorkspaceElem; +import org.argeo.eclipse.ui.EclipseUiException; +import org.argeo.eclipse.ui.TreeParent; + +/** Useful methods to manage the JCR Browser */ +public class JcrBrowserUtils { + + public static String getPropertyTypeAsString(Property prop) { + try { + return PropertyType.nameFromValue(prop.getType()); + } catch (RepositoryException e) { + throw new EclipseUiException("Cannot check type for " + prop, e); + } + } + + /** Insure that the UI component is not stale, refresh if needed */ + public static void forceRefreshIfNeeded(TreeParent element) { + Node curNode = null; + + boolean doRefresh = false; + + try { + if (element instanceof SingleJcrNodeElem) { + curNode = ((SingleJcrNodeElem) element).getNode(); + } else if (element instanceof WorkspaceElem) { + curNode = ((WorkspaceElem) element).getRootNode(); + } + + if (curNode != null && element.getChildren().length != curNode.getNodes().getSize()) + doRefresh = true; + else if (element instanceof RepositoryElem) { + RepositoryElem rn = (RepositoryElem) element; + if (rn.isConnected()) { + String[] wkpNames = rn.getAccessibleWorkspaceNames(); + if (element.getChildren().length != wkpNames.length) + doRefresh = true; + } + } else if (element instanceof RepositoriesElem) { + doRefresh = true; + // Always force refresh for RepositoriesElem : the condition + // below does not take remote repository into account and it is + // not trivial to do so. + + // RepositoriesElem rn = (RepositoriesElem) element; + // if (element.getChildren().length != + // rn.getRepositoryRegister() + // .getRepositories().size()) + // doRefresh = true; + } + if (doRefresh) { + element.clearChildren(); + element.getChildren(); + } + } catch (RepositoryException re) { + throw new EclipseUiException("Unexpected error while synchronising the UI with the JCR repository", re); + } + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrDClickListener.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrDClickListener.java new file mode 100644 index 000000000..1707681b4 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrDClickListener.java @@ -0,0 +1,60 @@ +package org.argeo.cms.ui.jcr; + +import javax.jcr.Node; + +import org.argeo.cms.ui.jcr.model.RepositoryElem; +import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem; +import org.argeo.cms.ui.jcr.model.WorkspaceElem; +import org.eclipse.jface.viewers.DoubleClickEvent; +import org.eclipse.jface.viewers.IDoubleClickListener; +import org.eclipse.jface.viewers.IStructuredSelection; +import org.eclipse.jface.viewers.TreeViewer; + +/** Centralizes the management of double click on a NodeTreeViewer */ +public class JcrDClickListener implements IDoubleClickListener { + // private final static Log log = LogFactory + // .getLog(GenericNodeDoubleClickListener.class); + + private TreeViewer nodeViewer; + + // private JcrFileProvider jfp; + // private FileHandler fileHandler; + + public JcrDClickListener(TreeViewer nodeViewer) { + this.nodeViewer = nodeViewer; + // jfp = new JcrFileProvider(); + // Commented out. see https://www.argeo.org/bugzilla/show_bug.cgi?id=188 + // fileHandler = null; + // fileHandler = new FileHandler(jfp); + } + + public void doubleClick(DoubleClickEvent event) { + if (event.getSelection() == null || event.getSelection().isEmpty()) + return; + Object obj = ((IStructuredSelection) event.getSelection()).getFirstElement(); + if (obj instanceof RepositoryElem) { + RepositoryElem rpNode = (RepositoryElem) obj; + if (rpNode.isConnected()) { + rpNode.logout(); + } else { + rpNode.login(); + } + nodeViewer.refresh(obj); + } else if (obj instanceof WorkspaceElem) { + WorkspaceElem wn = (WorkspaceElem) obj; + if (wn.isConnected()) + wn.logout(); + else + wn.login(); + nodeViewer.refresh(obj); + } else if (obj instanceof SingleJcrNodeElem) { + SingleJcrNodeElem sjn = (SingleJcrNodeElem) obj; + Node node = sjn.getNode(); + openNode(node); + } + } + + protected void openNode(Node node) { + // TODO implement generic behaviour + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrImages.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrImages.java new file mode 100644 index 000000000..d1d1e31ef --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrImages.java @@ -0,0 +1,24 @@ +package org.argeo.cms.ui.jcr; + +import org.argeo.cms.ui.theme.CmsImages; +import org.eclipse.swt.graphics.Image; + +/** Shared icons. */ +public class JcrImages { + public final static Image NODE = CmsImages.createIcon("node.gif"); + public final static Image FOLDER = CmsImages.createIcon("folder.gif"); + public final static Image FILE = CmsImages.createIcon("file.gif"); + public final static Image BINARY = CmsImages.createIcon("binary.png"); + public final static Image HOME = CmsImages.createIcon("person-logged-in.png"); + public final static Image SORT = CmsImages.createIcon("sort.gif"); + public final static Image REMOVE = CmsImages.createIcon("remove.gif"); + + public final static Image REPOSITORIES = CmsImages.createIcon("repositories.gif"); + public final static Image REPOSITORY_DISCONNECTED = CmsImages.createIcon("repository_disconnected.gif"); + public final static Image REPOSITORY_CONNECTED = CmsImages.createIcon("repository_connected.gif"); + public final static Image REMOTE_DISCONNECTED = CmsImages.createIcon("remote_disconnected.gif"); + public final static Image REMOTE_CONNECTED = CmsImages.createIcon("remote_connected.gif"); + public final static Image WORKSPACE_DISCONNECTED = CmsImages.createIcon("workspace_disconnected.png"); + public final static Image WORKSPACE_CONNECTED = CmsImages.createIcon("workspace_connected.png"); + +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrTreeContentProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrTreeContentProvider.java new file mode 100644 index 000000000..cc8479f78 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrTreeContentProvider.java @@ -0,0 +1,82 @@ +package org.argeo.cms.ui.jcr; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.RepositoryException; + +import org.argeo.eclipse.ui.EclipseUiException; +import org.argeo.eclipse.ui.jcr.util.JcrItemsComparator; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.Viewer; + +/** + * Implementation of the {@code ITreeContentProvider} in order to display a + * single JCR node and its children in a tree like structure + */ +public class JcrTreeContentProvider implements ITreeContentProvider { + private static final long serialVersionUID = -2128326504754297297L; + // private Node rootNode; + private JcrItemsComparator itemComparator = new JcrItemsComparator(); + + /** + * Sends back the first level of the Tree. input element must be a single node + * object + */ + public Object[] getElements(Object inputElement) { + Node rootNode = (Node) inputElement; + return childrenNodes(rootNode); + } + + public Object[] getChildren(Object parentElement) { + return childrenNodes((Node) parentElement); + } + + public Object getParent(Object element) { + try { + Node node = (Node) element; + if (!node.getPath().equals("/")) + return node.getParent(); + else + return null; + } catch (RepositoryException e) { + return null; + } + } + + public boolean hasChildren(Object element) { + try { + return ((Node) element).hasNodes(); + } catch (RepositoryException e) { + throw new EclipseUiException("Cannot check children existence on " + element, e); + } + } + + protected Object[] childrenNodes(Node parentNode) { + try { + List children = new ArrayList(); + NodeIterator nit = parentNode.getNodes(); + while (nit.hasNext()) { + Node node = nit.nextNode(); +// if (node.getName().startsWith("rep:") || node.getName().startsWith("jcr:") +// || node.getName().startsWith("nt:")) +// continue nodes; + children.add(node); + } + Node[] arr = children.toArray(new Node[0]); + Arrays.sort(arr, itemComparator); + return arr; + } catch (RepositoryException e) { + throw new EclipseUiException("Cannot list children of " + parentNode, e); + } + } + + public void dispose() { + } + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/NodeContentProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/NodeContentProvider.java new file mode 100644 index 000000000..00449df26 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/NodeContentProvider.java @@ -0,0 +1,175 @@ +package org.argeo.cms.ui.jcr; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.RepositoryFactory; +import javax.jcr.Session; +import javax.jcr.nodetype.NodeType; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.cms.jcr.CmsJcrUtils; +import org.argeo.cms.security.Keyring; +import org.argeo.cms.ui.jcr.model.RepositoriesElem; +import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem; +import org.argeo.eclipse.ui.TreeParent; +import org.eclipse.jface.viewers.ITreeContentProvider; +import org.eclipse.jface.viewers.Viewer; + +/** + * Implementation of the {@code ITreeContentProvider} to display multiple + * repository environment in a tree like structure + */ +public class NodeContentProvider implements ITreeContentProvider { + private static final long serialVersionUID = -4083809398848374403L; + final private RepositoryRegister repositoryRegister; + final private RepositoryFactory repositoryFactory; + + // Current user session on the default workspace of the argeo Node + final private Session userSession; + final private Keyring keyring; + private boolean sortChildren; + + // Reference for cleaning + private SingleJcrNodeElem homeNode = null; + private RepositoriesElem repositoriesNode = null; + + // Utils + private TreeBrowserComparator itemComparator = new TreeBrowserComparator(); + + public NodeContentProvider(Session userSession, Keyring keyring, + RepositoryRegister repositoryRegister, + RepositoryFactory repositoryFactory, Boolean sortChildren) { + this.userSession = userSession; + this.keyring = keyring; + this.repositoryRegister = repositoryRegister; + this.repositoryFactory = repositoryFactory; + this.sortChildren = sortChildren; + } + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + if (newInput == null)// dispose + return; + + if (userSession != null) { + Node userHome = CmsJcrUtils.getUserHome(userSession); + if (userHome != null) { + // TODO : find a way to dynamically get alias for the node + if (homeNode != null) + homeNode.dispose(); + homeNode = new SingleJcrNodeElem(null, userHome, + userSession.getUserID(), CmsConstants.EGO_REPOSITORY); + } + } + if (repositoryRegister != null) { + if (repositoriesNode != null) + repositoriesNode.dispose(); + repositoriesNode = new RepositoriesElem("Repositories", + repositoryRegister, repositoryFactory, null, userSession, + keyring); + } + } + + /** + * Sends back the first level of the Tree. Independent from inputElement + * that can be null + */ + public Object[] getElements(Object inputElement) { + List objs = new ArrayList(); + if (homeNode != null) + objs.add(homeNode); + if (repositoriesNode != null) + objs.add(repositoriesNode); + return objs.toArray(); + } + + public Object[] getChildren(Object parentElement) { + if (parentElement instanceof TreeParent) { + if (sortChildren) { + Object[] tmpArr = ((TreeParent) parentElement).getChildren(); + if (tmpArr == null) + return new Object[0]; + TreeParent[] arr = new TreeParent[tmpArr.length]; + for (int i = 0; i < tmpArr.length; i++) + arr[i] = (TreeParent) tmpArr[i]; + Arrays.sort(arr, itemComparator); + return arr; + } else + return ((TreeParent) parentElement).getChildren(); + } else + return new Object[0]; + } + + /** + * Sets whether the content provider should order the children nodes or not. + * It is user duty to call a full refresh of the tree after changing this + * parameter. + */ + public void setSortChildren(boolean sortChildren) { + this.sortChildren = sortChildren; + } + + public Object getParent(Object element) { + if (element instanceof TreeParent) { + return ((TreeParent) element).getParent(); + } else + return null; + } + + public boolean hasChildren(Object element) { + if (element instanceof RepositoriesElem) { + RepositoryRegister rr = ((RepositoriesElem) element) + .getRepositoryRegister(); + return rr.getRepositories().size() > 0; + } else if (element instanceof TreeParent) { + TreeParent tp = (TreeParent) element; + return tp.hasChildren(); + } + return false; + } + + public void dispose() { + if (homeNode != null) + homeNode.dispose(); + if (repositoriesNode != null) { + // logs out open sessions + // see https://bugzilla.argeo.org/show_bug.cgi?id=23 + repositoriesNode.dispose(); + } + } + + /** + * Specific comparator for this view. See specification here: + * https://www.argeo.org/bugzilla/show_bug.cgi?id=139 + */ + private class TreeBrowserComparator implements Comparator { + + public int category(TreeParent element) { + if (element instanceof SingleJcrNodeElem) { + Node node = ((SingleJcrNodeElem) element).getNode(); + try { + if (node.isNodeType(NodeType.NT_FOLDER)) + return 5; + } catch (RepositoryException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + return 10; + } + + public int compare(TreeParent o1, TreeParent o2) { + int cat1 = category(o1); + int cat2 = category(o2); + + if (cat1 != cat2) { + return cat1 - cat2; + } + return o1.getName().compareTo(o2.getName()); + } + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/NodeLabelProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/NodeLabelProvider.java new file mode 100644 index 000000000..a5751c083 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/NodeLabelProvider.java @@ -0,0 +1,113 @@ +package org.argeo.cms.ui.jcr; + +import javax.jcr.NamespaceException; +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.RepositoryException; +import javax.jcr.nodetype.NodeType; + +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.ui.jcr.model.RemoteRepositoryElem; +import org.argeo.cms.ui.jcr.model.RepositoriesElem; +import org.argeo.cms.ui.jcr.model.RepositoryElem; +import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem; +import org.argeo.cms.ui.jcr.model.WorkspaceElem; +import org.argeo.eclipse.ui.EclipseUiException; +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.swt.graphics.Image; + +/** Provides reasonable defaults for know JCR types. */ +public class NodeLabelProvider extends ColumnLabelProvider { + private static final long serialVersionUID = -3662051696443321843L; + + private final static CmsLog log = CmsLog.getLog(NodeLabelProvider.class); + + public String getText(Object element) { + try { + if (element instanceof SingleJcrNodeElem) { + SingleJcrNodeElem sjn = (SingleJcrNodeElem) element; + return getText(sjn.getNode()); + } else if (element instanceof Node) { + return getText((Node) element); + } else + return super.getText(element); + } catch (RepositoryException e) { + throw new EclipseUiException("Unexpected JCR error while getting node name."); + } + } + + protected String getText(Node node) throws RepositoryException { + String label = node.getName(); + StringBuffer mixins = new StringBuffer(""); + for (NodeType type : node.getMixinNodeTypes()) + mixins.append(' ').append(type.getName()); + + return label + " [" + node.getPrimaryNodeType().getName() + mixins + "]"; + } + + @Override + public Image getImage(Object element) { + if (element instanceof RemoteRepositoryElem) { + if (((RemoteRepositoryElem) element).isConnected()) + return JcrImages.REMOTE_CONNECTED; + else + return JcrImages.REMOTE_DISCONNECTED; + } else if (element instanceof RepositoryElem) { + if (((RepositoryElem) element).isConnected()) + return JcrImages.REPOSITORY_CONNECTED; + else + return JcrImages.REPOSITORY_DISCONNECTED; + } else if (element instanceof WorkspaceElem) { + if (((WorkspaceElem) element).isConnected()) + return JcrImages.WORKSPACE_CONNECTED; + else + return JcrImages.WORKSPACE_DISCONNECTED; + } else if (element instanceof RepositoriesElem) { + return JcrImages.REPOSITORIES; + } else if (element instanceof SingleJcrNodeElem) { + Node nodeElem = ((SingleJcrNodeElem) element).getNode(); + return getImage(nodeElem); + + // if (element instanceof Node) { + // return getImage((Node) element); + // } else if (element instanceof WrappedNode) { + // return getImage(((WrappedNode) element).getNode()); + // } else if (element instanceof NodesWrapper) { + // return getImage(((NodesWrapper) element).getNode()); + // } + } + // try { + // return super.getImage(); + // } catch (RepositoryException e) { + // return null; + // } + return super.getImage(element); + } + + protected Image getImage(Node node) { + try { + if (node.getPrimaryNodeType().isNodeType(NodeType.NT_FILE)) + return JcrImages.FILE; + else if (node.getPrimaryNodeType().isNodeType(NodeType.NT_FOLDER)) + return JcrImages.FOLDER; + else if (node.getPrimaryNodeType().isNodeType(NodeType.NT_RESOURCE)) + return JcrImages.BINARY; + try { + // TODO check workspace type? + if (node.getDepth() == 1 && node.hasProperty(Property.JCR_ID)) + return JcrImages.HOME; + + // optimizes +// if (node.hasProperty(LdapAttrs.uid.property()) && node.isNodeType(NodeTypes.NODE_USER_HOME)) +// return JcrImages.HOME; + } catch (NamespaceException e) { + // node namespace is not registered in this repo + } + return JcrImages.NODE; + } catch (RepositoryException e) { + log.warn("Error while retrieving type for " + node + " in order to display corresponding image"); + e.printStackTrace(); + return null; + } + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/OsgiRepositoryRegister.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/OsgiRepositoryRegister.java new file mode 100644 index 000000000..444350aeb --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/OsgiRepositoryRegister.java @@ -0,0 +1,52 @@ +package org.argeo.cms.ui.jcr; + +import java.util.HashMap; +import java.util.Map; + +import javax.jcr.Repository; + +import org.osgi.framework.BundleContext; +import org.osgi.framework.FrameworkUtil; +import org.osgi.framework.ServiceReference; +import org.osgi.util.tracker.ServiceTracker; + +public class OsgiRepositoryRegister extends DefaultRepositoryRegister { + private final static BundleContext bc = FrameworkUtil.getBundle(OsgiRepositoryRegister.class).getBundleContext(); + private final ServiceTracker repositoryTracker; + + public OsgiRepositoryRegister() { + repositoryTracker = new ServiceTracker(bc, Repository.class, null) { + + @Override + public Repository addingService(ServiceReference reference) { + + Repository repository = super.addingService(reference); + Map props = new HashMap<>(); + for (String key : reference.getPropertyKeys()) { + props.put(key, reference.getProperty(key)); + } + register(repository, props); + return repository; + } + + @Override + public void removedService(ServiceReference reference, Repository service) { + Map props = new HashMap<>(); + for (String key : reference.getPropertyKeys()) { + props.put(key, reference.getProperty(key)); + } + unregister(service, props); + super.removedService(reference, service); + } + + }; + } + + public void init() { + repositoryTracker.open(); + } + + public void destroy() { + repositoryTracker.close(); + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/PropertiesContentProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/PropertiesContentProvider.java new file mode 100644 index 000000000..fd544bbd8 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/PropertiesContentProvider.java @@ -0,0 +1,42 @@ +package org.argeo.cms.ui.jcr; + +import java.util.Set; +import java.util.TreeSet; + +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.PropertyIterator; +import javax.jcr.RepositoryException; + +import org.argeo.eclipse.ui.EclipseUiException; +import org.argeo.eclipse.ui.jcr.util.JcrItemsComparator; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.Viewer; + +/** Simple content provider that displays all properties of a given Node */ +public class PropertiesContentProvider implements IStructuredContentProvider { + private static final long serialVersionUID = 5227554668841613078L; + private JcrItemsComparator itemComparator = new JcrItemsComparator(); + + public void dispose() { + } + + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + + public Object[] getElements(Object inputElement) { + try { + if (inputElement instanceof Node) { + Set props = new TreeSet(itemComparator); + PropertyIterator pit = ((Node) inputElement).getProperties(); + while (pit.hasNext()) + props.add(pit.nextProperty()); + return props.toArray(); + } + return new Object[] {}; + } catch (RepositoryException e) { + throw new EclipseUiException("Cannot get element for " + + inputElement, e); + } + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/PropertyLabelProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/PropertyLabelProvider.java new file mode 100644 index 000000000..37b90f7ee --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/PropertyLabelProvider.java @@ -0,0 +1,101 @@ +package org.argeo.cms.ui.jcr; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; + +import javax.jcr.Property; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; +import javax.jcr.Value; + +import org.argeo.cms.ui.CmsUiConstants; +import org.argeo.eclipse.ui.EclipseUiException; +import org.argeo.jcr.JcrUtils; +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.jface.viewers.ViewerCell; + +/** Default basic label provider for a given JCR Node's properties */ +public class PropertyLabelProvider extends ColumnLabelProvider { + private static final long serialVersionUID = -5405794508731390147L; + + // To be able to change column order easily + public static final int COLUMN_PROPERTY = 0; + public static final int COLUMN_VALUE = 1; + public static final int COLUMN_TYPE = 2; + public static final int COLUMN_ATTRIBUTES = 3; + + // Utils + protected DateFormat timeFormatter = new SimpleDateFormat(CmsUiConstants.DATE_TIME_FORMAT); + + public void update(ViewerCell cell) { + Object element = cell.getElement(); + cell.setText(getColumnText(element, cell.getColumnIndex())); + } + + public String getColumnText(Object element, int columnIndex) { + try { + if (element instanceof Property) { + Property prop = (Property) element; + if (prop.isMultiple()) { + switch (columnIndex) { + case COLUMN_PROPERTY: + return prop.getName(); + case COLUMN_VALUE: + // Corresponding values are listed on children + return ""; + case COLUMN_TYPE: + return JcrBrowserUtils.getPropertyTypeAsString(prop); + case COLUMN_ATTRIBUTES: + return JcrUtils.getPropertyDefinitionAsString(prop); + } + } else { + switch (columnIndex) { + case COLUMN_PROPERTY: + return prop.getName(); + case COLUMN_VALUE: + return formatValueAsString(prop.getValue()); + case COLUMN_TYPE: + return JcrBrowserUtils.getPropertyTypeAsString(prop); + case COLUMN_ATTRIBUTES: + return JcrUtils.getPropertyDefinitionAsString(prop); + } + } + } else if (element instanceof Value) { + Value val = (Value) element; + switch (columnIndex) { + case COLUMN_PROPERTY: + // Nothing to show + return ""; + case COLUMN_VALUE: + return formatValueAsString(val); + case COLUMN_TYPE: + // listed on the parent + return ""; + case COLUMN_ATTRIBUTES: + // Corresponding attributes are listed on the parent + return ""; + } + } + } catch (RepositoryException re) { + throw new EclipseUiException("Cannot retrieve prop value on " + element, re); + } + return null; + } + + private String formatValueAsString(Value value) { + // TODO enhance this method + try { + String strValue; + + if (value.getType() == PropertyType.BINARY) + strValue = ""; + else if (value.getType() == PropertyType.DATE) + strValue = timeFormatter.format(value.getDate().getTime()); + else + strValue = value.getString(); + return strValue; + } catch (RepositoryException e) { + throw new EclipseUiException("unexpected error while formatting value", e); + } + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/RepositoryRegister.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/RepositoryRegister.java new file mode 100644 index 000000000..802c75619 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/RepositoryRegister.java @@ -0,0 +1,16 @@ +package org.argeo.cms.ui.jcr; + +import java.util.Map; + +import javax.jcr.Repository; +import javax.jcr.RepositoryFactory; + +/** Allows to register repositories by name. */ +public interface RepositoryRegister extends RepositoryFactory { + /** + * The registered {@link Repository} as a read-only map. Note that this + * method should be called for each access in order to be sure to be up to + * date in case repositories have registered/unregistered + */ + public Map getRepositories(); +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/VersionLabelProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/VersionLabelProvider.java new file mode 100644 index 000000000..37dfe2b8f --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/VersionLabelProvider.java @@ -0,0 +1,33 @@ +package org.argeo.cms.ui.jcr; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.version.Version; + +import org.argeo.eclipse.ui.EclipseUiException; +import org.eclipse.jface.viewers.ColumnLabelProvider; + +/** + * Simple wrapping of the ColumnLabelProvider class to provide text display in + * order to build a tree for version. The getText() method does not assume that + * {@link Version} extends {@link Node} class to respect JCR 2.0 specification + * + */ +public class VersionLabelProvider extends ColumnLabelProvider { + private static final long serialVersionUID = 5270739851193688238L; + + public String getText(Object element) { + try { + if (element instanceof Version) { + Version version = (Version) element; + return version.getName(); + } else if (element instanceof Node) { + return ((Node) element).getName(); + } + } catch (RepositoryException re) { + throw new EclipseUiException( + "Unexpected error while getting element name", re); + } + return super.getText(element); + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/MaintainedRepositoryElem.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/MaintainedRepositoryElem.java new file mode 100644 index 000000000..61654b61a --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/MaintainedRepositoryElem.java @@ -0,0 +1,21 @@ +package org.argeo.cms.ui.jcr.model; + +import javax.jcr.Repository; + +import org.argeo.eclipse.ui.TreeParent; + +/** Wrap a MaintainedRepository */ +public class MaintainedRepositoryElem extends RepositoryElem { + + public MaintainedRepositoryElem(String alias, Repository repository, TreeParent parent) { + super(alias, repository, parent); + // if (!(repository instanceof MaintainedRepository)) { + // throw new ArgeoException("Repository " + alias + // + " is not a maintained repository"); + // } + } + + // protected MaintainedRepository getMaintainedRepository() { + // return (MaintainedRepository) getRepository(); + // } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RemoteRepositoryElem.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RemoteRepositoryElem.java new file mode 100644 index 000000000..428e7f1cd --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RemoteRepositoryElem.java @@ -0,0 +1,76 @@ +package org.argeo.cms.ui.jcr.model; + +import java.util.Arrays; + +import javax.jcr.Node; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.RepositoryFactory; +import javax.jcr.Session; +import javax.jcr.SimpleCredentials; + +import org.argeo.cms.ArgeoNames; +import org.argeo.cms.jcr.CmsJcrUtils; +import org.argeo.cms.security.Keyring; +import org.argeo.eclipse.ui.EclipseUiException; +import org.argeo.eclipse.ui.TreeParent; + +/** Root of a remote repository */ +public class RemoteRepositoryElem extends RepositoryElem { + private final Keyring keyring; + /** + * A session of the logged in user on the default workspace of the node + * repository. + */ + private final Session userSession; + private final String remoteNodePath; + + private final RepositoryFactory repositoryFactory; + private final String uri; + + public RemoteRepositoryElem(String alias, RepositoryFactory repositoryFactory, String uri, TreeParent parent, + Session userSession, Keyring keyring, String remoteNodePath) { + super(alias, null, parent); + this.repositoryFactory = repositoryFactory; + this.uri = uri; + this.keyring = keyring; + this.userSession = userSession; + this.remoteNodePath = remoteNodePath; + } + + @Override + protected Session repositoryLogin(String workspaceName) throws RepositoryException { + Node remoteRepository = userSession.getNode(remoteNodePath); + String userID = remoteRepository.getProperty(ArgeoNames.ARGEO_USER_ID).getString(); + if (userID.trim().equals("")) { + return getRepository().login(workspaceName); + } else { + String pwdPath = remoteRepository.getPath() + '/' + ArgeoNames.ARGEO_PASSWORD; + char[] password = keyring.getAsChars(pwdPath); + try { + SimpleCredentials credentials = new SimpleCredentials(userID, password); + return getRepository().login(credentials, workspaceName); + } finally { + Arrays.fill(password, 0, password.length, ' '); + } + } + } + + @Override + public Repository getRepository() { + if (repository == null) + repository = CmsJcrUtils.getRepositoryByUri(repositoryFactory, uri); + return super.getRepository(); + } + + public void remove() { + try { + Node remoteNode = userSession.getNode(remoteNodePath); + remoteNode.remove(); + remoteNode.getSession().save(); + } catch (RepositoryException e) { + throw new EclipseUiException("Cannot remove " + remoteNodePath, e); + } + } + +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RepositoriesElem.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RepositoriesElem.java new file mode 100644 index 000000000..858633202 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RepositoriesElem.java @@ -0,0 +1,112 @@ +package org.argeo.cms.ui.jcr.model; + +import java.util.Map; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.RepositoryFactory; +import javax.jcr.Session; + +import org.argeo.cms.ArgeoNames; +import org.argeo.cms.jcr.CmsJcrUtils; +import org.argeo.cms.security.Keyring; +import org.argeo.cms.ui.jcr.RepositoryRegister; +import org.argeo.eclipse.ui.EclipseUiException; +import org.argeo.eclipse.ui.TreeParent; +import org.argeo.eclipse.ui.dialogs.ErrorFeedback; + +/** + * UI Tree component that implements the Argeo abstraction of a + * {@link RepositoryFactory} that enable a user to "mount" various repositories + * in a single Tree like View. It is usually meant to be at the root of the UI + * Tree and thus {@link getParent()} method will return null. + * + * The {@link RepositoryFactory} is injected at instantiation time and must be + * use get or register new {@link Repository} objects upon which a reference is + * kept here. + */ + +public class RepositoriesElem extends TreeParent implements ArgeoNames { + private final RepositoryRegister repositoryRegister; + private final RepositoryFactory repositoryFactory; + + /** + * A session of the logged in user on the default workspace of the node + * repository. + */ + private final Session userSession; + private final Keyring keyring; + + public RepositoriesElem(String name, RepositoryRegister repositoryRegister, RepositoryFactory repositoryFactory, + TreeParent parent, Session userSession, Keyring keyring) { + super(name); + this.repositoryRegister = repositoryRegister; + this.repositoryFactory = repositoryFactory; + this.userSession = userSession; + this.keyring = keyring; + } + + /** + * Override normal behavior to initialize the various repositories only at + * request time + */ + @Override + public synchronized Object[] getChildren() { + if (isLoaded()) { + return super.getChildren(); + } else { + // initialize current object + Map refRepos = repositoryRegister.getRepositories(); + for (String name : refRepos.keySet()) { + Repository repository = refRepos.get(name); + // if (repository instanceof MaintainedRepository) + // super.addChild(new MaintainedRepositoryElem(name, + // repository, this)); + // else + super.addChild(new RepositoryElem(name, repository, this)); + } + + // remote + if (keyring != null) { + try { + addRemoteRepositories(keyring); + } catch (RepositoryException e) { + throw new EclipseUiException("Cannot browse remote repositories", e); + } + } + return super.getChildren(); + } + } + + protected void addRemoteRepositories(Keyring jcrKeyring) throws RepositoryException { + Node userHome = CmsJcrUtils.getUserHome(userSession); + if (userHome != null && userHome.hasNode(ARGEO_REMOTE)) { + NodeIterator it = userHome.getNode(ARGEO_REMOTE).getNodes(); + while (it.hasNext()) { + Node remoteNode = it.nextNode(); + String uri = remoteNode.getProperty(ARGEO_URI).getString(); + try { + RemoteRepositoryElem remoteRepositoryNode = new RemoteRepositoryElem(remoteNode.getName(), + repositoryFactory, uri, this, userSession, jcrKeyring, remoteNode.getPath()); + super.addChild(remoteRepositoryNode); + } catch (Exception e) { + ErrorFeedback.show("Cannot add remote repository " + remoteNode, e); + } + } + } + } + + public void registerNewRepository(String alias, Repository repository) { + // TODO: implement this + // Create a new RepositoryNode Object + // add it + // super.addChild(new RepositoriesNode(...)); + } + + /** Returns the {@link RepositoryRegister} wrapped by this object. */ + public RepositoryRegister getRepositoryRegister() { + return repositoryRegister; + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RepositoryElem.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RepositoryElem.java new file mode 100644 index 000000000..afff3ef9e --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RepositoryElem.java @@ -0,0 +1,98 @@ +package org.argeo.cms.ui.jcr.model; + +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.argeo.api.cms.CmsConstants; +import org.argeo.eclipse.ui.EclipseUiException; +import org.argeo.eclipse.ui.TreeParent; +import org.argeo.jcr.JcrUtils; + +/** + * UI Tree component that wraps a JCR {@link Repository}. It also keeps a + * reference to its parent Tree Ui component; typically the unique + * {@link RepositoriesElem} object of the current view to enable bi-directionnal + * browsing in the tree. + */ + +public class RepositoryElem extends TreeParent { + private String alias; + protected Repository repository; + private Session defaultSession = null; + + /** Create a new repository with distinct name and alias */ + public RepositoryElem(String alias, Repository repository, TreeParent parent) { + super(alias); + this.repository = repository; + setParent(parent); + this.alias = alias; + } + + public void login() { + try { + defaultSession = repositoryLogin(CmsConstants.SYS_WORKSPACE); + String[] wkpNames = defaultSession.getWorkspace().getAccessibleWorkspaceNames(); + for (String wkpName : wkpNames) { + if (wkpName.equals(defaultSession.getWorkspace().getName())) + addChild(new WorkspaceElem(this, wkpName, defaultSession)); + else + addChild(new WorkspaceElem(this, wkpName)); + } + } catch (RepositoryException e) { + throw new EclipseUiException("Cannot connect to repository " + alias, e); + } + } + + public synchronized void logout() { + for (Object child : getChildren()) { + if (child instanceof WorkspaceElem) + ((WorkspaceElem) child).logout(); + } + clearChildren(); + JcrUtils.logoutQuietly(defaultSession); + defaultSession = null; + } + + /** + * Actual call to the {@link Repository#login(javax.jcr.Credentials, String)} + * method. To be overridden. + */ + protected Session repositoryLogin(String workspaceName) throws RepositoryException { + return repository.login(workspaceName); + } + + public String[] getAccessibleWorkspaceNames() { + try { + return defaultSession.getWorkspace().getAccessibleWorkspaceNames(); + } catch (RepositoryException e) { + throw new EclipseUiException("Cannot retrieve workspace names", e); + } + } + + public void createWorkspace(String workspaceName) { + if (!isConnected()) + login(); + try { + defaultSession.getWorkspace().createWorkspace(workspaceName); + } catch (RepositoryException e) { + throw new EclipseUiException("Cannot create workspace", e); + } + } + + /** returns the {@link Repository} referenced by the current UI Node */ + public Repository getRepository() { + return repository; + } + + public String getAlias() { + return alias; + } + + public Boolean isConnected() { + if (defaultSession != null && defaultSession.isLive()) + return true; + else + return false; + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/SingleJcrNodeElem.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/SingleJcrNodeElem.java new file mode 100644 index 000000000..859deee97 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/SingleJcrNodeElem.java @@ -0,0 +1,84 @@ +package org.argeo.cms.ui.jcr.model; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.RepositoryException; +import javax.jcr.Workspace; + +import org.argeo.eclipse.ui.EclipseUiException; +import org.argeo.eclipse.ui.TreeParent; + +/** + * UI Tree component. Wraps a node of a JCR {@link Workspace}. It also keeps a + * reference to its parent node that can either be a {@link WorkspaceElem}, a + * {@link SingleJcrNodeElem} or null if the node is "mounted" as the root of the + * UI tree. + */ +public class SingleJcrNodeElem extends TreeParent { + + private final Node node; + private String alias = null; + + /** Creates a new UiNode in the UI Tree */ + public SingleJcrNodeElem(TreeParent parent, Node node, String name) { + super(name); + setParent(parent); + this.node = node; + } + + /** + * Creates a new UiNode in the UI Tree, keeping a reference to the alias of + * the corresponding repository in the current UI environment. It is useful + * to be able to mount nodes as roots of the UI tree. + */ + public SingleJcrNodeElem(TreeParent parent, Node node, String name, String alias) { + super(name); + setParent(parent); + this.node = node; + this.alias = alias; + } + + /** Returns the node wrapped by the current UI object */ + public Node getNode() { + return node; + } + + protected String getRepositoryAlias() { + return alias; + } + + /** + * Overrides normal behaviour to initialise children only when first + * requested + */ + @Override + public synchronized Object[] getChildren() { + if (isLoaded()) { + return super.getChildren(); + } else { + // initialize current object + try { + NodeIterator ni = node.getNodes(); + while (ni.hasNext()) { + Node curNode = ni.nextNode(); + addChild(new SingleJcrNodeElem(this, curNode, curNode.getName())); + } + return super.getChildren(); + } catch (RepositoryException re) { + throw new EclipseUiException("Cannot initialize SingleJcrNode children", re); + } + } + } + + @Override + public boolean hasChildren() { + try { + if (node.getSession().isLive()) + return node.hasNodes(); + else + return false; + } catch (RepositoryException re) { + throw new EclipseUiException("Cannot check children node existence", re); + } + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/WorkspaceElem.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/WorkspaceElem.java new file mode 100644 index 000000000..24fc5758d --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/WorkspaceElem.java @@ -0,0 +1,117 @@ +package org.argeo.cms.ui.jcr.model; + +import javax.jcr.AccessDeniedException; +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +// import javax.jcr.Workspace; +import javax.jcr.Workspace; + +import org.argeo.eclipse.ui.EclipseUiException; +import org.argeo.eclipse.ui.TreeParent; +import org.argeo.jcr.JcrUtils; + +/** + * UI Tree component. Wraps the root node of a JCR {@link Workspace}. It also + * keeps a reference to its parent {@link RepositoryElem}, to be able to + * retrieve alias of the current used repository + */ +public class WorkspaceElem extends TreeParent { + private Session session = null; + + public WorkspaceElem(RepositoryElem parent, String name) { + this(parent, name, null); + } + + public WorkspaceElem(RepositoryElem parent, String name, Session session) { + super(name); + this.session = session; + setParent(parent); + } + + public synchronized Session getSession() { + return session; + } + + public synchronized Node getRootNode() { + try { + if (session != null) + return session.getRootNode(); + else + return null; + } catch (RepositoryException e) { + throw new EclipseUiException("Cannot get root node of workspace " + getName(), e); + } + } + + public synchronized void login() { + try { + session = ((RepositoryElem) getParent()).repositoryLogin(getName()); + } catch (RepositoryException e) { + throw new EclipseUiException("Cannot connect to repository " + getName(), e); + } + } + + public Boolean isConnected() { + if (session != null && session.isLive()) + return true; + else + return false; + } + + @Override + public synchronized void dispose() { + logout(); + super.dispose(); + } + + /** Logouts the session, does not nothing if there is no live session. */ + public synchronized void logout() { + clearChildren(); + JcrUtils.logoutQuietly(session); + session = null; + } + + @Override + public synchronized boolean hasChildren() { + try { + if (isConnected()) + try { + return session.getRootNode().hasNodes(); + } catch (AccessDeniedException e) { + // current user may not have access to the root node + return false; + } + else + return false; + } catch (RepositoryException re) { + throw new EclipseUiException("Unexpected error while checking children node existence", re); + } + } + + /** Override normal behaviour to initialize display of the workspace */ + @Override + public synchronized Object[] getChildren() { + if (isLoaded()) { + return super.getChildren(); + } else { + // initialize current object + try { + Node rootNode; + if (session == null) + return null; + else + rootNode = session.getRootNode(); + NodeIterator ni = rootNode.getNodes(); + while (ni.hasNext()) { + Node node = ni.nextNode(); + addChild(new SingleJcrNodeElem(this, node, node.getName())); + } + return super.getChildren(); + } catch (RepositoryException e) { + throw new EclipseUiException("Cannot initialize WorkspaceNode UI object." + getName(), e); + } + } + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/package-info.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/package-info.java new file mode 100644 index 000000000..8f5474449 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/package-info.java @@ -0,0 +1,2 @@ +/** Model for SWT/JFace JCR components. */ +package org.argeo.cms.ui.jcr.model; \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/package-info.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/package-info.java new file mode 100644 index 000000000..26ae330b5 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/package-info.java @@ -0,0 +1,2 @@ +/** SWT/JFace JCR components. */ +package org.argeo.cms.ui.jcr; \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/package-info.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/package-info.java new file mode 100644 index 000000000..82fdee796 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/package-info.java @@ -0,0 +1,2 @@ +/** SWT/JFace components for Argeo CMS. */ +package org.argeo.cms.ui; \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsLink.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsLink.java new file mode 100644 index 000000000..3821e6045 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsLink.java @@ -0,0 +1,282 @@ +package org.argeo.cms.ui.util; + +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.api.cms.CmsStyle; +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.auth.CurrentUser; +import org.argeo.cms.jcr.CmsJcrUtils; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.ui.CmsUiProvider; +import org.argeo.jcr.JcrException; +import org.eclipse.rap.rwt.RWT; +import org.eclipse.rap.rwt.service.ResourceManager; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.osgi.framework.BundleContext; + +/** A link to an internal or external location. */ +public class CmsLink implements CmsUiProvider { + private final static CmsLog log = CmsLog.getLog(CmsLink.class); + private BundleContext bundleContext; + + private String label; + private String style; + private String target; + private String image; + private boolean openNew = false; + private MouseListener mouseListener; + + private int horizontalAlignment = SWT.CENTER; + private int verticalAlignment = SWT.CENTER; + + private String loggedInLabel = null; + private String loggedInTarget = null; + + // internal + // private Boolean isUrl = false; + private Integer imageWidth, imageHeight; + + public CmsLink() { + super(); + } + + public CmsLink(String label, String target) { + this(label, target, (String) null); + } + + public CmsLink(String label, String target, CmsStyle style) { + this(label, target, style != null ? style.style() : null); + } + + public CmsLink(String label, String target, String style) { + super(); + this.label = label; + this.target = target; + this.style = style; + init(); + } + + public void init() { + if (image != null) { + ImageData image = loadImage(); + if (imageHeight == null && imageWidth == null) { + imageWidth = image.width; + imageHeight = image.height; + } else if (imageHeight == null) { + imageHeight = (imageWidth * image.height) / image.width; + } else if (imageWidth == null) { + imageWidth = (imageHeight * image.width) / image.height; + } + } + } + + /** @return {@link Composite} with a single {@link Label} child. */ + @Override + public Control createUi(final Composite parent, Node context) { +// if (image != null && (imageWidth == null || imageHeight == null)) { +// throw new CmsException("Image is not properly configured." +// + " Make sure bundleContext property is set and init() method has been called."); +// } + + Composite comp = new Composite(parent, SWT.NONE); + comp.setLayout(CmsSwtUtils.noSpaceGridLayout()); + + Label link = new Label(comp, SWT.NONE); + CmsSwtUtils.markup(link); + GridData layoutData = new GridData(horizontalAlignment, verticalAlignment, false, false); + if (image != null) { + if (imageHeight != null) + layoutData.heightHint = imageHeight; + if (label == null) + if (imageWidth != null) + layoutData.widthHint = imageWidth; + } + + link.setLayoutData(layoutData); + CmsSwtUtils.style(comp, style != null ? style : getDefaultStyle()); + CmsSwtUtils.style(link, style != null ? style : getDefaultStyle()); + + // label + StringBuilder labelText = new StringBuilder(); + if (loggedInTarget != null && isLoggedIn()) { + labelText.append(""); + } else if (target != null) { + labelText.append(""); + } + if (image != null) { + registerImageIfNeeded(); + String imageLocation = RWT.getResourceManager().getLocation(image); + labelText.append(""); + + } + + if (loggedInLabel != null && isLoggedIn()) { + labelText.append(' ').append(loggedInLabel); + } else if (label != null) { + labelText.append(' ').append(label); + } + + if ((loggedInTarget != null && isLoggedIn()) || target != null) + labelText.append(""); + + link.setText(labelText.toString()); + + if (mouseListener != null) + link.addMouseListener(mouseListener); + + return comp; + } + + private void registerImageIfNeeded() { + ResourceManager resourceManager = RWT.getResourceManager(); + if (!resourceManager.isRegistered(image)) { + URL res = getImageUrl(); + try (InputStream inputStream = res.openStream()) { + resourceManager.register(image, inputStream); + if (log.isTraceEnabled()) + log.trace("Registered image " + image); + } catch (IOException e) { + throw new RuntimeException("Cannot load image " + image, e); + } + } + } + + private ImageData loadImage() { + URL url = getImageUrl(); + ImageData result = null; + try (InputStream inputStream = url.openStream()) { + result = new ImageData(inputStream); + if (log.isTraceEnabled()) + log.trace("Loaded image " + image); + } catch (IOException e) { + throw new RuntimeException("Cannot load image " + image, e); + } + return result; + } + + private URL getImageUrl() { + URL url; + try { + // pure URL + url = new URL(image); + } catch (MalformedURLException e1) { + url = bundleContext.getBundle().getResource(image); + } + + if (url == null) + throw new IllegalStateException("No image " + image + " available."); + + return url; + } + + public void setBundleContext(BundleContext bundleContext) { + this.bundleContext = bundleContext; + } + + public void setLabel(String label) { + this.label = label; + } + + public void setStyle(String style) { + this.style = style; + } + + /** @deprecated Use {@link #setStyle(String)} instead. */ + @Deprecated + public void setCustom(String custom) { + this.style = custom; + } + + public void setTarget(String target) { + this.target = target; + // try { + // new URL(target); + // isUrl = true; + // } catch (MalformedURLException e1) { + // isUrl = false; + // } + } + + public void setImage(String image) { + this.image = image; + } + + public void setLoggedInLabel(String loggedInLabel) { + this.loggedInLabel = loggedInLabel; + } + + public void setLoggedInTarget(String loggedInTarget) { + this.loggedInTarget = loggedInTarget; + } + + public void setMouseListener(MouseListener mouseListener) { + this.mouseListener = mouseListener; + } + + public void setvAlign(String vAlign) { + if ("bottom".equals(vAlign)) { + verticalAlignment = SWT.BOTTOM; + } else if ("top".equals(vAlign)) { + verticalAlignment = SWT.TOP; + } else if ("center".equals(vAlign)) { + verticalAlignment = SWT.CENTER; + } else { + throw new IllegalArgumentException( + "Unsupported vertical alignment " + vAlign + " (must be: top, bottom or center)"); + } + } + + protected boolean isLoggedIn() { + return !CurrentUser.isAnonymous(); + } + + public void setImageWidth(Integer imageWidth) { + this.imageWidth = imageWidth; + } + + public void setImageHeight(Integer imageHeight) { + this.imageHeight = imageHeight; + } + + public void setOpenNew(boolean openNew) { + this.openNew = openNew; + } + + protected String getDefaultStyle() { + return SimpleStyle.link.name(); + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsPane.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsPane.java new file mode 100644 index 000000000..fc0c82146 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsPane.java @@ -0,0 +1,49 @@ +package org.argeo.cms.ui.util; + +import org.argeo.cms.swt.CmsSwtUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.layout.GridLayout; +import org.eclipse.swt.layout.RowLayout; +import org.eclipse.swt.widgets.Composite; + +/** The main pane of a CMS display, with QA and support areas. */ +public class CmsPane { + + private Composite mainArea; + private Composite qaArea; + private Composite supportArea; + + public CmsPane(Composite parent, int style) { + parent.setLayout(CmsSwtUtils.noSpaceGridLayout()); + +// qaArea = new Composite(parent, SWT.NONE); +// qaArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); +// RowLayout qaLayout = new RowLayout(); +// qaLayout.spacing = 0; +// qaArea.setLayout(qaLayout); + + mainArea = new Composite(parent, SWT.NONE); + mainArea.setLayout(new GridLayout()); + mainArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + +// supportArea = new Composite(parent, SWT.NONE); +// supportArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); +// RowLayout supportLayout = new RowLayout(); +// supportLayout.spacing = 0; +// supportArea.setLayout(supportLayout); + } + + public Composite getMainArea() { + return mainArea; + } + + public Composite getQaArea() { + return qaArea; + } + + public Composite getSupportArea() { + return supportArea; + } + +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsUiUtils.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsUiUtils.java new file mode 100644 index 000000000..8b384799f --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsUiUtils.java @@ -0,0 +1,220 @@ +package org.argeo.cms.ui.util; + +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.util.StringTokenizer; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.servlet.http.HttpServletRequest; + +import org.argeo.api.cms.Cms2DSize; +import org.argeo.api.cms.CmsView; +import org.argeo.api.cms.CmsConstants; +import org.argeo.cms.jcr.CmsJcrUtils; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.ui.CmsUiConstants; +import org.argeo.jcr.JcrUtils; +import org.eclipse.rap.rwt.RWT; +import org.eclipse.rap.rwt.service.ResourceManager; +import org.eclipse.swt.graphics.Image; +import org.eclipse.swt.graphics.ImageData; +import org.eclipse.swt.layout.RowData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Table; + +/** Static utilities for the CMS framework. */ +public class CmsUiUtils { + // private final static Log log = LogFactory.getLog(CmsUiUtils.class); + + /* + * CMS VIEW + */ + + /** + * The CMS view related to this display, or null if none is available from this + * call. + * + * @deprecated Use {@link CmsSwtUtils#getCmsView(Composite)} instead. + */ + @Deprecated + public static CmsView getCmsView() { +// return UiContext.getData(CmsView.class.getName()); + return CmsSwtUtils.getCmsView(Display.getCurrent().getActiveShell()); + } + + public static StringBuilder getServerBaseUrl(HttpServletRequest request) { + try { + URL url = new URL(request.getRequestURL().toString()); + StringBuilder buf = new StringBuilder(); + buf.append(url.getProtocol()).append("://").append(url.getHost()); + if (url.getPort() != -1) + buf.append(':').append(url.getPort()); + return buf; + } catch (MalformedURLException e) { + throw new IllegalArgumentException("Cannot extract server base URL from " + request.getRequestURL(), e); + } + } + + // + public static String getDataUrl(Node node, HttpServletRequest request) { + try { + StringBuilder buf = getServerBaseUrl(request); + buf.append(getDataPath(node)); + return new URL(buf.toString()).toString(); + } catch (MalformedURLException e) { + throw new IllegalArgumentException("Cannot build data URL for " + node, e); + } + } + + /** A path in the node repository */ + public static String getDataPath(Node node) { + return getDataPath(CmsConstants.EGO_REPOSITORY, node); + } + + public static String getDataPath(String cn, Node node) { + return CmsJcrUtils.getDataPath(cn, node); + } + + /** Clean reserved URL characters for use in HTTP links. */ + public static String getDataPathForUrl(Node node) { + return cleanPathForUrl(getDataPath(node)); + } + + /** Clean reserved URL characters for use in HTTP links. */ + public static String cleanPathForUrl(String path) { + StringTokenizer st = new StringTokenizer(path, "/"); + StringBuilder sb = new StringBuilder(); + while (st.hasMoreElements()) { + sb.append('/'); + String encoded = URLEncoder.encode(st.nextToken(), StandardCharsets.UTF_8); + encoded = encoded.replace("+", "%20"); + sb.append(encoded); + + } + return sb.toString(); + } + + /** @deprecated Use rowData16px() instead. GridData should not be reused. */ + @Deprecated + public static RowData ROW_DATA_16px = new RowData(16, 16); + + + + /* + * FORM LAYOUT + */ + + + + @Deprecated + public static void setItemHeight(Table table, int height) { + table.setData(CmsUiConstants.ITEM_HEIGHT, height); + } + + // + // JCR + // + public static Node getOrAddEmptyFile(Node parent, Enum child) throws RepositoryException { + if (has(parent, child)) + return child(parent, child); + return JcrUtils.copyBytesAsFile(parent, child.name(), new byte[0]); + } + + public static Node child(Node parent, Enum en) throws RepositoryException { + return parent.getNode(en.name()); + } + + public static Boolean has(Node parent, Enum en) throws RepositoryException { + return parent.hasNode(en.name()); + } + + public static Node getOrAdd(Node parent, Enum en) throws RepositoryException { + return getOrAdd(parent, en, null); + } + + public static Node getOrAdd(Node parent, Enum en, String primaryType) throws RepositoryException { + if (has(parent, en)) + return child(parent, en); + else if (primaryType == null) + return parent.addNode(en.name()); + else + return parent.addNode(en.name(), primaryType); + } + + // IMAGES + + public static String img(Node fileNode, String width, String height) { + return img(null, fileNode, width, height); + } + + public static String img(String serverBase, Node fileNode, String width, String height) { +// String src = (serverBase != null ? serverBase : "") + NodeUtils.getDataPath(fileNode); + String src; + src = (serverBase != null ? serverBase : "") + getDataPathForUrl(fileNode); + return imgBuilder(src, width, height).append("/>").toString(); + } + + public static String img(String src, String width, String height) { + return imgBuilder(src, width, height).append("/>").toString(); + } + + public static String img(String src, Cms2DSize size) { + return img(src, Integer.toString(size.getWidth()), Integer.toString(size.getHeight())); + } + + public static StringBuilder imgBuilder(String src, String width, String height) { + return new StringBuilder(64).append(" { + private final static CmsLog log = CmsLog.getLog(DefaultImageManager.class); +// private MimetypesFileTypeMap fileTypeMap = new MimetypesFileTypeMap(); + + public Boolean load(Node node, Control control, Cms2DSize preferredSize) { + Cms2DSize imageSize = getImageSize(node); + Cms2DSize size; + String imgTag = null; + if (preferredSize == null || imageSize.getWidth() == 0 || imageSize.getHeight() == 0 + || (preferredSize.getWidth() == 0 && preferredSize.getHeight() == 0)) { + if (imageSize.getWidth() != 0 && imageSize.getHeight() != 0) { + // actual image size if completely known + size = imageSize; + } else { + // no image if not completely known + size = resizeTo(NO_IMAGE_SIZE, preferredSize != null ? preferredSize : imageSize); + imgTag = CmsUiUtils.noImg(size); + } + + } else if (preferredSize.getWidth() != 0 && preferredSize.getHeight() != 0) { + // given size if completely provided + size = preferredSize; + } else { + // at this stage : + // image is completely known + assert imageSize.getWidth() != 0 && imageSize.getHeight() != 0; + // one and only one of the dimension as been specified + assert preferredSize.getWidth() == 0 || preferredSize.getHeight() == 0; + size = resizeTo(imageSize, preferredSize); + } + + boolean loaded = false; + if (control == null) + return loaded; + + if (control instanceof Label) { + if (imgTag == null) { + // IMAGE RETRIEVED HERE + imgTag = getImageTag(node, size); + // + if (imgTag == null) + imgTag = CmsUiUtils.noImg(size); + else + loaded = true; + } + + Label lbl = (Label) control; + lbl.setText(imgTag); + // lbl.setSize(size); + } else if (control instanceof FileUpload) { + FileUpload lbl = (FileUpload) control; + lbl.setImage(CmsUiUtils.noImage(size)); + lbl.setSize(new Point(size.getWidth(), size.getHeight())); + return loaded; + } else + loaded = false; + + return loaded; + } + + private Cms2DSize resizeTo(Cms2DSize orig, Cms2DSize constraints) { + if (constraints.getWidth() != 0 && constraints.getHeight() != 0) { + return constraints; + } else if (constraints.getWidth() == 0 && constraints.getHeight() == 0) { + return orig; + } else if (constraints.getHeight() == 0) {// force width + return new Cms2DSize(constraints.getWidth(), + scale(orig.getHeight(), orig.getWidth(), constraints.getWidth())); + } else if (constraints.getWidth() == 0) {// force height + return new Cms2DSize(scale(orig.getWidth(), orig.getHeight(), constraints.getHeight()), + constraints.getHeight()); + } + throw new IllegalArgumentException("Cannot resize " + orig + " to " + constraints); + } + + private int scale(int origDimension, int otherDimension, int otherConstraint) { + return Math.round(origDimension * divide(otherConstraint, otherDimension)); + } + + private float divide(int a, int b) { + return ((float) a) / ((float) b); + } + + public Cms2DSize getImageSize(Node node) { + // TODO optimise + Image image = getSwtImage(node); + return new Cms2DSize(image.getBounds().width, image.getBounds().height); + } + + /** @return null if not available */ + @Override + public String getImageTag(Node node) { + return getImageTag(node, getImageSize(node)); + } + + private String getImageTag(Node node, Cms2DSize size) { + StringBuilder buf = getImageTagBuilder(node, size); + if (buf == null) + return null; + return buf.append("/>").toString(); + } + + /** @return null if not available */ + @Override + public StringBuilder getImageTagBuilder(Node node, Cms2DSize size) { + return getImageTagBuilder(node, Integer.toString(size.getWidth()), Integer.toString(size.getHeight())); + } + + /** @return null if not available */ + private StringBuilder getImageTagBuilder(Node node, String width, String height) { + String url = getImageUrl(node); + if (url == null) + return null; + return CmsUiUtils.imgBuilder(url, width, height); + } + + /** @return null if not available */ + @Override + public String getImageUrl(Node node) { + return CmsUiUtils.getDataPathForUrl(node); + } + + protected String getResourceName(Node node) { + try { + String workspace = node.getSession().getWorkspace().getName(); + if (node.hasNode(JCR_CONTENT)) + return workspace + '_' + node.getNode(JCR_CONTENT).getIdentifier(); + else + return workspace + '_' + node.getIdentifier(); + } catch (RepositoryException e) { + throw new JcrException(e); + } + } + + public Binary getImageBinary(Node node) { + try { + if (node.isNodeType(NT_FILE)) { + return node.getNode(JCR_CONTENT).getProperty(JCR_DATA).getBinary(); + } else { + return null; + } + } catch (RepositoryException e) { + throw new JcrException(e); + } + } + + public Image getSwtImage(Node node) { + InputStream inputStream = null; + Binary binary = getImageBinary(node); + if (binary == null) + return null; + try { + inputStream = binary.getStream(); + return new Image(Display.getCurrent(), inputStream); + } catch (RepositoryException e) { + throw new JcrException(e); + } finally { + IOUtils.closeQuietly(inputStream); + JcrUtils.closeQuietly(binary); + } + } + + @Override + public String uploadImage(Node context, Node parentNode, String fileName, InputStream in, String contentType) { + InputStream inputStream = null; + try { + String previousResourceName = null; + if (parentNode.hasNode(fileName)) { + Node node = parentNode.getNode(fileName); + previousResourceName = getResourceName(node); + if (node.hasNode(JCR_CONTENT)) { + node.getNode(JCR_CONTENT).remove(); + node.addNode(JCR_CONTENT, NT_RESOURCE); + } + } + + byte[] arr = IOUtils.toByteArray(in); + Node fileNode = JcrUtils.copyBytesAsFile(parentNode, fileName, arr); + inputStream = new ByteArrayInputStream(arr); + ImageData id = new ImageData(inputStream); + processNewImageFile(context, fileNode, id); + + String mime = contentType != null ? contentType : Files.probeContentType(Paths.get(fileName)); + if (mime != null) { + fileNode.getNode(JCR_CONTENT).setProperty(Property.JCR_MIMETYPE, mime); + } + fileNode.getSession().save(); + + // reset resource manager + ResourceManager resourceManager = RWT.getResourceManager(); + if (previousResourceName != null && resourceManager.isRegistered(previousResourceName)) { + resourceManager.unregister(previousResourceName); + if (log.isDebugEnabled()) + log.debug("Unregistered image " + previousResourceName); + } + return CmsUiUtils.getDataPath(fileNode); + } catch (IOException e) { + throw new RuntimeException("Cannot upload image " + fileName + " in " + parentNode, e); + } catch (RepositoryException e) { + throw new JcrException(e); + } finally { + IOUtils.closeQuietly(inputStream); + } + } + + /** Does nothing by default. */ + protected void processNewImageFile(Node context, Node fileNode, ImageData id) + throws RepositoryException, IOException { + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/MenuLink.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/MenuLink.java new file mode 100644 index 000000000..284d2bd0c --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/MenuLink.java @@ -0,0 +1,22 @@ +package org.argeo.cms.ui.util; + +import org.argeo.cms.swt.CmsStyles; + +/** + * Convenience class setting the custom style {@link CmsStyles#CMS_MENU_LINK} on + * a {@link CmsLink} when simple menus are used. + */ +public class MenuLink extends CmsLink { + public MenuLink() { + setCustom(CmsStyles.CMS_MENU_LINK); + } + + public MenuLink(String label, String target, String custom) { + super(label, target, custom); + } + + public MenuLink(String label, String target) { + super(label, target, CmsStyles.CMS_MENU_LINK); + } + +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleCmsHeader.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleCmsHeader.java new file mode 100644 index 000000000..e8bf66297 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleCmsHeader.java @@ -0,0 +1,97 @@ +package org.argeo.cms.ui.util; + +import java.util.ArrayList; +import java.util.List; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.cms.CmsException; +import org.argeo.cms.swt.CmsStyles; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.ui.CmsUiProvider; +import org.eclipse.rap.rwt.RWT; +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; + +/** A header in three parts */ +public class SimpleCmsHeader implements CmsUiProvider { + private List lead = new ArrayList(); + private List center = new ArrayList(); + private List end = new ArrayList(); + + private Boolean subPartsSameWidth = false; + + @Override + public Control createUi(Composite parent, Node context) throws RepositoryException { + Composite header = new Composite(parent, SWT.NONE); + header.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_HEADER); + header.setBackgroundMode(SWT.INHERIT_DEFAULT); + header.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(3, false))); + + configurePart(context, header, lead); + configurePart(context, header, center); + configurePart(context, header, end); + return header; + } + + protected void configurePart(Node context, Composite parent, List partProviders) + throws RepositoryException { + final int style; + final String custom; + if (lead == partProviders) { + style = SWT.LEAD; + custom = CmsStyles.CMS_HEADER_LEAD; + } else if (center == partProviders) { + style = SWT.CENTER; + custom = CmsStyles.CMS_HEADER_CENTER; + } else if (end == partProviders) { + style = SWT.END; + custom = CmsStyles.CMS_HEADER_END; + } else { + throw new CmsException("Unsupported part providers " + partProviders); + } + + Composite part = new Composite(parent, SWT.NONE); + part.setData(RWT.CUSTOM_VARIANT, custom); + GridData gridData = new GridData(style, SWT.FILL, true, true); + part.setLayoutData(gridData); + part.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(partProviders.size(), subPartsSameWidth))); + for (CmsUiProvider uiProvider : partProviders) { + Control subPart = uiProvider.createUi(part, context); + subPart.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); + } + } + + public void setLead(List lead) { + this.lead = lead; + } + + public void setCenter(List center) { + this.center = center; + } + + public void setEnd(List end) { + this.end = end; + } + + public void setSubPartsSameWidth(Boolean subPartsSameWidth) { + this.subPartsSameWidth = subPartsSameWidth; + } + + public List getLead() { + return lead; + } + + public List getCenter() { + return center; + } + + public List getEnd() { + return end; + } + +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleDynamicPages.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleDynamicPages.java new file mode 100644 index 000000000..8e0e7c179 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleDynamicPages.java @@ -0,0 +1,118 @@ +package org.argeo.cms.ui.util; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.Property; +import javax.jcr.PropertyIterator; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; +import javax.jcr.Value; + +import org.argeo.cms.CmsException; +import org.argeo.cms.ui.CmsUiProvider; +import org.argeo.jcr.JcrUtils; +import org.eclipse.rap.rwt.RWT; +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; + +public class SimpleDynamicPages implements CmsUiProvider { + + @Override + public Control createUi(Composite parent, Node context) + throws RepositoryException { + if (context == null) + throw new CmsException("Context cannot be null"); + parent.setLayout(new GridLayout(2, false)); + + // parent + if (!context.getPath().equals("/")) { + new CmsLink("..", context.getParent().getPath()).createUi(parent, + context); + new Label(parent, SWT.NONE).setText(context.getParent() + .getPrimaryNodeType().getName()); + } + + // context + Label contextL = new Label(parent, SWT.NONE); + contextL.setData(RWT.MARKUP_ENABLED, true); + contextL.setText("" + context.getName() + ""); + new Label(parent, SWT.NONE).setText(context.getPrimaryNodeType() + .getName()); + + // children + // Label childrenL = new Label(parent, SWT.NONE); + // childrenL.setData(RWT.MARKUP_ENABLED, true); + // childrenL.setText("Children:"); + // childrenL.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, + // false, 2, 1)); + + for (NodeIterator nIt = context.getNodes(); nIt.hasNext();) { + Node child = nIt.nextNode(); + new CmsLink(child.getName(), child.getPath()).createUi(parent, + context); + + new Label(parent, SWT.NONE).setText(child.getPrimaryNodeType() + .getName()); + } + + // properties + // Label propsL = new Label(parent, SWT.NONE); + // propsL.setData(RWT.MARKUP_ENABLED, true); + // propsL.setText("Properties:"); + // propsL.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false, + // 2, 1)); + for (PropertyIterator pIt = context.getProperties(); pIt.hasNext();) { + Property property = pIt.nextProperty(); + + Label label = new Label(parent, SWT.NONE); + label.setText(property.getName()); + label.setToolTipText(JcrUtils + .getPropertyDefinitionAsString(property)); + + new Label(parent, SWT.NONE).setText(getPropAsString(property)); + } + + return null; + } + + private String getPropAsString(Property property) + throws RepositoryException { + String result = ""; + DateFormat timeFormatter = new SimpleDateFormat(""); + if (property.isMultiple()) { + result = getMultiAsString(property, ", "); + } else { + Value value = property.getValue(); + if (value.getType() == PropertyType.BINARY) + result = ""; + else if (value.getType() == PropertyType.DATE) + result = timeFormatter.format(value.getDate().getTime()); + else + result = value.getString(); + } + return result; + } + + private String getMultiAsString(Property property, String separator) + throws RepositoryException { + if (separator == null) + separator = "; "; + Value[] values = property.getValues(); + StringBuilder builder = new StringBuilder(); + for (Value val : values) { + String currStr = val.getString(); + if (!"".equals(currStr.trim())) + builder.append(currStr).append(separator); + } + if (builder.lastIndexOf(separator) >= 0) + return builder.substring(0, builder.length() - separator.length()); + else + return builder.toString(); + } +} \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleImageManager.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleImageManager.java new file mode 100644 index 000000000..ac09b2a02 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleImageManager.java @@ -0,0 +1,5 @@ +package org.argeo.cms.ui.util; + +public class SimpleImageManager extends DefaultImageManager { + +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStaticPage.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStaticPage.java new file mode 100644 index 000000000..63e504b7a --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStaticPage.java @@ -0,0 +1,32 @@ +package org.argeo.cms.ui.util; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.cms.swt.CmsStyles; +import org.argeo.cms.ui.CmsUiProvider; +import org.eclipse.rap.rwt.RWT; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; + +public class SimpleStaticPage implements CmsUiProvider { + private String text; + + @Override + public Control createUi(Composite parent, Node context) + throws RepositoryException { + Label textC = new Label(parent, SWT.WRAP); + textC.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_STATIC_TEXT); + textC.setData(RWT.MARKUP_ENABLED, Boolean.TRUE); + textC.setText(text); + + return textC; + } + + public void setText(String text) { + this.text = text; + } + +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStyle.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStyle.java new file mode 100644 index 000000000..8ed06a292 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStyle.java @@ -0,0 +1,8 @@ +package org.argeo.cms.ui.util; + +import org.argeo.api.cms.CmsStyle; + +/** Simple styles used by the CMS UI utilities. */ +public enum SimpleStyle implements CmsStyle { + link; +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/StyleSheetResourceLoader.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/StyleSheetResourceLoader.java new file mode 100644 index 000000000..ad1523c1a --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/StyleSheetResourceLoader.java @@ -0,0 +1,71 @@ +package org.argeo.cms.ui.util; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.apache.commons.io.IOUtils; +import org.argeo.cms.CmsException; +import org.eclipse.rap.rwt.service.ResourceLoader; +import org.osgi.framework.Bundle; + +/** {@link ResourceLoader} caching stylesheets. */ +public class StyleSheetResourceLoader implements ResourceLoader { + private Bundle themeBundle; + private Map stylesheets = new LinkedHashMap(); + + public StyleSheetResourceLoader(Bundle themeBundle) { + this.themeBundle = themeBundle; + } + + @Override + public InputStream getResourceAsStream(String resourceName) throws IOException { + if (!stylesheets.containsKey(resourceName)) { + // TODO deal with other bundles + // Bundle bundle = bundleContext.getBundle(); + // String location = + // bundle.getLocation().substring("initial@reference:".length()); + // if (location.startsWith("file:")) { + // Path path = null; + // try { + // path = Paths.get(new URI(location)); + // } catch (URISyntaxException e) { + // e.printStackTrace(); + // } + // if (path != null) { + // Path resourcePath = path.resolve(resourceName); + // if (Files.exists(resourcePath)) + // return Files.newInputStream(resourcePath); + // } + // } + + URL res = themeBundle.getEntry(resourceName); + if (res == null) + throw new CmsException( + "Entry " + resourceName + " not found in bundle " + themeBundle.getSymbolicName()); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + IOUtils.copy(res.openStream(), out); + stylesheets.put(resourceName, new StyleSheet(out.toByteArray())); + } + return new ByteArrayInputStream(stylesheets.get(resourceName).getData()); + // return res.openStream(); + } + + private class StyleSheet { + private byte[] data; + + public StyleSheet(byte[] data) { + super(); + this.data = data; + } + + public byte[] getData() { + return data; + } + + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SystemNotifications.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SystemNotifications.java new file mode 100644 index 000000000..156a6082f --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SystemNotifications.java @@ -0,0 +1,129 @@ +package org.argeo.cms.ui.util; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.net.URLEncoder; +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.apache.commons.io.IOUtils; +import org.argeo.cms.CmsException; +import org.argeo.cms.swt.CmsStyles; +import org.argeo.cms.swt.CmsSwtUtils; +import org.eclipse.rap.rwt.RWT; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.events.ShellAdapter; +import org.eclipse.swt.events.ShellEvent; +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.Label; +import org.eclipse.swt.widgets.Shell; + +/** Shell displaying system notifications such as exceptions */ +public class SystemNotifications extends Shell implements CmsStyles, + MouseListener { + private static final long serialVersionUID = -8129377525216022683L; + + private Control source; + + public SystemNotifications(Control source) { + super(source.getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP); + setData(RWT.CUSTOM_VARIANT, CMS_USER_MENU); + + this.source = source; + + // TODO UI + // setLocation(source.toDisplay(source.getSize().x - getSize().x, + // source.getSize().y)); + setLayout(new GridLayout()); + addMouseListener(this); + + addShellListener(new ShellAdapter() { + private static final long serialVersionUID = 5178980294808435833L; + + @Override + public void shellDeactivated(ShellEvent e) { + close(); + dispose(); + } + }); + + } + + public void notifyException(Throwable exception) { + Composite pane = this; + + Label lbl = new Label(pane, SWT.NONE); + lbl.setText(exception.getLocalizedMessage() + + (exception instanceof CmsException ? "" : "(" + + exception.getClass().getName() + ")") + "\n"); + lbl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + lbl.addMouseListener(this); + if (exception.getCause() != null) + appendCause(pane, exception.getCause()); + + StringBuilder mailToUrl = new StringBuilder("mailto:?"); + try { + mailToUrl.append("subject=").append( + URLEncoder.encode( + "Exception " + + new SimpleDateFormat("yyyy-MM-dd hh:mm") + .format(new Date()), "UTF-8") + .replace("+", "%20")); + + StringWriter sw = new StringWriter(); + exception.printStackTrace(new PrintWriter(sw)); + IOUtils.closeQuietly(sw); + + // see + // http://stackoverflow.com/questions/4737841/urlencoder-not-able-to-translate-space-character + String encoded = URLEncoder.encode(sw.toString(), "UTF-8").replace( + "+", "%20"); + mailToUrl.append("&body=").append(encoded); + } catch (UnsupportedEncodingException e) { + mailToUrl.append("&body=").append("Could not encode: ") + .append(e.getMessage()); + } + Label mailTo = new Label(pane, SWT.NONE); + CmsSwtUtils.markup(mailTo); + mailTo.setText("Send details"); + mailTo.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false)); + + pack(); + layout(); + + setLocation(source.toDisplay(source.getSize().x - getSize().x, + source.getSize().y - getSize().y)); + open(); + } + + private void appendCause(Composite parent, Throwable e) { + Label lbl = new Label(parent, SWT.NONE); + lbl.setText(" caused by: " + e.getLocalizedMessage() + " (" + + e.getClass().getName() + ")" + "\n"); + lbl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); + lbl.addMouseListener(this); + if (e.getCause() != null) + appendCause(parent, e.getCause()); + } + + @Override + public void mouseDoubleClick(MouseEvent e) { + } + + @Override + public void mouseDown(MouseEvent e) { + close(); + dispose(); + } + + @Override + public void mouseUp(MouseEvent e) { + } + +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/UserMenu.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/UserMenu.java new file mode 100644 index 000000000..316cb51c0 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/UserMenu.java @@ -0,0 +1,55 @@ +package org.argeo.cms.ui.util; + +import javax.jcr.Node; + +import org.argeo.cms.CmsException; +import org.argeo.cms.swt.auth.CmsLoginShell; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ShellAdapter; +import org.eclipse.swt.events.ShellEvent; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Shell; + +/** The site-related user menu */ +public class UserMenu extends CmsLoginShell { + private final Control source; + private final Node context; + + public UserMenu(Control source, Node context) { + super(CmsUiUtils.getCmsView()); + this.context = context; + createUi(); + if (source == null) + throw new CmsException("Source control cannot be null."); + this.source = source; + open(); + } + + @Override + protected Shell createShell() { + return new Shell(Display.getCurrent(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP); + } + + @Override + public void open() { + Shell shell = getShell(); + shell.pack(); + shell.layout(); + shell.setLocation(source.toDisplay(source.getSize().x - shell.getSize().x, source.getSize().y)); + shell.addShellListener(new ShellAdapter() { + private static final long serialVersionUID = 5178980294808435833L; + + @Override + public void shellDeactivated(ShellEvent e) { + closeShell(); + } + }); + super.open(); + } + + protected Node getContext() { + return context; + } + +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/UserMenuLink.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/UserMenuLink.java new file mode 100644 index 000000000..317a7b55b --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/UserMenuLink.java @@ -0,0 +1,84 @@ +package org.argeo.cms.ui.util; + +import javax.jcr.Node; + +import org.argeo.cms.CmsMsg; +import org.argeo.cms.auth.CurrentUser; +import org.argeo.cms.swt.CmsStyles; +import org.argeo.cms.swt.auth.CmsLoginShell; +import org.eclipse.swt.events.DisposeEvent; +import org.eclipse.swt.events.DisposeListener; +import org.eclipse.swt.events.MouseEvent; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; + +/** Open the user menu when clicked */ +public class UserMenuLink extends MenuLink { + + public UserMenuLink() { + setCustom(CmsStyles.CMS_USER_MENU_LINK); + } + + @Override + public Control createUi(Composite parent, Node context) { + if (CurrentUser.isAnonymous()) + setLabel(CmsMsg.login.lead()); + else { + setLabel(CurrentUser.getDisplayName()); + } + Label link = (Label) ((Composite) super.createUi(parent, context)).getChildren()[0]; + link.addMouseListener(new UserMenuLinkController(context)); + return link.getParent(); + } + + protected CmsLoginShell createUserMenu(Control source, Node context) { + return new UserMenu(source.getParent(), context); + } + + private class UserMenuLinkController implements MouseListener, DisposeListener { + private static final long serialVersionUID = 3634864186295639792L; + + private CmsLoginShell userMenu = null; + private long lastDisposeTS = 0l; + + private final Node context; + + public UserMenuLinkController(Node context) { + this.context = context; + } + + // + // MOUSE LISTENER + // + @Override + public void mouseDown(MouseEvent e) { + if (e.button == 1) { + Control source = (Control) e.getSource(); + if (userMenu == null) { + long durationSinceLastDispose = System.currentTimeMillis() - lastDisposeTS; + // avoid to reopen the menu, if one has clicked gain + if (durationSinceLastDispose > 200) { + userMenu = createUserMenu(source, context); + userMenu.getShell().addDisposeListener(this); + } + } + } + } + + @Override + public void mouseDoubleClick(MouseEvent e) { + } + + @Override + public void mouseUp(MouseEvent e) { + } + + @Override + public void widgetDisposed(DisposeEvent event) { + userMenu = null; + lastDisposeTS = System.currentTimeMillis(); + } + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/VerticalMenu.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/VerticalMenu.java new file mode 100644 index 000000000..7f846c932 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/VerticalMenu.java @@ -0,0 +1,44 @@ +package org.argeo.cms.ui.util; + +import java.util.ArrayList; +import java.util.List; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.ui.CmsUiProvider; +import org.eclipse.swt.SWT; +import org.eclipse.swt.layout.GridData; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + +public class VerticalMenu implements CmsUiProvider { + private List items = new ArrayList(); + + @Override + public Control createUi(Composite parent, Node context) throws RepositoryException { + Composite part = new Composite(parent, SWT.NONE); + part.setLayoutData(new GridData(SWT.LEAD, SWT.TOP, false, false)); +// part.setData(RWT.CUSTOM_VARIANT, custom); + part.setLayout(CmsSwtUtils.noSpaceGridLayout()); + for (CmsUiProvider uiProvider : items) { + Control subPart = uiProvider.createUi(part, context); + subPart.setLayoutData(new GridData(SWT.LEAD, SWT.TOP, false, false)); + } + return part; + } + + public void add(CmsUiProvider uiProvider) { + items.add(uiProvider); + } + + public List getItems() { + return items; + } + + public void setItems(List items) { + this.items = items; + } + +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/package-info.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/package-info.java new file mode 100644 index 000000000..566df883c --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/util/package-info.java @@ -0,0 +1,2 @@ +/** Argeo CMS UI utilities. */ +package org.argeo.cms.ui.util; \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/AbstractPageViewer.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/AbstractPageViewer.java new file mode 100644 index 000000000..ef24ee0d5 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/AbstractPageViewer.java @@ -0,0 +1,350 @@ +package org.argeo.cms.ui.viewers; + +import java.security.AccessControlContext; +import java.security.AccessController; +import java.security.PrivilegedAction; +import java.util.Observable; +import java.util.Observer; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.security.auth.Subject; + +import org.argeo.api.cms.CmsEditable; +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.ui.widgets.ScrolledPage; +import org.argeo.jcr.JcrException; +import org.eclipse.jface.viewers.ContentViewer; +import org.eclipse.jface.viewers.ISelection; +import org.eclipse.jface.viewers.StructuredSelection; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusEvent; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.MouseAdapter; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Widget; +import org.xml.sax.SAXParseException; + +/** Base class for viewers related to a page */ +public abstract class AbstractPageViewer extends ContentViewer implements Observer { + private static final long serialVersionUID = 5438688173410341485L; + + private final static CmsLog log = CmsLog.getLog(AbstractPageViewer.class); + + private final boolean readOnly; + /** The basis for the layouts, typically a ScrolledPage. */ + private final Composite page; + private final CmsEditable cmsEditable; + + private MouseListener mouseListener; + private FocusListener focusListener; + + private EditablePart edited; + private ISelection selection = StructuredSelection.EMPTY; + + private AccessControlContext accessControlContext; + + protected AbstractPageViewer(Section parent, int style, CmsEditable cmsEditable) { + // read only at UI level + readOnly = SWT.READ_ONLY == (style & SWT.READ_ONLY); + + this.cmsEditable = cmsEditable == null ? CmsEditable.NON_EDITABLE : cmsEditable; + if (this.cmsEditable instanceof Observable) + ((Observable) this.cmsEditable).addObserver(this); + + if (cmsEditable.canEdit()) { + mouseListener = createMouseListener(); + focusListener = createFocusListener(); + } + page = findPage(parent); + accessControlContext = AccessController.getContext(); + } + + /** + * Can be called to simplify the called to isModelInitialized() and initModel() + */ + protected void initModelIfNeeded(Node node) { + try { + if (!isModelInitialized(node)) + if (getCmsEditable().canEdit()) { + initModel(node); + node.getSession().save(); + } + } catch (RepositoryException e) { + throw new JcrException("Cannot initialize model", e); + } + } + + /** Called if user can edit and model is not initialized */ + protected Boolean isModelInitialized(Node node) throws RepositoryException { + return true; + } + + /** Called if user can edit and model is not initialized */ + protected void initModel(Node node) throws RepositoryException { + } + + /** Create (retrieve) the MouseListener to use. */ + protected MouseListener createMouseListener() { + return new MouseAdapter() { + private static final long serialVersionUID = 1L; + }; + } + + /** Create (retrieve) the FocusListener to use. */ + protected FocusListener createFocusListener() { + return new FocusListener() { + private static final long serialVersionUID = 1L; + + @Override + public void focusLost(FocusEvent event) { + } + + @Override + public void focusGained(FocusEvent event) { + } + }; + } + + protected Composite findPage(Composite composite) { + if (composite instanceof ScrolledPage) { + return (ScrolledPage) composite; + } else { + if (composite.getParent() == null) + return composite; + return findPage(composite.getParent()); + } + } + + public void layoutPage() { + if (page != null) + page.layout(true, true); + } + + protected void showControl(Control control) { + if (page != null && (page instanceof ScrolledPage)) + ((ScrolledPage) page).showControl(control); + } + + @Override + public void update(Observable o, Object arg) { + if (o == cmsEditable) + editingStateChanged(cmsEditable); + } + + /** To be overridden in order to provide the actual refresh */ + protected void refresh(Control control) throws RepositoryException { + } + + /** To be overridden.Save the edited part. */ + protected void save(EditablePart part) throws RepositoryException { + } + + /** Prepare the edited part */ + protected void prepare(EditablePart part, Object caretPosition) { + } + + /** Notified when the editing state changed. Does nothing, to be overridden */ + protected void editingStateChanged(CmsEditable cmsEditable) { + } + + @Override + public void refresh() { + // TODO check actual context in order to notice a discrepancy + Subject viewerSubject = getViewerSubject(); + Subject.doAs(viewerSubject, (PrivilegedAction) () -> { + try { + if (cmsEditable.canEdit() && !readOnly) + mouseListener = createMouseListener(); + else + mouseListener = null; + refresh(getControl()); + // layout(getControl()); + if (!getControl().isDisposed()) + layoutPage(); + } catch (RepositoryException e) { + throw new JcrException("Cannot refresh", e); + } + return null; + }); + } + + @Override + public void setSelection(ISelection selection, boolean reveal) { + this.selection = selection; + } + + protected void updateContent(EditablePart part) throws RepositoryException { + } + + // LOW LEVEL EDITION + protected void edit(EditablePart part, Object caretPosition) { + try { + if (edited == part) + return; + + if (edited != null && edited != part) { + EditablePart previouslyEdited = edited; + try { + stopEditing(true); + } catch (Exception e) { + notifyEditionException(e); + edit(previouslyEdited, caretPosition); + return; + } + } + + part.startEditing(); + edited = part; + updateContent(part); + prepare(part, caretPosition); + edited.getControl().addFocusListener(new FocusListener() { + private static final long serialVersionUID = 6883521812717097017L; + + @Override + public void focusLost(FocusEvent event) { + stopEditing(true); + } + + @Override + public void focusGained(FocusEvent event) { + } + }); + + layout(part.getControl()); + showControl(part.getControl()); + } catch (RepositoryException e) { + throw new JcrException("Cannot edit " + part, e); + } + } + + protected void stopEditing(Boolean save) { + if (edited instanceof Widget && ((Widget) edited).isDisposed()) { + edited = null; + return; + } + + assert edited != null; + if (edited == null) { + if (log.isTraceEnabled()) + log.warn("Told to stop editing while not editing anything"); + return; + } + + try { + if (save) + save(edited); + + edited.stopEditing(); + EditablePart editablePart = edited; + Control control = ((EditablePart) edited).getControl(); + edited = null; + // TODO make edited state management more robust + updateContent(editablePart); + layout(control); + } catch (RepositoryException e) { + throw new JcrException("Cannot stop editing", e); + } finally { + edited = null; + } + } + + // METHODS AVAILABLE TO EXTENDING CLASSES + protected void saveEdit() { + if (edited != null) + stopEditing(true); + } + + protected void cancelEdit() { + if (edited != null) + stopEditing(false); + } + + /** Layout this controls from the related base page. */ + public void layout(Control... controls) { + page.layout(controls); + } + + /** + * Find the first {@link EditablePart} in the parents hierarchy of this control + */ + protected EditablePart findDataParent(Control parent) { + if (parent instanceof EditablePart) { + return (EditablePart) parent; + } + if (parent.getParent() != null) + return findDataParent(parent.getParent()); + else + throw new IllegalStateException("No data parent found"); + } + + // UTILITIES + /** Check whether the edited part is in a proper state */ + protected void checkEdited() { + if (edited == null || (edited instanceof Widget) && ((Widget) edited).isDisposed()) + throw new IllegalStateException("Edited should not be null or disposed at this stage"); + } + + /** Persist all changes. */ + protected void persistChanges(Session session) throws RepositoryException { + session.save(); + session.refresh(false); + // TODO notify that changes have been persisted + } + + /** Convenience method using a Node in order to save the underlying session. */ + protected void persistChanges(Node anyNode) throws RepositoryException { + persistChanges(anyNode.getSession()); + } + + /** Notify edition exception */ + protected void notifyEditionException(Throwable e) { + Throwable eToLog = e; + if (e instanceof IllegalArgumentException) + if (e.getCause() instanceof SAXParseException) + eToLog = e.getCause(); + log.error(eToLog.getMessage(), eToLog); +// if (log.isTraceEnabled()) +// log.trace("Full stack of " + eToLog.getMessage(), e); + // TODO Light error notification popup + } + + protected Subject getViewerSubject() { + Subject res = null; + if (accessControlContext != null) { + res = Subject.getSubject(accessControlContext); + } + if (res == null) + throw new IllegalStateException("No subject associated with this viewer"); + return res; + } + + // GETTERS / SETTERS + public boolean isReadOnly() { + return readOnly; + } + + protected EditablePart getEdited() { + return edited; + } + + public MouseListener getMouseListener() { + return mouseListener; + } + + public FocusListener getFocusListener() { + return focusListener; + } + + public CmsEditable getCmsEditable() { + return cmsEditable; + } + + @Override + public ISelection getSelection() { + return selection; + } +} \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/EditablePart.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/EditablePart.java new file mode 100644 index 000000000..3967c97fb --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/EditablePart.java @@ -0,0 +1,12 @@ +package org.argeo.cms.ui.viewers; + +import org.eclipse.swt.widgets.Control; + +/** Manages whether an editable or non editable control is shown. */ +public interface EditablePart { + public void startEditing(); + + public void stopEditing(); + + public Control getControl(); +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/ItemPart.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/ItemPart.java new file mode 100644 index 000000000..4ca45d191 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/ItemPart.java @@ -0,0 +1,9 @@ +package org.argeo.cms.ui.viewers; + +import javax.jcr.Item; +import javax.jcr.RepositoryException; + +/** An editable part related to a JCR Item */ +public interface ItemPart { + public Item getItem() throws RepositoryException; +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/JcrVersionCmsEditable.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/JcrVersionCmsEditable.java new file mode 100644 index 000000000..11162e87f --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/JcrVersionCmsEditable.java @@ -0,0 +1,101 @@ +package org.argeo.cms.ui.viewers; + +import java.util.Observable; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.nodetype.NodeType; +import javax.jcr.version.VersionManager; + +import org.argeo.api.cms.CmsEditable; +import org.argeo.cms.CmsException; +import org.argeo.cms.ui.CmsEditionEvent; +import org.eclipse.rap.rwt.RWT; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Display; +import org.eclipse.swt.widgets.Event; +import org.eclipse.swt.widgets.Listener; + +/** Provides the CmsEditable semantic based on JCR versioning. */ +public class JcrVersionCmsEditable extends Observable implements CmsEditable { + private final String nodePath;// cache + private final VersionManager versionManager; + private final Boolean canEdit; + + public JcrVersionCmsEditable(Node node) throws RepositoryException { + this.nodePath = node.getPath(); + if (node.getSession().hasPermission(node.getPath(), + Session.ACTION_SET_PROPERTY)) { + // was Session.ACTION_ADD_NODE + canEdit = true; + if (!node.isNodeType(NodeType.MIX_VERSIONABLE)) { + node.addMixin(NodeType.MIX_VERSIONABLE); + node.getSession().save(); + } + versionManager = node.getSession().getWorkspace() + .getVersionManager(); + } else { + canEdit = false; + versionManager = null; + } + + // bind keys + if (canEdit) { + Display display = Display.getCurrent(); + display.setData(RWT.ACTIVE_KEYS, new String[] { "CTRL+RETURN", + "CTRL+E" }); + display.addFilter(SWT.KeyDown, new Listener() { + private static final long serialVersionUID = -4378653870463187318L; + + public void handleEvent(Event e) { + boolean ctrlPressed = (e.stateMask & SWT.CTRL) != 0; + if (ctrlPressed && e.keyCode == '\r') + stopEditing(); + else if (ctrlPressed && e.keyCode == 'E') + stopEditing(); + } + }); + } + } + + @Override + public Boolean canEdit() { + return canEdit; + } + + public Boolean isEditing() { + try { + if (!canEdit()) + return false; + return versionManager.isCheckedOut(nodePath); + } catch (RepositoryException e) { + throw new CmsException("Cannot check whether " + nodePath + + " is editing", e); + } + } + + @Override + public void startEditing() { + try { + versionManager.checkout(nodePath); + setChanged(); + } catch (RepositoryException e1) { + throw new CmsException("Cannot publish " + nodePath); + } + notifyObservers(new CmsEditionEvent(nodePath, + CmsEditionEvent.START_EDITING)); + } + + @Override + public void stopEditing() { + try { + versionManager.checkin(nodePath); + setChanged(); + } catch (RepositoryException e1) { + throw new CmsException("Cannot publish " + nodePath, e1); + } + notifyObservers(new CmsEditionEvent(nodePath, + CmsEditionEvent.STOP_EDITING)); + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/NodePart.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/NodePart.java new file mode 100644 index 000000000..b51d4fcba --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/NodePart.java @@ -0,0 +1,8 @@ +package org.argeo.cms.ui.viewers; + +import javax.jcr.Node; + +/** An editable part related to a node */ +public interface NodePart extends ItemPart { + public Node getNode(); +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/PropertyPart.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/PropertyPart.java new file mode 100644 index 000000000..793079e20 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/PropertyPart.java @@ -0,0 +1,8 @@ +package org.argeo.cms.ui.viewers; + +import javax.jcr.Property; + +/** An editable part related to a JCR Property */ +public interface PropertyPart extends ItemPart { + public Property getProperty(); +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/Section.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/Section.java new file mode 100644 index 000000000..d282eebbe --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/Section.java @@ -0,0 +1,165 @@ +package org.argeo.cms.ui.viewers; + +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.ui.widgets.JcrComposite; +import org.eclipse.swt.SWT; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + +/** A structured UI related to a JCR context. */ +public class Section extends JcrComposite { + private static final long serialVersionUID = -5933796173755739207L; + + private final Section parentSection; + private Composite sectionHeader; + private final Integer relativeDepth; + + public Section(Composite parent, int style, Node node) { + this(parent, findSection(parent), style, node); + } + + public Section(Section section, int style, Node node) { + this(section, section, style, node); + } + + protected Section(Composite parent, Section parentSection, int style, Node node) { + super(parent, style, node); + try { + this.parentSection = parentSection; + if (parentSection != null) { + relativeDepth = getNode().getDepth() - parentSection.getNode().getDepth(); + } else { + relativeDepth = 0; + } + setLayout(CmsSwtUtils.noSpaceGridLayout()); + } catch (RepositoryException e) { + throw new IllegalStateException("Cannot create section from " + node, e); + } + } + + public Map getSubSections() throws RepositoryException { + LinkedHashMap result = new LinkedHashMap(); + for (Control child : getChildren()) { + if (child instanceof Composite) { + collectDirectSubSections((Composite) child, result); + } + } + return Collections.unmodifiableMap(result); + } + + private void collectDirectSubSections(Composite composite, LinkedHashMap subSections) + throws RepositoryException { + if (composite == sectionHeader || composite instanceof EditablePart) + return; + if (composite instanceof Section) { + Section section = (Section) composite; + subSections.put(section.getNodeId(), section); + return; + } + + for (Control child : composite.getChildren()) + if (child instanceof Composite) + collectDirectSubSections((Composite) child, subSections); + } + + public Composite createHeader() { + return createHeader(this); + } + + public Composite createHeader(Composite parent) { + if (sectionHeader != null) + sectionHeader.dispose(); + + sectionHeader = new Composite(parent, SWT.NONE); + sectionHeader.setLayoutData(CmsSwtUtils.fillWidth()); + sectionHeader.setLayout(CmsSwtUtils.noSpaceGridLayout()); + // sectionHeader.moveAbove(null); + // layout(); + return sectionHeader; + } + + public Composite getHeader() { + if (sectionHeader != null && sectionHeader.isDisposed()) + sectionHeader = null; + return sectionHeader; + } + + // SECTION PARTS + public SectionPart getSectionPart(String partId) { + for (Control child : getChildren()) { + if (child instanceof SectionPart) { + SectionPart sectionPart = (SectionPart) child; + if (sectionPart.getPartId().equals(partId)) + return sectionPart; + } + } + return null; + } + + public SectionPart nextSectionPart(SectionPart sectionPart) { + Control[] children = getChildren(); + for (int i = 0; i < children.length; i++) { + if (sectionPart == children[i]) { + for (int j = i + 1; j < children.length; j++) { + if (children[i + 1] instanceof SectionPart) { + return (SectionPart) children[i + 1]; + } + } + +// if (i + 1 < children.length) { +// Composite next = (Composite) children[i + 1]; +// return (SectionPart) next; +// } else { +// // next section +// } + } + } + return null; + } + + public SectionPart previousSectionPart(SectionPart sectionPart) { + Control[] children = getChildren(); + for (int i = 0; i < children.length; i++) { + if (sectionPart == children[i]) + if (i != 0) { + Composite previous = (Composite) children[i - 1]; + return (SectionPart) previous; + } else { + // previous section + } + } + return null; + } + + @Override + public String toString() { + if (parentSection == null) + return "Main section " + getNode(); + return "Section " + getNode(); + } + + public Section getParentSection() { + return parentSection; + } + + public Integer getRelativeDepth() { + return relativeDepth; + } + + /** Recursively finds the related section in the parents (can be itself) */ + public static Section findSection(Control control) { + if (control == null) + return null; + if (control instanceof Section) + return (Section) control; + else + return findSection(control.getParent()); + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/SectionPart.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/SectionPart.java new file mode 100644 index 000000000..f0b367f5a --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/SectionPart.java @@ -0,0 +1,9 @@ +package org.argeo.cms.ui.viewers; + + +/** An editable part dynamically related to a Section */ +public interface SectionPart extends EditablePart, NodePart { + public String getPartId(); + + public Section getSection(); +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/package-info.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/package-info.java new file mode 100644 index 000000000..2f0793127 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/package-info.java @@ -0,0 +1,2 @@ +/** Argeo CMS generic viewers, based on JFace. */ +package org.argeo.cms.ui.viewers; \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/ContextOverlay.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/ContextOverlay.java new file mode 100644 index 000000000..7bc0f79b5 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/ContextOverlay.java @@ -0,0 +1,113 @@ +package org.argeo.cms.ui.widgets; + +import org.argeo.cms.swt.CmsSwtUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.ShellAdapter; +import org.eclipse.swt.events.ShellEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Shell; + +/** + * Manages a lightweight shell which is related to a {@link Control}, typically + * in order to reproduce a dropdown semantic, but with more flexibility. + */ +public class ContextOverlay extends ScrolledPage { + private static final long serialVersionUID = 6702077429573324009L; + +// private Shell shell; + private Control control; + + private int maxHeight = 400; + + public ContextOverlay(Control control, int style) { + super(createShell(control, style), SWT.NONE); + Shell shell = getShell(); + setLayoutData(CmsSwtUtils.fillAll()); + // TODO make autohide configurable? + //shell.addShellListener(new AutoHideShellListener()); + this.control = control; + control.addDisposeListener((e) -> { + dispose(); + shell.dispose(); + }); + } + + private static Composite createShell(Control control, int style) { + if (control == null) + throw new IllegalArgumentException("Control cannot be null"); + if (control.isDisposed()) + throw new IllegalArgumentException("Control is disposed"); + Shell shell = new Shell(control.getShell(), SWT.NO_TRIM); + shell.setLayout(CmsSwtUtils.noSpaceGridLayout()); + Composite placeholder = new Composite(shell, SWT.BORDER); + placeholder.setLayoutData(CmsSwtUtils.fillAll()); + placeholder.setLayout(CmsSwtUtils.noSpaceGridLayout()); + return placeholder; + } + + public void show() { + Point relativeControlLocation = control.getLocation(); + Point controlLocation = control.toDisplay(relativeControlLocation.x, relativeControlLocation.y); + + int controlWidth = control.getBounds().width; + + Shell shell = getShell(); + + layout(true, true); + shell.pack(); + shell.layout(true, true); + int targetShellWidth = shell.getSize().x < controlWidth ? controlWidth : shell.getSize().x; + if (shell.getSize().y > maxHeight) { + shell.setSize(targetShellWidth, maxHeight); + } else { + shell.setSize(targetShellWidth, shell.getSize().y); + } + + int shellHeight = shell.getSize().y; + int controlHeight = control.getBounds().height; + Point shellLocation = new Point(controlLocation.x, controlLocation.y + controlHeight); + int displayHeight = shell.getDisplay().getBounds().height; + if (shellLocation.y + shellHeight > displayHeight) {// bottom of page + shellLocation = new Point(controlLocation.x, controlLocation.y - shellHeight); + } + shell.setLocation(shellLocation); + + if (getChildren().length != 0) + shell.open(); + if (!control.isDisposed()) + control.setFocus(); + } + + public void hide() { + getShell().setVisible(false); + onHide(); + } + + public boolean isShellVisible() { + if (isDisposed()) + return false; + return getShell().isVisible(); + } + + /** to be overridden */ + protected void onHide() { + // does nothing by default. + } + + private class AutoHideShellListener extends ShellAdapter { + private static final long serialVersionUID = 7743287433907938099L; + + @Override + public void shellDeactivated(ShellEvent e) { + try { + Thread.sleep(1000); + } catch (InterruptedException e1) { + // silent + } + if (!control.isDisposed() && !control.isFocusControl()) + hide(); + } + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/EditableImage.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/EditableImage.java new file mode 100644 index 000000000..c2393f267 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/EditableImage.java @@ -0,0 +1,112 @@ +package org.argeo.cms.ui.widgets; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.api.cms.Cms2DSize; +import org.argeo.api.cms.CmsLog; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.ui.util.CmsUiUtils; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +/** A stylable and editable image. */ +public abstract class EditableImage extends StyledControl { + private static final long serialVersionUID = -5689145523114022890L; + private final static CmsLog log = CmsLog.getLog(EditableImage.class); + + private Cms2DSize preferredImageSize; + private Boolean loaded = false; + + public EditableImage(Composite parent, int swtStyle) { + super(parent, swtStyle); + } + + public EditableImage(Composite parent, int swtStyle, Cms2DSize preferredImageSize) { + super(parent, swtStyle); + this.preferredImageSize = preferredImageSize; + } + + public EditableImage(Composite parent, int style, Node node, boolean cacheImmediately, Cms2DSize preferredImageSize) + throws RepositoryException { + super(parent, style, node, cacheImmediately); + this.preferredImageSize = preferredImageSize; + } + + @Override + protected void setContainerLayoutData(Composite composite) { + // composite.setLayoutData(fillWidth()); + } + + @Override + protected void setControlLayoutData(Control control) { + // control.setLayoutData(fillWidth()); + } + + /** To be overriden. */ + protected String createImgTag() throws RepositoryException { + return CmsUiUtils + .noImg(preferredImageSize != null ? preferredImageSize : new Cms2DSize(getSize().x, getSize().y)); + } + + protected Label createLabel(Composite box, String style) { + Label lbl = new Label(box, getStyle()); + // lbl.setLayoutData(CmsUiUtils.fillWidth()); + CmsSwtUtils.markup(lbl); + CmsSwtUtils.style(lbl, style); + if (mouseListener != null) + lbl.addMouseListener(mouseListener); + load(lbl); + return lbl; + } + + /** To be overriden. */ + protected synchronized Boolean load(Control control) { + String imgTag; + try { + imgTag = createImgTag(); + } catch (Exception e) { + // throw new CmsException("Cannot retrieve image", e); + log.error("Cannot retrieve image", e); + imgTag = CmsUiUtils.noImg(preferredImageSize); + loaded = false; + } + + if (imgTag == null) { + loaded = false; + imgTag = CmsUiUtils.noImg(preferredImageSize); + } else + loaded = true; + if (control != null) { + ((Label) control).setText(imgTag); + control.setSize(preferredImageSize != null + ? new Point(preferredImageSize.getWidth(), preferredImageSize.getHeight()) + : getSize()); + } else { + loaded = false; + } + getParent().layout(); + return loaded; + } + + public void setPreferredSize(Cms2DSize size) { + this.preferredImageSize = size; + if (!loaded) { + load((Label) getControl()); + } + } + + protected Text createText(Composite box, String style) { + Text text = new Text(box, getStyle()); + CmsSwtUtils.style(text, style); + return text; + } + + public Cms2DSize getPreferredImageSize() { + return preferredImageSize; + } + +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/EditableText.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/EditableText.java new file mode 100644 index 000000000..e3499ac4b --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/EditableText.java @@ -0,0 +1,145 @@ +package org.argeo.cms.ui.widgets; + +import javax.jcr.Item; +import javax.jcr.RepositoryException; + +import org.argeo.cms.swt.CmsSwtUtils; +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.Control; +import org.eclipse.swt.widgets.Label; +import org.eclipse.swt.widgets.Text; + +/** Editable text part displaying styled text. */ +public class EditableText extends StyledControl { + private static final long serialVersionUID = -6372283442330912755L; + + private boolean editable = true; + + private Color highlightColor; + private Composite highlight; + + private boolean useTextAsLabel = false; + + public EditableText(Composite parent, int style) { + super(parent, style); + editable = !(SWT.READ_ONLY == (style & SWT.READ_ONLY)); + highlightColor = parent.getDisplay().getSystemColor(SWT.COLOR_GRAY); + } + + public EditableText(Composite parent, int style, Item item) throws RepositoryException { + this(parent, style, item, false); + } + + public EditableText(Composite parent, int style, Item item, boolean cacheImmediately) throws RepositoryException { + super(parent, style, item, cacheImmediately); + editable = !(SWT.READ_ONLY == (style & SWT.READ_ONLY)); + highlightColor = parent.getDisplay().getSystemColor(SWT.COLOR_GRAY); + } + + @Override + protected Control createControl(Composite box, String style) { + if (isEditing() && getEditable()) { + return createText(box, style, true); + } else { + if (useTextAsLabel) { + return createTextLabel(box, style); + } else { + return createLabel(box, style); + } + } + } + + protected Label createLabel(Composite box, String style) { + Label lbl = new Label(box, getStyle() | SWT.WRAP); + lbl.setLayoutData(CmsSwtUtils.fillWidth()); + if (style != null) + CmsSwtUtils.style(lbl, style); + CmsSwtUtils.markup(lbl); + if (mouseListener != null) + lbl.addMouseListener(mouseListener); + return lbl; + } + + protected Text createTextLabel(Composite box, String style) { + Text lbl = new Text(box, getStyle() | SWT.MULTI); + lbl.setEditable(false); + lbl.setLayoutData(CmsSwtUtils.fillWidth()); + if (style != null) + CmsSwtUtils.style(lbl, style); + CmsSwtUtils.markup(lbl); + if (mouseListener != null) + lbl.addMouseListener(mouseListener); + return lbl; + } + + protected Text createText(Composite box, String style, boolean editable) { + highlight = new Composite(box, SWT.NONE); + highlight.setBackground(highlightColor); + GridData highlightGd = new GridData(SWT.FILL, SWT.FILL, false, false); + highlightGd.widthHint = 5; + highlightGd.heightHint = 3; + highlight.setLayoutData(highlightGd); + + final Text text = new Text(box, getStyle() | SWT.MULTI | SWT.WRAP); + text.setEditable(editable); + GridData textLayoutData = CmsSwtUtils.fillWidth(); + // textLayoutData.heightHint = preferredHeight; + text.setLayoutData(textLayoutData); + if (style != null) + CmsSwtUtils.style(text, style); + text.setFocus(); + return text; + } + + @Override + protected void clear(boolean deep) { + if (highlight != null) + highlight.dispose(); + super.clear(deep); + } + + public void setText(String text) { + Control child = getControl(); + if (child instanceof Label) + ((Label) child).setText(text); + else if (child instanceof Text) + ((Text) child).setText(text); + } + + public Text getAsText() { + return (Text) getControl(); + } + + public Label getAsLabel() { + return (Label) getControl(); + } + + public String getText() { + Control child = getControl(); + + if (child instanceof Label) + return ((Label) child).getText(); + else if (child instanceof Text) + return ((Text) child).getText(); + else + throw new IllegalStateException("Unsupported control " + child.getClass()); + } + + /** @deprecated Use {@link #isEditable()} instead. */ + @Deprecated + public boolean getEditable() { + return isEditable(); + } + + public boolean isEditable() { + return editable; + } + + public void setUseTextAsLabel(boolean useTextAsLabel) { + this.useTextAsLabel = useTextAsLabel; + } + +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/Img.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/Img.java new file mode 100644 index 000000000..3a4a60c9f --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/Img.java @@ -0,0 +1,155 @@ +package org.argeo.cms.ui.widgets; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.api.cms.Cms2DSize; +import org.argeo.api.cms.CmsImageManager; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.ui.internal.JcrFileUploadReceiver; +import org.argeo.cms.ui.viewers.NodePart; +import org.argeo.cms.ui.viewers.Section; +import org.argeo.cms.ui.viewers.SectionPart; +import org.argeo.jcr.Jcr; +import org.argeo.jcr.JcrException; +import org.eclipse.rap.fileupload.FileUploadHandler; +import org.eclipse.rap.fileupload.FileUploadListener; +import org.eclipse.rap.fileupload.FileUploadReceiver; +import org.eclipse.rap.rwt.service.ServerPushSession; +import org.eclipse.rap.rwt.widgets.FileUpload; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + +/** An image within the Argeo Text framework */ +public class Img extends EditableImage implements SectionPart, NodePart { + private static final long serialVersionUID = 6233572783968188476L; + + private final Section section; + + private final CmsImageManager imageManager; + private FileUploadHandler currentUploadHandler = null; + private FileUploadListener fileUploadListener; + + public Img(Composite parent, int swtStyle, Node imgNode, Cms2DSize preferredImageSize) throws RepositoryException { + this(Section.findSection(parent), parent, swtStyle, imgNode, preferredImageSize, null); + setStyle(TextStyles.TEXT_IMAGE); + } + + public Img(Composite parent, int swtStyle, Node imgNode) throws RepositoryException { + this(Section.findSection(parent), parent, swtStyle, imgNode, null, null); + setStyle(TextStyles.TEXT_IMAGE); + } + + public Img(Composite parent, int swtStyle, Node imgNode, CmsImageManager imageManager) + throws RepositoryException { + this(Section.findSection(parent), parent, swtStyle, imgNode, null, imageManager); + setStyle(TextStyles.TEXT_IMAGE); + } + + Img(Section section, Composite parent, int swtStyle, Node imgNode, Cms2DSize preferredImageSize, + CmsImageManager imageManager) throws RepositoryException { + super(parent, swtStyle, imgNode, false, preferredImageSize); + this.section = section; + this.imageManager = imageManager != null ? imageManager + : (CmsImageManager) CmsSwtUtils.getCmsView(section).getImageManager(); + CmsSwtUtils.style(this, TextStyles.TEXT_IMG); + } + + @Override + protected Control createControl(Composite box, String style) { + if (isEditing()) { + try { + return createImageChooser(box, style); + } catch (RepositoryException e) { + throw new JcrException("Cannot create image chooser", e); + } + } else { + return createLabel(box, style); + } + } + + @Override + public synchronized void stopEditing() { + super.stopEditing(); + fileUploadListener = null; + } + + @Override + protected synchronized Boolean load(Control lbl) { + Node imgNode = getNode(); + boolean loaded = imageManager.load(imgNode, lbl, getPreferredImageSize()); + // getParent().layout(); + return loaded; + } + + protected Node getUploadFolder() { + return Jcr.getParent(getNode()); + } + + protected String getUploadName() { + Node node = getNode(); + return Jcr.getName(node) + '[' + Jcr.getIndex(node) + ']'; + } + + protected CmsImageManager getImageManager() { + return imageManager; + } + + protected Control createImageChooser(Composite box, String style) throws RepositoryException { + JcrFileUploadReceiver receiver = new JcrFileUploadReceiver(this, getUploadFolder(), getUploadName(), + imageManager); + if (currentUploadHandler != null) + currentUploadHandler.dispose(); + currentUploadHandler = prepareUpload(receiver); + final ServerPushSession pushSession = new ServerPushSession(); + final FileUpload fileUpload = new FileUpload(box, SWT.NONE); + CmsSwtUtils.style(fileUpload, style); + fileUpload.addSelectionListener(new SelectionAdapter() { + private static final long serialVersionUID = -9158471843941668562L; + + @Override + public void widgetSelected(SelectionEvent e) { + pushSession.start(); + fileUpload.submit(currentUploadHandler.getUploadUrl()); + } + }); + return fileUpload; + } + + protected FileUploadHandler prepareUpload(FileUploadReceiver receiver) { + final FileUploadHandler uploadHandler = new FileUploadHandler(receiver); + if (fileUploadListener != null) + uploadHandler.addUploadListener(fileUploadListener); + return uploadHandler; + } + + @Override + public Section getSection() { + return section; + } + + public void setFileUploadListener(FileUploadListener fileUploadListener) { + this.fileUploadListener = fileUploadListener; + if (currentUploadHandler != null) + currentUploadHandler.addUploadListener(fileUploadListener); + } + + @Override + public Node getItem() throws RepositoryException { + return getNode(); + } + + @Override + public String getPartId() { + return getNodeId(); + } + + @Override + public String toString() { + return "Img #" + getPartId(); + } + +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/JcrComposite.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/JcrComposite.java new file mode 100644 index 000000000..5d3576f27 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/JcrComposite.java @@ -0,0 +1,213 @@ +package org.argeo.cms.ui.widgets; + +import javax.jcr.Item; +import javax.jcr.ItemNotFoundException; +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.jcr.JcrException; +import org.eclipse.swt.widgets.Composite; + +/** A composite which can (optionally) manage a JCR Item. */ +public class JcrComposite extends Composite { + private static final long serialVersionUID = -1447009015451153367L; + + private Session session; + + private String nodeId; + private String property = null; + private Node cache; + + /** Regular composite constructor. No layout is set. */ + public JcrComposite(Composite parent, int style) { + super(parent, style); + session = null; + nodeId = null; + } + + public JcrComposite(Composite parent, int style, Item item) { + this(parent, style, item, false); + } + + public JcrComposite(Composite parent, int style, Item item, boolean cacheImmediately) { + super(parent, style); + if (item != null) + try { + this.session = item.getSession(); +// if (!cacheImmediately && (SWT.READ_ONLY == (style & SWT.READ_ONLY))) { +// // (useless?) optimization: we only save a pointer to the session, +// // not even a reference to the item +// this.nodeId = null; +// } else { + Node node; + Property property = null; + if (item instanceof Node) { + node = (Node) item; + } else {// Property + property = (Property) item; + if (property.isMultiple())// TODO manage property index + throw new UnsupportedOperationException("Multiple properties not supported yet."); + this.property = property.getName(); + node = property.getParent(); + } + this.nodeId = node.getIdentifier(); + if (cacheImmediately) + this.cache = node; +// } + setLayout(CmsSwtUtils.noSpaceGridLayout()); + } catch (RepositoryException e) { + throw new IllegalStateException("Cannot create composite from " + item, e); + } + } + + public synchronized Node getNode() { + try { + if (!itemIsNode()) + throw new IllegalStateException("Item is not a Node"); + return getNodeInternal(); + } catch (RepositoryException e) { + throw new JcrException("Cannot get node " + nodeId, e); + } + } + + private synchronized Node getNodeInternal() throws RepositoryException { + if (cache != null) + return cache; + else if (session != null) + if (nodeId != null) + return session.getNodeByIdentifier(nodeId); + else + return null; + else + return null; + } + + public synchronized String getPropertyName() { + try { + return getProperty().getName(); + } catch (RepositoryException e) { + throw new JcrException("Cannot get property name", e); + } + } + + public synchronized Node getPropertyNode() { + try { + return getProperty().getNode(); + } catch (RepositoryException e) { + throw new JcrException("Cannot get property name", e); + } + } + + public synchronized Property getProperty() { + try { + if (itemIsNode()) + throw new IllegalStateException("Item is not a Property"); + Node node = getNodeInternal(); + if (!node.hasProperty(property)) + throw new IllegalStateException("Property " + property + " is not set on " + node); + return node.getProperty(property); + } catch (RepositoryException e) { + throw new JcrException("Cannot get property " + property + " from node " + nodeId, e); + } + } + + public synchronized boolean itemIsNode() { + return property == null; + } + + public synchronized boolean itemExists() { + if (session == null) + return false; + try { + Node n = session.getNodeByIdentifier(nodeId); + if (!itemIsNode()) + return n.hasProperty(property); + else + return true; + } catch (ItemNotFoundException e) { + return false; + } catch (RepositoryException e) { + throw new JcrException("Cannot check whether node exists", e); + } + } + + /** Set/update the cache or change the node */ + public synchronized void setNode(Node node) { + if (!itemIsNode()) + throw new IllegalArgumentException("Cannot set a Node on a Property"); + + if (node == null) {// clear cache + this.cache = null; + return; + } + + try { +// if (session != null || session != node.getSession())// check session +// throw new IllegalArgumentException("Uncompatible session"); +// if (session == null) + session = node.getSession(); + if (nodeId == null || !nodeId.equals(node.getIdentifier())) { + nodeId = node.getIdentifier(); + cache = node; + itemUpdated(); + } else { + cache = node;// set/update cache + } + } catch (RepositoryException e) { + throw new IllegalStateException(e); + } + } + + /** Set/update the cache or change the property */ + public synchronized void setProperty(Property prop) { + if (itemIsNode()) + throw new IllegalArgumentException("Cannot set a Property on a Node"); + + if (prop == null) {// clear cache + this.cache = null; + return; + } + + try { + if (session == null || session != prop.getSession())// check session + throw new IllegalArgumentException("Uncompatible session"); + + Node node = prop.getNode(); + if (nodeId == null || !nodeId.equals(node.getIdentifier()) || !property.equals(prop.getName())) { + nodeId = node.getIdentifier(); + property = prop.getName(); + cache = node; + itemUpdated(); + } else { + cache = node;// set/update cache + } + } catch (RepositoryException e) { + throw new IllegalStateException(e); + } + } + + public synchronized String getNodeId() { + return nodeId; + } + + /** Change the node, does nothing if same. */ + public synchronized void setNodeId(String nodeId) throws RepositoryException { + if (this.nodeId != null && this.nodeId.equals(nodeId)) + return; + this.nodeId = nodeId; + if (cache != null) + cache = session.getNodeByIdentifier(this.nodeId); + itemUpdated(); + } + + protected synchronized void itemUpdated() { + layout(); + } + + public Session getSession() { + return session; + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/ScrolledPage.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/ScrolledPage.java new file mode 100644 index 000000000..517e796e9 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/ScrolledPage.java @@ -0,0 +1,74 @@ +package org.argeo.cms.ui.widgets; + +import org.eclipse.swt.SWT; +import org.eclipse.swt.custom.ScrolledComposite; +import org.eclipse.swt.events.ControlEvent; +import org.eclipse.swt.graphics.Point; +import org.eclipse.swt.graphics.Rectangle; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + +/** + * A composite that can be scrolled vertically. It wraps a + * {@link ScrolledComposite} (and is being wrapped by it), simplifying its + * configuration. + */ +public class ScrolledPage extends Composite { + private static final long serialVersionUID = 1593536965663574437L; + + private ScrolledComposite scrolledComposite; + + public ScrolledPage(Composite parent, int style) { + this(parent, style, false); + } + + public ScrolledPage(Composite parent, int style, boolean alwaysShowScroll) { + super(createScrolledComposite(parent, alwaysShowScroll), style); + scrolledComposite = (ScrolledComposite) getParent(); + scrolledComposite.setContent(this); + + scrolledComposite.setExpandVertical(true); + scrolledComposite.setExpandHorizontal(true); + scrolledComposite.addControlListener(new ScrollControlListener()); + } + + private static ScrolledComposite createScrolledComposite(Composite parent, boolean alwaysShowScroll) { + ScrolledComposite scrolledComposite = new ScrolledComposite(parent, SWT.V_SCROLL); + scrolledComposite.setAlwaysShowScrollBars(alwaysShowScroll); + return scrolledComposite; + } + + @Override + public void layout(boolean changed, boolean all) { + updateScroll(); + super.layout(changed, all); + } + + public void showControl(Control control) { + scrolledComposite.showControl(control); + } + + protected void updateScroll() { + Rectangle r = scrolledComposite.getClientArea(); + Point preferredSize = computeSize(r.width, SWT.DEFAULT); + scrolledComposite.setMinHeight(preferredSize.y); + } + + // public ScrolledComposite getScrolledComposite() { + // return this.scrolledComposite; + // } + + /** Set it on the wrapping scrolled composite */ + @Override + public void setLayoutData(Object layoutData) { + scrolledComposite.setLayoutData(layoutData); + } + + private class ScrollControlListener extends org.eclipse.swt.events.ControlAdapter { + private static final long serialVersionUID = -3586986238567483316L; + + public void controlResized(ControlEvent e) { + updateScroll(); + } + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/StyledControl.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/StyledControl.java new file mode 100644 index 000000000..e3a5cb473 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/StyledControl.java @@ -0,0 +1,153 @@ +package org.argeo.cms.ui.widgets; + +import javax.jcr.Item; + +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.ui.CmsUiConstants; +import org.argeo.eclipse.ui.specific.EclipseUiSpecificUtils; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.FocusListener; +import org.eclipse.swt.events.MouseListener; +import org.eclipse.swt.widgets.Composite; +import org.eclipse.swt.widgets.Control; + +/** Editable text part displaying styled text. */ +public abstract class StyledControl extends JcrComposite implements CmsUiConstants { + private static final long serialVersionUID = -6372283442330912755L; + private Control control; + + private Composite container; + private Composite box; + + protected MouseListener mouseListener; + protected FocusListener focusListener; + + private Boolean editing = Boolean.FALSE; + + private Composite ancestorToLayout; + + public StyledControl(Composite parent, int swtStyle) { + super(parent, swtStyle); + setLayout(CmsSwtUtils.noSpaceGridLayout()); + } + + public StyledControl(Composite parent, int style, Item item) { + super(parent, style, item); + } + + public StyledControl(Composite parent, int style, Item item, boolean cacheImmediately) { + super(parent, style, item, cacheImmediately); + } + + protected abstract Control createControl(Composite box, String style); + + protected Composite createBox() { + Composite box = new Composite(container, SWT.INHERIT_DEFAULT); + setContainerLayoutData(box); + box.setLayout(CmsSwtUtils.noSpaceGridLayout(3)); + return box; + } + + protected Composite createContainer() { + Composite container = new Composite(this, SWT.INHERIT_DEFAULT); + setContainerLayoutData(container); + container.setLayout(CmsSwtUtils.noSpaceGridLayout()); + return container; + } + + public Control getControl() { + return control; + } + + protected synchronized Boolean isEditing() { + return editing; + } + + public synchronized void startEditing() { + assert !isEditing(); + editing = true; + // int height = control.getSize().y; + String style = (String) EclipseUiSpecificUtils.getStyleData(control); + clear(false); + refreshControl(style); + + // add the focus listener to the newly created edition control + if (focusListener != null) + control.addFocusListener(focusListener); + } + + public synchronized void stopEditing() { + assert isEditing(); + editing = false; + String style = (String) EclipseUiSpecificUtils.getStyleData(control); + clear(false); + refreshControl(style); + } + + protected void refreshControl(String style) { + control = createControl(box, style); + setControlLayoutData(control); + if (ancestorToLayout != null) + ancestorToLayout.layout(true, true); + else + getParent().layout(true, true); + } + + public void setStyle(String style) { + Object currentStyle = null; + if (control != null) + currentStyle = EclipseUiSpecificUtils.getStyleData(control); + if (currentStyle != null && currentStyle.equals(style)) + return; + + clear(true); + refreshControl(style); + + if (style != null) { + CmsSwtUtils.style(box, style + "_box"); + CmsSwtUtils.style(container, style + "_container"); + } + } + + /** To be overridden */ + protected void setControlLayoutData(Control control) { + control.setLayoutData(CmsSwtUtils.fillWidth()); + } + + /** To be overridden */ + protected void setContainerLayoutData(Composite composite) { + composite.setLayoutData(CmsSwtUtils.fillWidth()); + } + + protected void clear(boolean deep) { + if (deep) { + for (Control control : getChildren()) + control.dispose(); + container = createContainer(); + box = createBox(); + } else { + control.dispose(); + } + } + + public void setMouseListener(MouseListener mouseListener) { + if (this.mouseListener != null && control != null) + control.removeMouseListener(this.mouseListener); + this.mouseListener = mouseListener; + if (control != null && this.mouseListener != null) + control.addMouseListener(mouseListener); + } + + public void setFocusListener(FocusListener focusListener) { + if (this.focusListener != null && control != null) + control.removeFocusListener(this.focusListener); + this.focusListener = focusListener; + if (control != null && this.focusListener != null) + control.addFocusListener(focusListener); + } + + public void setAncestorToLayout(Composite ancestorToLayout) { + this.ancestorToLayout = ancestorToLayout; + } + +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/TextStyles.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/TextStyles.java new file mode 100644 index 000000000..e461ed0df --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/TextStyles.java @@ -0,0 +1,37 @@ +package org.argeo.cms.ui.widgets; + +/** Styles references in the CSS. */ +public interface TextStyles { + /** The whole page area */ + public final static String TEXT_AREA = "text_area"; + /** Area providing controls for editing text */ + public final static String TEXT_EDITOR_HEADER = "text_editor_header"; + /** The styled composite for editing the text */ + public final static String TEXT_STYLED_COMPOSITE = "text_styled_composite"; + /** A section */ + public final static String TEXT_SECTION = "text_section"; + /** A paragraph */ + public final static String TEXT_PARAGRAPH = "text_paragraph"; + /** An image */ + public final static String TEXT_IMG = "text_img"; + /** The dialog to edit styled paragraph */ + public final static String TEXT_STYLED_TOOLS_DIALOG = "text_styled_tools_dialog"; + + /* + * DEFAULT TEXT STYLES + */ + /** Default style for text body */ + public final static String TEXT_DEFAULT = "text_default"; + /** Fixed-width, typically code */ + public final static String TEXT_PRE = "text_pre"; + /** Quote */ + public final static String TEXT_QUOTE = "text_quote"; + /** Title */ + public final static String TEXT_TITLE = "text_title"; + /** Header (to be dynamically completed with the depth, e.g. text_h1) */ + public final static String TEXT_H = "text_h"; + + /** Default style for images */ + public final static String TEXT_IMAGE = "text_image"; + +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/package-info.java b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/package-info.java new file mode 100644 index 000000000..514f75380 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/package-info.java @@ -0,0 +1,2 @@ +/** Argeo CMS generic widgets, based on SWT. */ +package org.argeo.cms.ui.widgets; \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/AbstractNodeContentProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/AbstractNodeContentProvider.java new file mode 100644 index 000000000..fdafa982e --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/AbstractNodeContentProvider.java @@ -0,0 +1,138 @@ +package org.argeo.eclipse.ui.jcr; + +import java.util.ArrayList; +import java.util.List; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.argeo.api.cms.CmsLog; +import org.argeo.eclipse.ui.AbstractTreeContentProvider; +import org.argeo.eclipse.ui.EclipseUiException; + +/** Canonical implementation of tree content provider manipulating JCR nodes. */ +public abstract class AbstractNodeContentProvider extends + AbstractTreeContentProvider { + private static final long serialVersionUID = -4905836490027272569L; + + private final static CmsLog log = CmsLog + .getLog(AbstractNodeContentProvider.class); + + private Session session; + + public AbstractNodeContentProvider(Session session) { + this.session = session; + } + + /** + * Whether this path is a base path (and thus has no parent). By default it + * returns true if path is '/' (root node) + */ + protected Boolean isBasePath(String path) { + // root node + return path.equals("/"); + } + + @Override + public Object[] getChildren(Object element) { + Object[] children; + if (element instanceof Node) { + try { + Node node = (Node) element; + children = getChildren(node); + } catch (RepositoryException e) { + throw new EclipseUiException("Cannot get children of " + element, e); + } + } else if (element instanceof WrappedNode) { + WrappedNode wrappedNode = (WrappedNode) element; + try { + children = getChildren(wrappedNode.getNode()); + } catch (RepositoryException e) { + throw new EclipseUiException("Cannot get children of " + + wrappedNode, e); + } + } else if (element instanceof NodesWrapper) { + NodesWrapper node = (NodesWrapper) element; + children = node.getChildren(); + } else { + children = super.getChildren(element); + } + + children = sort(element, children); + return children; + } + + /** Do not sort by default. To be overidden to provide custom sort. */ + protected Object[] sort(Object parent, Object[] children) { + return children; + } + + /** + * To be overridden in order to filter out some nodes. Does nothing by + * default. The provided list is a temporary one and can thus be modified + * directly . (e.g. via an iterator) + */ + protected List filterChildren(List children) + throws RepositoryException { + return children; + } + + protected Object[] getChildren(Node node) throws RepositoryException { + List nodes = new ArrayList(); + for (NodeIterator nit = node.getNodes(); nit.hasNext();) + nodes.add(nit.nextNode()); + nodes = filterChildren(nodes); + return nodes.toArray(); + } + + @Override + public Object getParent(Object element) { + if (element instanceof Node) { + Node node = (Node) element; + try { + String path = node.getPath(); + if (isBasePath(path)) + return null; + else + return node.getParent(); + } catch (RepositoryException e) { + log.warn("Cannot get parent of " + element + ": " + e); + return null; + } + } else if (element instanceof WrappedNode) { + WrappedNode wrappedNode = (WrappedNode) element; + return wrappedNode.getParent(); + } else if (element instanceof NodesWrapper) { + NodesWrapper nodesWrapper = (NodesWrapper) element; + return this.getParent(nodesWrapper.getNode()); + } + return super.getParent(element); + } + + @Override + public boolean hasChildren(Object element) { + try { + if (element instanceof Node) { + Node node = (Node) element; + return node.hasNodes(); + } else if (element instanceof WrappedNode) { + WrappedNode wrappedNode = (WrappedNode) element; + return wrappedNode.getNode().hasNodes(); + } else if (element instanceof NodesWrapper) { + NodesWrapper nodesWrapper = (NodesWrapper) element; + return nodesWrapper.hasChildren(); + } + + } catch (RepositoryException e) { + throw new EclipseUiException("Cannot check whether " + element + + " has children", e); + } + return super.hasChildren(element); + } + + public Session getSession() { + return session; + } +} \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/AsyncUiEventListener.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/AsyncUiEventListener.java new file mode 100644 index 000000000..b880a63c5 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/AsyncUiEventListener.java @@ -0,0 +1,83 @@ +package org.argeo.eclipse.ui.jcr; + +import java.util.ArrayList; +import java.util.List; + +import javax.jcr.RepositoryException; +import javax.jcr.observation.Event; +import javax.jcr.observation.EventIterator; +import javax.jcr.observation.EventListener; + +import org.argeo.api.cms.CmsLog; +import org.argeo.eclipse.ui.EclipseUiException; +import org.eclipse.swt.widgets.Display; + +/** + * {@link EventListener} which simplifies running actions within the UI thread. + */ +public abstract class AsyncUiEventListener implements EventListener { + // private final static Log logSuper = LogFactory + // .getLog(AsyncUiEventListener.class); + private final CmsLog logThis = CmsLog.getLog(getClass()); + + private final Display display; + + public AsyncUiEventListener(Display display) { + super(); + this.display = display; + } + + /** Called asynchronously in the UI thread. */ + protected abstract void onEventInUiThread(List events) throws RepositoryException; + + /** + * Whether these events should be processed in the UI or skipped with no UI + * job created. + */ + protected Boolean willProcessInUiThread(List events) throws RepositoryException { + return true; + } + + protected CmsLog getLog() { + return logThis; + } + + public final void onEvent(final EventIterator eventIterator) { + final List events = new ArrayList(); + while (eventIterator.hasNext()) + events.add(eventIterator.nextEvent()); + + if (logThis.isTraceEnabled()) + logThis.trace("Received " + events.size() + " events"); + + try { + if (!willProcessInUiThread(events)) + return; + } catch (RepositoryException e) { + throw new EclipseUiException("Cannot test skip events " + events, e); + } + + // Job job = new Job("JCR Events") { + // protected IStatus run(IProgressMonitor monitor) { + // if (display.isDisposed()) { + // logSuper.warn("Display is disposed cannot update UI"); + // return Status.CANCEL_STATUS; + // } + + if (!display.isDisposed()) + display.asyncExec(new Runnable() { + public void run() { + try { + onEventInUiThread(events); + } catch (RepositoryException e) { + throw new EclipseUiException("Cannot process events " + events, e); + } + } + }); + + // return Status.OK_STATUS; + // } + // }; + // job.schedule(); + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/DefaultNodeLabelProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/DefaultNodeLabelProvider.java new file mode 100644 index 000000000..22ffeafc6 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/DefaultNodeLabelProvider.java @@ -0,0 +1,82 @@ +package org.argeo.eclipse.ui.jcr; + +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.RepositoryException; +import javax.jcr.nodetype.NodeType; + +import org.argeo.eclipse.ui.EclipseUiException; +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.swt.graphics.Image; + +/** + * Default label provider to manage node and corresponding UI objects. It + * provides reasonable overwrite-able default for known JCR types. + */ +public class DefaultNodeLabelProvider extends ColumnLabelProvider { + private static final long serialVersionUID = 1216182332792151235L; + + public String getText(Object element) { + try { + if (element instanceof Node) { + return getText((Node) element); + } else if (element instanceof WrappedNode) { + return getText(((WrappedNode) element).getNode()); + } else if (element instanceof NodesWrapper) { + return getText(((NodesWrapper) element).getNode()); + } + return super.getText(element); + } catch (RepositoryException e) { + throw new EclipseUiException("Cannot get text for of " + element, e); + } + } + + protected String getText(Node node) throws RepositoryException { + if (node.isNodeType(NodeType.MIX_TITLE) + && node.hasProperty(Property.JCR_TITLE)) + return node.getProperty(Property.JCR_TITLE).getString(); + else + return node.getName(); + } + + @Override + public Image getImage(Object element) { + try { + if (element instanceof Node) { + return getImage((Node) element); + } else if (element instanceof WrappedNode) { + return getImage(((WrappedNode) element).getNode()); + } else if (element instanceof NodesWrapper) { + return getImage(((NodesWrapper) element).getNode()); + } + } catch (RepositoryException e) { + throw new EclipseUiException("Cannot retrieve image for " + element, e); + } + return super.getImage(element); + } + + protected Image getImage(Node node) throws RepositoryException { + // FIXME who uses that? + return null; + } + + @Override + public String getToolTipText(Object element) { + try { + if (element instanceof Node) { + return getToolTipText((Node) element); + } else if (element instanceof WrappedNode) { + return getToolTipText(((WrappedNode) element).getNode()); + } else if (element instanceof NodesWrapper) { + return getToolTipText(((NodesWrapper) element).getNode()); + } + } catch (RepositoryException e) { + throw new EclipseUiException("Cannot get tooltip for " + element, e); + } + return super.getToolTipText(element); + } + + protected String getToolTipText(Node node) throws RepositoryException { + return null; + } +} \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/EclipseJcrMonitor.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/EclipseJcrMonitor.java new file mode 100644 index 000000000..b83aaa25a --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/EclipseJcrMonitor.java @@ -0,0 +1,44 @@ +package org.argeo.eclipse.ui.jcr; + +import org.argeo.jcr.JcrMonitor; +import org.eclipse.core.runtime.IProgressMonitor; + +/** + * Wraps an Eclipse {@link IProgressMonitor} so that it can be passed to + * framework agnostic Argeo routines. + */ +public class EclipseJcrMonitor implements JcrMonitor { + private final IProgressMonitor progressMonitor; + + public EclipseJcrMonitor(IProgressMonitor progressMonitor) { + this.progressMonitor = progressMonitor; + } + + public void beginTask(String name, int totalWork) { + progressMonitor.beginTask(name, totalWork); + } + + public void done() { + progressMonitor.done(); + } + + public boolean isCanceled() { + return progressMonitor.isCanceled(); + } + + public void setCanceled(boolean value) { + progressMonitor.setCanceled(value); + } + + public void setTaskName(String name) { + progressMonitor.setTaskName(name); + } + + public void subTask(String name) { + progressMonitor.subTask(name); + } + + public void worked(int work) { + progressMonitor.worked(work); + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/JcrUiUtils.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/JcrUiUtils.java new file mode 100644 index 000000000..420154b83 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/JcrUiUtils.java @@ -0,0 +1,149 @@ +package org.argeo.eclipse.ui.jcr; + +import java.util.Calendar; + +import javax.jcr.Node; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; + +import org.argeo.eclipse.ui.EclipseUiException; +import org.argeo.eclipse.ui.jcr.lists.NodeViewerComparator; +import org.argeo.eclipse.ui.jcr.lists.RowViewerComparator; +import org.eclipse.jface.viewers.TableViewer; +import org.eclipse.swt.SWT; +import org.eclipse.swt.events.SelectionAdapter; +import org.eclipse.swt.events.SelectionEvent; +import org.eclipse.swt.widgets.Table; + +/** Utility methods to simplify UI development using SWT (or RWT), jface and JCR. */ +public class JcrUiUtils { + + /** + * Centralizes management of updating property value. Among other to avoid + * infinite loop when the new value is the same as the ones that is already + * stored in JCR. + * + * @return true if the value as changed + */ + public static boolean setJcrProperty(Node node, String propName, + int propertyType, Object value) { + try { + switch (propertyType) { + case PropertyType.STRING: + if ("".equals((String) value) + && (!node.hasProperty(propName) || node + .hasProperty(propName) + && "".equals(node.getProperty(propName) + .getString()))) + // workaround the fact that the Text widget value cannot be + // set to null + return false; + else if (node.hasProperty(propName) + && node.getProperty(propName).getString() + .equals((String) value)) + // nothing changed yet + return false; + else { + node.setProperty(propName, (String) value); + return true; + } + case PropertyType.BOOLEAN: + if (node.hasProperty(propName) + && node.getProperty(propName).getBoolean() == (Boolean) value) + // nothing changed yet + return false; + else { + node.setProperty(propName, (Boolean) value); + return true; + } + case PropertyType.DATE: + if (node.hasProperty(propName) + && node.getProperty(propName).getDate() + .equals((Calendar) value)) + // nothing changed yet + return false; + else { + node.setProperty(propName, (Calendar) value); + return true; + } + case PropertyType.LONG: + Long lgValue = (Long) value; + + if (lgValue == null) + lgValue = 0L; + + if (node.hasProperty(propName) + && node.getProperty(propName).getLong() == lgValue) + // nothing changed yet + return false; + else { + node.setProperty(propName, lgValue); + return true; + } + + default: + throw new EclipseUiException("Unimplemented property save"); + } + } catch (RepositoryException re) { + throw new EclipseUiException("Unexpected error while setting property", + re); + } + } + + /** + * Creates a new selection adapter in order to provide sorting abitily on a + * SWT Table that display a row list + **/ + public static SelectionAdapter getRowSelectionAdapter(final int index, + final int propertyType, final String selectorName, + final String propertyName, final RowViewerComparator comparator, + final TableViewer viewer) { + SelectionAdapter selectionAdapter = new SelectionAdapter() { + private static final long serialVersionUID = -5738918304901437720L; + + @Override + public void widgetSelected(SelectionEvent e) { + Table table = viewer.getTable(); + comparator.setColumn(propertyType, selectorName, propertyName); + int dir = table.getSortDirection(); + if (table.getSortColumn() == table.getColumn(index)) { + dir = dir == SWT.UP ? SWT.DOWN : SWT.UP; + } else { + dir = SWT.DOWN; + } + table.setSortDirection(dir); + table.setSortColumn(table.getColumn(index)); + viewer.refresh(); + } + }; + return selectionAdapter; + } + + /** + * Creates a new selection adapter in order to provide sorting abitily on a + * swt table that display a row list + **/ + public static SelectionAdapter getNodeSelectionAdapter(final int index, + final int propertyType, final String propertyName, + final NodeViewerComparator comparator, final TableViewer viewer) { + SelectionAdapter selectionAdapter = new SelectionAdapter() { + private static final long serialVersionUID = -1683220869195484625L; + + @Override + public void widgetSelected(SelectionEvent e) { + Table table = viewer.getTable(); + comparator.setColumn(propertyType, propertyName); + int dir = table.getSortDirection(); + if (table.getSortColumn() == table.getColumn(index)) { + dir = dir == SWT.UP ? SWT.DOWN : SWT.UP; + } else { + dir = SWT.DOWN; + } + table.setSortDirection(dir); + table.setSortColumn(table.getColumn(index)); + viewer.refresh(); + } + }; + return selectionAdapter; + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeColumnLabelProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeColumnLabelProvider.java new file mode 100644 index 000000000..7e12becd8 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeColumnLabelProvider.java @@ -0,0 +1,123 @@ +package org.argeo.eclipse.ui.jcr; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.swt.graphics.Color; +import org.eclipse.swt.graphics.Font; +import org.eclipse.swt.graphics.Image; + +/** Simplifies writing JCR-based column label provider. */ +public class NodeColumnLabelProvider extends ColumnLabelProvider { + private static final long serialVersionUID = -6586692836928505358L; + + protected String getNodeText(Node node) throws RepositoryException { + return super.getText(node); + } + + protected String getNodeToolTipText(Node node) throws RepositoryException { + return super.getToolTipText(node); + } + + protected Image getNodeImage(Node node) throws RepositoryException { + return super.getImage(node); + } + + protected Font getNodeFont(Node node) throws RepositoryException { + return super.getFont(node); + } + + public Color getNodeBackground(Node node) throws RepositoryException { + return super.getBackground(node); + } + + public Color getNodeForeground(Node node) throws RepositoryException { + return super.getForeground(node); + } + + @Override + public String getText(Object element) { + try { + if (element instanceof Node) + return getNodeText((Node) element); + else if (element instanceof NodeElement) + return getNodeText(((NodeElement) element).getNode()); + else + throw new IllegalArgumentException("Unsupported element type " + element.getClass()); + } catch (RepositoryException e) { + throw new IllegalStateException("Repository exception when accessing " + element, e); + } + } + + @Override + public Image getImage(Object element) { + try { + if (element instanceof Node) + return getNodeImage((Node) element); + else if (element instanceof NodeElement) + return getNodeImage(((NodeElement) element).getNode()); + else + throw new IllegalArgumentException("Unsupported element type " + element.getClass()); + } catch (RepositoryException e) { + throw new IllegalStateException("Repository exception when accessing " + element, e); + } + } + + @Override + public String getToolTipText(Object element) { + try { + if (element instanceof Node) + return getNodeToolTipText((Node) element); + else if (element instanceof NodeElement) + return getNodeToolTipText(((NodeElement) element).getNode()); + else + throw new IllegalArgumentException("Unsupported element type " + element.getClass()); + } catch (RepositoryException e) { + throw new IllegalStateException("Repository exception when accessing " + element, e); + } + } + + @Override + public Font getFont(Object element) { + try { + if (element instanceof Node) + return getNodeFont((Node) element); + else if (element instanceof NodeElement) + return getNodeFont(((NodeElement) element).getNode()); + else + throw new IllegalArgumentException("Unsupported element type " + element.getClass()); + } catch (RepositoryException e) { + throw new IllegalStateException("Repository exception when accessing " + element, e); + } + } + + @Override + public Color getBackground(Object element) { + try { + if (element instanceof Node) + return getNodeBackground((Node) element); + else if (element instanceof NodeElement) + return getNodeBackground(((NodeElement) element).getNode()); + else + throw new IllegalArgumentException("Unsupported element type " + element.getClass()); + } catch (RepositoryException e) { + throw new IllegalStateException("Repository exception when accessing " + element, e); + } + } + + @Override + public Color getForeground(Object element) { + try { + if (element instanceof Node) + return getNodeForeground((Node) element); + else if (element instanceof NodeElement) + return getNodeForeground(((NodeElement) element).getNode()); + else + throw new IllegalArgumentException("Unsupported element type " + element.getClass()); + } catch (RepositoryException e) { + throw new IllegalStateException("Repository exception when accessing " + element, e); + } + } + +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeElement.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeElement.java new file mode 100644 index 000000000..787c92ed5 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeElement.java @@ -0,0 +1,8 @@ +package org.argeo.eclipse.ui.jcr; + +import javax.jcr.Node; + +/** An element which is related to a JCR {@link Node}. */ +public interface NodeElement { + Node getNode(); +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeElementComparer.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeElementComparer.java new file mode 100644 index 000000000..2f3d64d6b --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeElementComparer.java @@ -0,0 +1,36 @@ +package org.argeo.eclipse.ui.jcr; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.eclipse.ui.EclipseUiException; +import org.eclipse.jface.viewers.IElementComparer; + +/** Element comparer for JCR node, to be used in JFace viewers. */ +public class NodeElementComparer implements IElementComparer { + + public boolean equals(Object a, Object b) { + try { + if ((a instanceof Node) && (b instanceof Node)) { + Node nodeA = (Node) a; + Node nodeB = (Node) b; + return nodeA.getIdentifier().equals(nodeB.getIdentifier()); + } else { + return a.equals(b); + } + } catch (RepositoryException e) { + throw new EclipseUiException("Cannot compare nodes", e); + } + } + + public int hashCode(Object element) { + try { + if (element instanceof Node) + return ((Node) element).getIdentifier().hashCode(); + return element.hashCode(); + } catch (RepositoryException e) { + throw new EclipseUiException("Cannot get hash code", e); + } + } + +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodesWrapper.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodesWrapper.java new file mode 100644 index 000000000..2f808a51f --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodesWrapper.java @@ -0,0 +1,71 @@ +package org.argeo.eclipse.ui.jcr; + +import java.util.ArrayList; +import java.util.List; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.RepositoryException; + +import org.argeo.eclipse.ui.EclipseUiException; + +/** + * Element of tree which is based on a node, but whose children are not + * necessarily this node children. + */ +public class NodesWrapper { + private final Node node; + + public NodesWrapper(Node node) { + super(); + this.node = node; + } + + protected NodeIterator getNodeIterator() throws RepositoryException { + return node.getNodes(); + } + + protected List getWrappedNodes() throws RepositoryException { + List nodes = new ArrayList(); + for (NodeIterator nit = getNodeIterator(); nit.hasNext();) + nodes.add(new WrappedNode(this, nit.nextNode())); + return nodes; + } + + public Object[] getChildren() { + try { + return getWrappedNodes().toArray(); + } catch (RepositoryException e) { + throw new EclipseUiException("Cannot get wrapped children", e); + } + } + + /** + * @return true by default because we don't want to compute the wrapped + * nodes twice + */ + public Boolean hasChildren() { + return true; + } + + public Node getNode() { + return node; + } + + @Override + public int hashCode() { + return node.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof NodesWrapper) + return node.equals(((NodesWrapper) obj).getNode()); + else + return false; + } + + public String toString() { + return "nodes wrapper based on " + node; + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/QueryTableContentProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/QueryTableContentProvider.java new file mode 100644 index 000000000..934fa6781 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/QueryTableContentProvider.java @@ -0,0 +1,35 @@ +package org.argeo.eclipse.ui.jcr; + +import javax.jcr.NodeIterator; +import javax.jcr.RepositoryException; +import javax.jcr.query.Query; + +import org.argeo.jcr.JcrException; +import org.argeo.jcr.JcrUtils; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.Viewer; + +/** Content provider based on a JCR {@link Query}. */ +public class QueryTableContentProvider implements IStructuredContentProvider { + private static final long serialVersionUID = 760371460907204722L; + + @Override + public void dispose() { + } + + @Override + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + + @Override + public Object[] getElements(Object inputElement) { + Query query = (Query) inputElement; + try { + NodeIterator nit = query.execute().getNodes(); + return JcrUtils.nodeIteratorToList(nit).toArray(); + } catch (RepositoryException e) { + throw new JcrException(e); + } + } + +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/SimpleNodeContentProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/SimpleNodeContentProvider.java new file mode 100644 index 000000000..cb235d76b --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/SimpleNodeContentProvider.java @@ -0,0 +1,59 @@ +package org.argeo.eclipse.ui.jcr; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.argeo.eclipse.ui.EclipseUiException; +import org.argeo.jcr.JcrUtils; + +/** Simple JCR node content provider taking a list of String as base path. */ +public class SimpleNodeContentProvider extends AbstractNodeContentProvider { + private static final long serialVersionUID = -8245193308831384269L; + private final List basePaths; + private Boolean mkdirs = false; + + public SimpleNodeContentProvider(Session session, String... basePaths) { + this(session, Arrays.asList(basePaths)); + } + + public SimpleNodeContentProvider(Session session, List basePaths) { + super(session); + this.basePaths = basePaths; + } + + @Override + protected Boolean isBasePath(String path) { + if (basePaths.contains(path)) + return true; + return super.isBasePath(path); + } + + public Object[] getElements(Object inputElement) { + try { + List baseNodes = new ArrayList(); + for (String basePath : basePaths) + if (mkdirs && !getSession().itemExists(basePath)) + baseNodes.add(JcrUtils.mkdirs(getSession(), basePath)); + else + baseNodes.add(getSession().getNode(basePath)); + return baseNodes.toArray(); + } catch (RepositoryException e) { + throw new EclipseUiException("Cannot get base nodes for " + basePaths, + e); + } + } + + public List getBasePaths() { + return basePaths; + } + + public void setMkdirs(Boolean mkdirs) { + this.mkdirs = mkdirs; + } + +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/VersionColumnLabelProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/VersionColumnLabelProvider.java new file mode 100644 index 000000000..1ce315429 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/VersionColumnLabelProvider.java @@ -0,0 +1,80 @@ +package org.argeo.eclipse.ui.jcr; + +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.RepositoryException; +import javax.jcr.version.Version; + +import org.eclipse.jface.viewers.ColumnLabelProvider; +import org.eclipse.swt.graphics.Image; + +/** Simplifies writing JCR-based column label provider. */ +public class VersionColumnLabelProvider extends ColumnLabelProvider { + private static final long serialVersionUID = -6117690082313161159L; + + protected String getVersionText(Version version) throws RepositoryException { + return super.getText(version); + } + + protected String getVersionToolTipText(Version version) throws RepositoryException { + return super.getToolTipText(version); + } + + protected Image getVersionImage(Version version) throws RepositoryException { + return super.getImage(version); + } + + protected String getUserName(Version version) throws RepositoryException { + Node node = version.getFrozenNode(); + if(node.hasProperty(Property.JCR_LAST_MODIFIED_BY)) + return node.getProperty(Property.JCR_LAST_MODIFIED_BY).getString(); + if(node.hasProperty(Property.JCR_CREATED_BY)) + return node.getProperty(Property.JCR_CREATED_BY).getString(); + return null; + } + +// protected String getActivityTitle(Version version) throws RepositoryException { +// Node activity = getActivity(version); +// if (activity == null) +// return null; +// if (activity.hasProperty("jcr:activityTitle")) +// return activity.getProperty("jcr:activityTitle").getString(); +// else +// return activity.getName(); +// } +// +// protected Node getActivity(Version version) throws RepositoryException { +// if (version.hasProperty(Property.JCR_ACTIVITY)) { +// return version.getProperty(Property.JCR_ACTIVITY).getNode(); +// } else +// return null; +// } + + @Override + public String getText(Object element) { + try { + return getVersionText((Version) element); + } catch (RepositoryException e) { + throw new RuntimeException("Runtime repository exception when accessing " + element, e); + } + } + + @Override + public Image getImage(Object element) { + try { + return getVersionImage((Version) element); + } catch (RepositoryException e) { + throw new RuntimeException("Runtime repository exception when accessing " + element, e); + } + } + + @Override + public String getToolTipText(Object element) { + try { + return getVersionToolTipText((Version) element); + } catch (RepositoryException e) { + throw new RuntimeException("Runtime repository exception when accessing " + element, e); + } + } + +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/VersionHistoryContentProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/VersionHistoryContentProvider.java new file mode 100644 index 000000000..32e5d30c1 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/VersionHistoryContentProvider.java @@ -0,0 +1,27 @@ +package org.argeo.eclipse.ui.jcr; + +import javax.jcr.version.VersionHistory; + +import org.argeo.jcr.Jcr; +import org.eclipse.jface.viewers.IStructuredContentProvider; +import org.eclipse.jface.viewers.Viewer; + +/** Content provider based on a {@link VersionHistory}. */ +public class VersionHistoryContentProvider implements IStructuredContentProvider { + private static final long serialVersionUID = -4921107883428887012L; + + @Override + public void dispose() { + } + + @Override + public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { + } + + @Override + public Object[] getElements(Object inputElement) { + VersionHistory versionHistory = (VersionHistory) inputElement; + return Jcr.getLinearVersions(versionHistory).toArray(); + } + +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/WrappedNode.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/WrappedNode.java new file mode 100644 index 000000000..43df1fe01 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/WrappedNode.java @@ -0,0 +1,41 @@ +package org.argeo.eclipse.ui.jcr; + +import javax.jcr.Node; + +/** Wrap a node (created from a {@link NodesWrapper}) */ +public class WrappedNode { + private final NodesWrapper parent; + private final Node node; + + public WrappedNode(NodesWrapper parent, Node node) { + super(); + this.parent = parent; + this.node = node; + } + + public NodesWrapper getParent() { + return parent; + } + + public Node getNode() { + return node; + } + + public String toString() { + return "wrapped " + node; + } + + @Override + public int hashCode() { + return node.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (obj instanceof WrappedNode) + return node.equals(((WrappedNode) obj).getNode()); + else + return false; + } + +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/JcrColumnDefinition.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/JcrColumnDefinition.java new file mode 100644 index 000000000..c5dd73317 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/JcrColumnDefinition.java @@ -0,0 +1,116 @@ +package org.argeo.eclipse.ui.jcr.lists; + +import javax.jcr.Node; +import javax.jcr.query.Row; + +import org.argeo.eclipse.ui.ColumnDefinition; + +/** + * Utility object to manage column in various tables and extracts displaying + * data from JCR + */ +public class JcrColumnDefinition extends ColumnDefinition { + private final static int DEFAULT_COLUMN_SIZE = 120; + + private String selectorName; + private String propertyName; + private int propertyType; + private int columnSize; + + /** + * Use this kind of columns to configure a table that displays JCR + * {@link Row} + * + * @param selectorName + * @param propertyName + * @param propertyType + * @param headerLabel + */ + public JcrColumnDefinition(String selectorName, String propertyName, + int propertyType, String headerLabel) { + super(new SimpleJcrRowLabelProvider(selectorName, propertyName), + headerLabel); + this.selectorName = selectorName; + this.propertyName = propertyName; + this.propertyType = propertyType; + this.columnSize = DEFAULT_COLUMN_SIZE; + } + + /** + * Use this kind of columns to configure a table that displays JCR + * {@link Row} + * + * @param selectorName + * @param propertyName + * @param propertyType + * @param headerLabel + * @param columnSize + */ + public JcrColumnDefinition(String selectorName, String propertyName, + int propertyType, String headerLabel, int columnSize) { + super(new SimpleJcrRowLabelProvider(selectorName, propertyName), + headerLabel, columnSize); + this.selectorName = selectorName; + this.propertyName = propertyName; + this.propertyType = propertyType; + this.columnSize = columnSize; + } + + /** + * Use this kind of columns to configure a table that displays JCR + * {@link Node} + * + * @param propertyName + * @param propertyType + * @param headerLabel + * @param columnSize + */ + public JcrColumnDefinition(String propertyName, int propertyType, + String headerLabel, int columnSize) { + super(new SimpleJcrNodeLabelProvider(propertyName), headerLabel, + columnSize); + this.propertyName = propertyName; + this.propertyType = propertyType; + this.columnSize = columnSize; + } + + public String getSelectorName() { + return selectorName; + } + + public void setSelectorName(String selectorName) { + this.selectorName = selectorName; + } + + public String getPropertyName() { + return propertyName; + } + + public void setPropertyName(String propertyName) { + this.propertyName = propertyName; + } + + public int getPropertyType() { + return propertyType; + } + + public void setPropertyType(int propertyType) { + this.propertyType = propertyType; + } + + public int getColumnSize() { + return columnSize; + } + + public void setColumnSize(int columnSize) { + this.columnSize = columnSize; + } + + public String getHeaderLabel() { + return super.getLabel(); + } + + public void setHeaderLabel(String headerLabel) { + super.setLabel(headerLabel); + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/NodeViewerComparator.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/NodeViewerComparator.java new file mode 100644 index 000000000..341b3abee --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/NodeViewerComparator.java @@ -0,0 +1,190 @@ +package org.argeo.eclipse.ui.jcr.lists; + +import java.math.BigDecimal; +import java.util.Calendar; + +import javax.jcr.Node; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; +import javax.jcr.Value; +import javax.jcr.ValueFormatException; + +import org.argeo.eclipse.ui.EclipseUiException; +import org.eclipse.jface.viewers.Viewer; +import org.eclipse.jface.viewers.ViewerComparator; + +/** + * Base comparator to enable ordering on Table or Tree viewer that display Jcr + * Nodes. + * + * Note that the following snippet must be added before setting the comparator + * to the corresponding control: + * // IMPORTANT: initialize comparator before setting it + * JcrColumnDefinition firstCol = colDefs.get(0); + * comparator.setColumn(firstCol.getPropertyType(), + * firstCol.getPropertyName()); + * viewer.setComparator(comparator); + */ +public class NodeViewerComparator extends ViewerComparator { + private static final long serialVersionUID = -7782916140737279027L; + + protected String propertyName; + + protected int propertyType; + public static final int ASCENDING = 0, DESCENDING = 1; + protected int direction = DESCENDING; + + public NodeViewerComparator() { + } + + /** + * e1 and e2 must both be Jcr nodes. + * + * @param viewer + * @param e1 + * @param e2 + * @return + */ + @Override + public int compare(Viewer viewer, Object e1, Object e2) { + int rc = 0; + long lc = 0; + + try { + Node n1 = (Node) e1; + Node n2 = (Node) e2; + + Value v1 = null; + Value v2 = null; + if (n1.hasProperty(propertyName)) + v1 = n1.getProperty(propertyName).getValue(); + if (n2.hasProperty(propertyName)) + v2 = n2.getProperty(propertyName).getValue(); + + if (v2 == null && v1 == null) + return 0; + else if (v2 == null) + return -1; + else if (v1 == null) + return 1; + + switch (propertyType) { + case PropertyType.STRING: + rc = v1.getString().compareTo(v2.getString()); + break; + case PropertyType.BOOLEAN: + boolean b1 = v1.getBoolean(); + boolean b2 = v2.getBoolean(); + if (b1 == b2) + rc = 0; + else + // we assume true is greater than false + rc = b1 ? 1 : -1; + break; + case PropertyType.DATE: + Calendar c1 = v1.getDate(); + Calendar c2 = v2.getDate(); + if (c1 == null || c2 == null) + // log.trace("undefined date"); + ; + lc = c1.getTimeInMillis() - c2.getTimeInMillis(); + if (lc < Integer.MIN_VALUE) + rc = -1; + else if (lc > Integer.MAX_VALUE) + rc = 1; + else + rc = (int) lc; + break; + case PropertyType.LONG: + long l1; + long l2; + // TODO Sometimes an empty string is set instead of a long + try { + l1 = v1.getLong(); + } catch (ValueFormatException ve) { + l1 = 0; + } + try { + l2 = v2.getLong(); + } catch (ValueFormatException ve) { + l2 = 0; + } + + lc = l1 - l2; + if (lc < Integer.MIN_VALUE) + rc = -1; + else if (lc > Integer.MAX_VALUE) + rc = 1; + else + rc = (int) lc; + break; + case PropertyType.DECIMAL: + BigDecimal bd1 = v1.getDecimal(); + BigDecimal bd2 = v2.getDecimal(); + rc = bd1.compareTo(bd2); + break; + case PropertyType.DOUBLE: + Double d1 = v1.getDouble(); + Double d2 = v2.getDouble(); + rc = d1.compareTo(d2); + break; + default: + throw new EclipseUiException( + "Unimplemented comparaison for PropertyType " + + propertyType); + } + // If descending order, flip the direction + if (direction == DESCENDING) { + rc = -rc; + } + + } catch (RepositoryException re) { + throw new EclipseUiException("Unexpected error " + + "while comparing nodes", re); + } + return rc; + } + + /** + * @param propertyType + * Corresponding JCR type + * @param propertyName + * name of the property to use. + */ + public void setColumn(int propertyType, String propertyName) { + if (this.propertyName != null && this.propertyName.equals(propertyName)) { + // Same column as last sort; toggle the direction + direction = 1 - direction; + } else { + // New column; do an ascending sort + this.propertyType = propertyType; + this.propertyName = propertyName; + direction = ASCENDING; + } + } + + // Getters and setters + protected String getPropertyName() { + return propertyName; + } + + protected void setPropertyName(String propertyName) { + this.propertyName = propertyName; + } + + protected int getPropertyType() { + return propertyType; + } + + protected void setPropertyType(int propertyType) { + this.propertyType = propertyType; + } + + protected int getDirection() { + return direction; + } + + protected void setDirection(int direction) { + this.direction = direction; + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/RowViewerComparator.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/RowViewerComparator.java new file mode 100644 index 000000000..455fb0dcd --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/RowViewerComparator.java @@ -0,0 +1,62 @@ +package org.argeo.eclipse.ui.jcr.lists; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.query.Row; + +import org.argeo.eclipse.ui.EclipseUiException; +import org.eclipse.jface.viewers.Viewer; + +/** + * Base comparator to enable ordering on Table or Tree viewer that display Jcr + * rows + */ +public class RowViewerComparator extends NodeViewerComparator { + private static final long serialVersionUID = 7020939505172625113L; + protected String selectorName; + + public RowViewerComparator() { + } + + /** + * e1 and e2 must both be Jcr rows. + * + * @param viewer + * @param e1 + * @param e2 + * @return + */ + @Override + public int compare(Viewer viewer, Object e1, Object e2) { + try { + Node n1 = ((Row) e1).getNode(selectorName); + Node n2 = ((Row) e2).getNode(selectorName); + return super.compare(viewer, n1, n2); + } catch (RepositoryException re) { + throw new EclipseUiException("Unexpected error " + + "while comparing nodes", re); + } + } + + /** + * @param propertyType + * Corresponding JCR type + * @param propertyName + * name of the property to use. + */ + public void setColumn(int propertyType, String selectorName, + String propertyName) { + if (this.selectorName != null && getPropertyName() != null + && this.selectorName.equals(selectorName) + && this.getPropertyName().equals(propertyName)) { + // Same column as last sort; toggle the direction + setDirection(1 - getDirection()); + } else { + // New column; do a descending sort + setPropertyType(propertyType); + setPropertyName(propertyName); + this.selectorName = selectorName; + setDirection(NodeViewerComparator.ASCENDING); + } + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/SimpleJcrNodeLabelProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/SimpleJcrNodeLabelProvider.java new file mode 100644 index 000000000..aa2e3375c --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/SimpleJcrNodeLabelProvider.java @@ -0,0 +1,120 @@ +package org.argeo.eclipse.ui.jcr.lists; + +import java.text.DateFormat; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.text.SimpleDateFormat; + +import javax.jcr.Node; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; +import javax.jcr.Value; + +import org.argeo.eclipse.ui.EclipseUiException; +import org.eclipse.jface.viewers.ColumnLabelProvider; + +/** Base implementation of a label provider for controls that display JCR Nodes */ +public class SimpleJcrNodeLabelProvider extends ColumnLabelProvider { + private static final long serialVersionUID = -5215787695436221993L; + + private final static String DEFAULT_DATE_FORMAT = "EEE, dd MMM yyyy"; + private final static String DEFAULT_NUMBER_FORMAT = "#,##0.0"; + + private DateFormat dateFormat; + private NumberFormat numberFormat; + + final private String propertyName; + + /** + * Default Label provider for a given property of a node. Using default + * pattern for date and number formating + */ + public SimpleJcrNodeLabelProvider(String propertyName) { + this.propertyName = propertyName; + dateFormat = new SimpleDateFormat(DEFAULT_DATE_FORMAT); + numberFormat = DecimalFormat.getInstance(); + ((DecimalFormat) numberFormat).applyPattern(DEFAULT_NUMBER_FORMAT); + } + + /** + * Label provider for a given property of a node optionally precising date + * and/or number format patterns + */ + public SimpleJcrNodeLabelProvider(String propertyName, + String dateFormatPattern, String numberFormatPattern) { + this.propertyName = propertyName; + dateFormat = new SimpleDateFormat( + dateFormatPattern == null ? DEFAULT_DATE_FORMAT + : dateFormatPattern); + numberFormat = DecimalFormat.getInstance(); + ((DecimalFormat) numberFormat) + .applyPattern(numberFormatPattern == null ? DEFAULT_NUMBER_FORMAT + : numberFormatPattern); + } + + @Override + public String getText(Object element) { + try { + Node currNode = (Node) element; + + if (currNode.hasProperty(propertyName)) { + if (currNode.getProperty(propertyName).isMultiple()) { + StringBuilder builder = new StringBuilder(); + for (Value value : currNode.getProperty(propertyName) + .getValues()) { + String currStr = getSingleValueAsString(value); + if (notEmptyString(currStr)) + builder.append(currStr).append("; "); + } + if (builder.length() > 0) + builder.deleteCharAt(builder.length() - 2); + + return builder.toString(); + } else + return getSingleValueAsString(currNode.getProperty( + propertyName).getValue()); + } else + return ""; + } catch (RepositoryException re) { + throw new EclipseUiException("Unable to get text from row", re); + } + } + + private String getSingleValueAsString(Value value) + throws RepositoryException { + switch (value.getType()) { + case PropertyType.STRING: + return value.getString(); + case PropertyType.BOOLEAN: + return "" + value.getBoolean(); + case PropertyType.DATE: + return dateFormat.format(value.getDate().getTime()); + case PropertyType.LONG: + return "" + value.getLong(); + case PropertyType.DECIMAL: + return numberFormat.format(value.getDecimal()); + case PropertyType.DOUBLE: + return numberFormat.format(value.getDouble()); + case PropertyType.NAME: + return value.getString(); + default: + throw new EclipseUiException("Unimplemented label provider " + + "for property type " + value.getType() + + " while getting property " + propertyName + " - value: " + + value.getString()); + + } + } + + private boolean notEmptyString(String string) { + return string != null && !"".equals(string.trim()); + } + + public void setDateFormat(String dateFormatPattern) { + dateFormat = new SimpleDateFormat(dateFormatPattern); + } + + public void setNumberFormat(String numberFormatPattern) { + ((DecimalFormat) numberFormat).applyPattern(numberFormatPattern); + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/SimpleJcrRowLabelProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/SimpleJcrRowLabelProvider.java new file mode 100644 index 000000000..5d421f64b --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/SimpleJcrRowLabelProvider.java @@ -0,0 +1,47 @@ +package org.argeo.eclipse.ui.jcr.lists; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; +import javax.jcr.query.Row; + +import org.argeo.eclipse.ui.EclipseUiException; + +/** + * Base implementation of a label provider for widgets that display JCR Rows. + */ +public class SimpleJcrRowLabelProvider extends SimpleJcrNodeLabelProvider { + private static final long serialVersionUID = -3414654948197181740L; + + final private String selectorName; + + /** + * Default Label provider for a given property of a row. Using default + * pattern for date and number formating + */ + public SimpleJcrRowLabelProvider(String selectorName, String propertyName) { + super(propertyName); + this.selectorName = selectorName; + } + + /** + * Label provider for a given property of a node optionally precising date + * and/or number format patterns + */ + public SimpleJcrRowLabelProvider(String selectorName, String propertyName, + String dateFormatPattern, String numberFormatPattern) { + super(propertyName, dateFormatPattern, numberFormatPattern); + this.selectorName = selectorName; + } + + @Override + public String getText(Object element) { + try { + Row currRow = (Row) element; + Node currNode = currRow.getNode(selectorName); + return super.getText(currNode); + } catch (RepositoryException re) { + throw new EclipseUiException("Unable to get Node " + selectorName + + " from row " + element, re); + } + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/package-info.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/package-info.java new file mode 100644 index 000000000..3678aab88 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/package-info.java @@ -0,0 +1,2 @@ +/** Generic SWT/JFace JCR utilities for lists. */ +package org.argeo.eclipse.ui.jcr.lists; \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/package-info.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/package-info.java new file mode 100644 index 000000000..19e3cc3ff --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/package-info.java @@ -0,0 +1,2 @@ +/** Generic SWT/JFace JCR utilities. */ +package org.argeo.eclipse.ui.jcr; \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/JcrFileProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/JcrFileProvider.java new file mode 100644 index 000000000..c82e666d1 --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/JcrFileProvider.java @@ -0,0 +1,129 @@ +package org.argeo.eclipse.ui.jcr.util; + +import java.io.InputStream; + +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.RepositoryException; +import javax.jcr.nodetype.NodeType; + +import org.apache.commons.io.IOUtils; +import org.argeo.eclipse.ui.EclipseUiException; +import org.argeo.eclipse.ui.FileProvider; + +/** + * Implements a FileProvider for UI purposes. Note that it might not be very + * reliable as long as we have not fixed login and multi repository issues that + * will be addressed in the next version. + * + * NOTE: id used here is the real id of the JCR Node, not the JCR Path + * + * Relies on common approach for JCR file handling implementation. + * + */ +@SuppressWarnings("deprecation") +public class JcrFileProvider implements FileProvider { + + // private Object[] rootNodes; + private Node refNode; + + /** + * Must be set in order for the provider to be able to get current session + * and thus have the ability to get the file node corresponding to a given + * file ID + * + * @param refNode + */ + public void setReferenceNode(Node refNode) { + // FIXME : this introduces some concurrency ISSUES. + this.refNode = refNode; + } + + public byte[] getByteArrayFileFromId(String fileId) { + InputStream fis = null; + byte[] ba = null; + Node child = getFileNodeFromId(fileId); + try { + fis = (InputStream) child.getProperty(Property.JCR_DATA).getBinary().getStream(); + ba = IOUtils.toByteArray(fis); + + } catch (Exception e) { + throw new EclipseUiException("Stream error while opening file", e); + } finally { + IOUtils.closeQuietly(fis); + } + return ba; + } + + public InputStream getInputStreamFromFileId(String fileId) { + try { + InputStream fis = null; + + Node child = getFileNodeFromId(fileId); + fis = (InputStream) child.getProperty(Property.JCR_DATA).getBinary().getStream(); + return fis; + } catch (RepositoryException re) { + throw new EclipseUiException("Cannot get stream from file node for Id " + fileId, re); + } + } + + /** + * Throws an exception if the node is not found in the current repository (a + * bit like a FileNotFoundException) + * + * @param fileId + * @return Returns the child node of the nt:file node. It is the child node + * that have the jcr:data property where actual file is stored. + * never null + */ + private Node getFileNodeFromId(String fileId) { + try { + Node result = refNode.getSession().getNodeByIdentifier(fileId); + + // rootNodes: for (int j = 0; j < rootNodes.length; j++) { + // // in case we have a classic JCR Node + // if (rootNodes[j] instanceof Node) { + // Node curNode = (Node) rootNodes[j]; + // if (result != null) + // break rootNodes; + // } // Case of a repository Node + // else if (rootNodes[j] instanceof RepositoryNode) { + // Object[] nodes = ((RepositoryNode) rootNodes[j]) + // .getChildren(); + // for (int i = 0; i < nodes.length; i++) { + // Node node = (Node) nodes[i]; + // result = node.getSession().getNodeByIdentifier(fileId); + // if (result != null) + // break rootNodes; + // } + // } + // } + + // Sanity checks + if (result == null) + throw new EclipseUiException("File node not found for ID" + fileId); + + Node child = null; + + boolean isValid = true; + if (!result.isNodeType(NodeType.NT_FILE)) + // useless: mandatory child node + // || !result.hasNode(Property.JCR_CONTENT)) + isValid = false; + else { + child = result.getNode(Property.JCR_CONTENT); + if (!(child.isNodeType(NodeType.NT_RESOURCE) || child.hasProperty(Property.JCR_DATA))) + isValid = false; + } + + if (!isValid) + throw new EclipseUiException("ERROR: In the current implemented model, '" + NodeType.NT_FILE + + "' file node must have a child node named jcr:content " + + "that has a BINARY Property named jcr:data " + "where the actual data is stored"); + return child; + + } catch (RepositoryException re) { + throw new EclipseUiException("Erreur while getting file node of ID " + fileId, re); + } + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/JcrItemsComparator.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/JcrItemsComparator.java new file mode 100644 index 000000000..fb123992f --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/JcrItemsComparator.java @@ -0,0 +1,21 @@ +package org.argeo.eclipse.ui.jcr.util; + +import java.util.Comparator; + +import javax.jcr.Item; +import javax.jcr.RepositoryException; + +import org.argeo.eclipse.ui.EclipseUiException; + +/** Compares two JCR items (node or properties) based on their names. */ +public class JcrItemsComparator implements Comparator { + public int compare(Item o1, Item o2) { + try { + // TODO: put folder before files + return o1.getName().toLowerCase().compareTo(o2.getName().toLowerCase()); + } catch (RepositoryException e) { + throw new EclipseUiException("Cannot compare " + o1 + " and " + o2, e); + } + } + +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/NodeViewerComparer.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/NodeViewerComparer.java new file mode 100644 index 000000000..54b795f3b --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/NodeViewerComparer.java @@ -0,0 +1,36 @@ +package org.argeo.eclipse.ui.jcr.util; + +import javax.jcr.Node; +import javax.jcr.RepositoryException; + +import org.argeo.eclipse.ui.EclipseUiException; +import org.eclipse.jface.viewers.IElementComparer; + +/** Compare JCR nodes based on their JCR identifiers, for use in JFace viewers. */ +public class NodeViewerComparer implements IElementComparer { + + // force comparison on Node IDs only. + public boolean equals(Object elementA, Object elementB) { + if (!(elementA instanceof Node) || !(elementB instanceof Node)) { + return elementA == null ? elementB == null : elementA + .equals(elementB); + } else { + + boolean result = false; + try { + String idA = ((Node) elementA).getIdentifier(); + String idB = ((Node) elementB).getIdentifier(); + result = idA == null ? idB == null : idA.equals(idB); + } catch (RepositoryException re) { + throw new EclipseUiException("cannot compare nodes", re); + } + + return result; + } + } + + public int hashCode(Object element) { + // TODO enhanced this method. + return element.getClass().toString().hashCode(); + } +} \ No newline at end of file diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/SingleSessionFileProvider.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/SingleSessionFileProvider.java new file mode 100644 index 000000000..291d579ac --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/SingleSessionFileProvider.java @@ -0,0 +1,98 @@ +package org.argeo.eclipse.ui.jcr.util; + +import java.io.InputStream; + +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.nodetype.NodeType; + +import org.apache.commons.io.IOUtils; +import org.argeo.eclipse.ui.EclipseUiException; +import org.argeo.eclipse.ui.FileProvider; + +/** + * Implements a FileProvider for UI purposes. Unlike the + * JcrFileProvider , it relies on a single session and manages + * nodes with path only. + * + * Note that considered id is the JCR path + * + * Relies on common approach for JCR file handling implementation. + */ +@SuppressWarnings("deprecation") +public class SingleSessionFileProvider implements FileProvider { + + private Session session; + + public SingleSessionFileProvider(Session session) { + this.session = session; + } + + public byte[] getByteArrayFileFromId(String fileId) { + InputStream fis = null; + byte[] ba = null; + Node child = getFileNodeFromId(fileId); + try { + fis = (InputStream) child.getProperty(Property.JCR_DATA) + .getBinary().getStream(); + ba = IOUtils.toByteArray(fis); + + } catch (Exception e) { + throw new EclipseUiException("Stream error while opening file", e); + } finally { + IOUtils.closeQuietly(fis); + } + return ba; + } + + public InputStream getInputStreamFromFileId(String fileId) { + try { + InputStream fis = null; + + Node child = getFileNodeFromId(fileId); + fis = (InputStream) child.getProperty(Property.JCR_DATA) + .getBinary().getStream(); + return fis; + } catch (RepositoryException re) { + throw new EclipseUiException("Cannot get stream from file node for Id " + + fileId, re); + } + } + + /** + * + * @param fileId + * @return Returns the child node of the nt:file node. It is the child node + * that have the jcr:data property where actual file is stored. + * never null + */ + private Node getFileNodeFromId(String fileId) { + try { + Node result = null; + result = session.getNode(fileId); + + // Sanity checks + if (result == null) + throw new EclipseUiException("File node not found for ID" + fileId); + + // Ensure that the node have the correct type. + if (!result.isNodeType(NodeType.NT_FILE)) + throw new EclipseUiException( + "Cannot open file children Node that are not of " + + NodeType.NT_RESOURCE + " type."); + + Node child = result.getNodes().nextNode(); + if (child == null || !child.isNodeType(NodeType.NT_RESOURCE)) + throw new EclipseUiException( + "ERROR: IN the current implemented model, " + + NodeType.NT_FILE + + " file node must have one and only one child of the nt:ressource, where actual data is stored"); + return child; + } catch (RepositoryException re) { + throw new EclipseUiException("Erreur while getting file node of ID " + + fileId, re); + } + } +} diff --git a/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/package-info.java b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/package-info.java new file mode 100644 index 000000000..016348cda --- /dev/null +++ b/jcr/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/package-info.java @@ -0,0 +1,2 @@ +/** Generic SWT/JFace JCR helpers. */ +package org.argeo.eclipse.ui.jcr.util; \ No newline at end of file diff --git a/jcr/pom.xml b/jcr/pom.xml new file mode 100644 index 000000000..74dcc7df5 --- /dev/null +++ b/jcr/pom.xml @@ -0,0 +1,20 @@ + + + 4.0.0 + + org.argeo.commons + argeo-commons + 2.3-SNAPSHOT + .. + + org.argeo.commons + jcr + JCR + pom + + org.argeo.cms.jcr + org.argeo.cms.ui + + \ No newline at end of file diff --git a/org.argeo.cms.e4/.classpath b/org.argeo.cms.e4/.classpath deleted file mode 100644 index e801ebfb4..000000000 --- a/org.argeo.cms.e4/.classpath +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/org.argeo.cms.e4/.project b/org.argeo.cms.e4/.project deleted file mode 100644 index 0c0406952..000000000 --- a/org.argeo.cms.e4/.project +++ /dev/null @@ -1,33 +0,0 @@ - - - org.argeo.cms.e4 - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.pde.ManifestBuilder - - - - - org.eclipse.pde.SchemaBuilder - - - - - org.eclipse.pde.ds.core.builder - - - - - - org.eclipse.pde.PluginNature - org.eclipse.jdt.core.javanature - - diff --git a/org.argeo.cms.e4/META-INF/.gitignore b/org.argeo.cms.e4/META-INF/.gitignore deleted file mode 100644 index 4854a41b9..000000000 --- a/org.argeo.cms.e4/META-INF/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/MANIFEST.MF diff --git a/org.argeo.cms.e4/OSGI-INF/defaultCallbackHandler.xml b/org.argeo.cms.e4/OSGI-INF/defaultCallbackHandler.xml deleted file mode 100644 index fcd3ae5cb..000000000 --- a/org.argeo.cms.e4/OSGI-INF/defaultCallbackHandler.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/org.argeo.cms.e4/OSGI-INF/homeRepository.xml b/org.argeo.cms.e4/OSGI-INF/homeRepository.xml deleted file mode 100644 index 65690f262..000000000 --- a/org.argeo.cms.e4/OSGI-INF/homeRepository.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/org.argeo.cms.e4/OSGI-INF/userAdminWrapper.xml b/org.argeo.cms.e4/OSGI-INF/userAdminWrapper.xml deleted file mode 100644 index a267aa519..000000000 --- a/org.argeo.cms.e4/OSGI-INF/userAdminWrapper.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/org.argeo.cms.e4/bnd.bnd b/org.argeo.cms.e4/bnd.bnd deleted file mode 100644 index e4a4519f0..000000000 --- a/org.argeo.cms.e4/bnd.bnd +++ /dev/null @@ -1,15 +0,0 @@ -Service-Component: OSGI-INF/homeRepository.xml,\ -OSGI-INF/userAdminWrapper.xml,\ -OSGI-INF/defaultCallbackHandler.xml -Bundle-ActivationPolicy: lazy - -Import-Package: org.eclipse.swt,\ -org.eclipse.swt.widgets;version="0.0.0",\ -org.eclipse.e4.ui.model.application.ui,\ -org.eclipse.e4.ui.model.application,\ -javax.jcr.nodetype,\ -org.argeo.cms,\ -org.eclipse.core.commands.common,\ -org.eclipse.jface.window,\ -org.argeo.cms.swt.auth,\ -* diff --git a/org.argeo.cms.e4/build.properties b/org.argeo.cms.e4/build.properties deleted file mode 100644 index e46a7baee..000000000 --- a/org.argeo.cms.e4/build.properties +++ /dev/null @@ -1,9 +0,0 @@ -output.. = bin/ -bin.includes = META-INF/,\ - OSGI-INF/,\ - .,\ - OSGI-INF/homeRepository.xml,\ - OSGI-INF/userAdminWrapper.xml,\ - OSGI-INF/defaultCallbackHandler.xml,\ - e4xmi/cms-demo.e4xmi -source.. = src/ diff --git a/org.argeo.cms.e4/e4xmi/cms-devops.e4xmi b/org.argeo.cms.e4/e4xmi/cms-devops.e4xmi deleted file mode 100644 index 89bcc376d..000000000 --- a/org.argeo.cms.e4/e4xmi/cms-devops.e4xmi +++ /dev/null @@ -1,129 +0,0 @@ - - - - - shellMaximized - auth.cn=admin,ou=roles,ou=node - - - auth.cn=admin,ou=roles,ou=node - - - - - - - - - - - - - usersEditorArea - - - - - - - - - - - - - - - - - - - - - - - - - ViewMenu - - - - - - - - - - dataExplorer - - - - - - - - - - - - - - - - - - - - - - - - - auth.cn=admin,ou=roles,ou=node - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/org.argeo.cms.e4/e4xmi/cms-ego.e4xmi b/org.argeo.cms.e4/e4xmi/cms-ego.e4xmi deleted file mode 100644 index ef659fcc7..000000000 --- a/org.argeo.cms.e4/e4xmi/cms-ego.e4xmi +++ /dev/null @@ -1,67 +0,0 @@ - - - - - shellMaximized - auth.cn=user,ou=roles,ou=node - - - - - - - - - - - ViewMenu - - - - - - - - - - dataExplorer - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/org.argeo.cms.e4/pom.xml b/org.argeo.cms.e4/pom.xml deleted file mode 100644 index dda9b4f27..000000000 --- a/org.argeo.cms.e4/pom.xml +++ /dev/null @@ -1,35 +0,0 @@ - - - 4.0.0 - - org.argeo.commons - argeo-commons - 2.3-SNAPSHOT - .. - - org.argeo.cms.e4 - CMS E4 - jar - - - org.argeo.commons - org.argeo.cms.ui - 2.3-SNAPSHOT - - - - - org.argeo.commons.rap - org.argeo.swt.specific.rap - 2.3-SNAPSHOT - provided - - - org.argeo.tp - argeo-tp-rap-e4 - ${version.argeo-tp} - pom - provided - - - \ No newline at end of file diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/CmsE4Utils.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/CmsE4Utils.java deleted file mode 100644 index 21abf5828..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/CmsE4Utils.java +++ /dev/null @@ -1,77 +0,0 @@ -package org.argeo.cms.e4; - -import java.util.List; - -import org.argeo.cms.CmsException; -import org.eclipse.e4.ui.model.application.MApplication; -import org.eclipse.e4.ui.model.application.commands.MCommand; -import org.eclipse.e4.ui.model.application.ui.basic.MPart; -import org.eclipse.e4.ui.model.application.ui.menu.MDirectMenuItem; -import org.eclipse.e4.ui.model.application.ui.menu.MHandledMenuItem; -import org.eclipse.e4.ui.workbench.modeling.EModelService; -import org.eclipse.e4.ui.workbench.modeling.EPartService; -import org.eclipse.e4.ui.workbench.modeling.EPartService.PartState; -import org.osgi.framework.Bundle; -import org.osgi.framework.FrameworkUtil; - -/** Static utilities simplifying recurring Eclipse 4 patterns. */ -public class CmsE4Utils { - /** Open an editor based on its id. */ - public static void openEditor(EPartService partService, String editorId, String key, String state) { - for (MPart part : partService.getParts()) { - String id = part.getPersistedState().get(key); - if (id != null && state.equals(id)) { - partService.showPart(part, PartState.ACTIVATE); - return; - } - } - - // new part - MPart part = partService.createPart(editorId); - if (part == null) - throw new CmsException("No editor found with id " + editorId); - part.getPersistedState().put(key, state); - partService.showPart(part, PartState.ACTIVATE); - } - - /** Dynamically creates an handled menu item from a command ID. */ - public static MHandledMenuItem createHandledMenuItem(EModelService modelService, MApplication app, - String commandId) { - MCommand command = findCommand(modelService, app, commandId); - if (command == null) - return null; - MHandledMenuItem handledItem = modelService.createModelElement(MHandledMenuItem.class); - handledItem.setCommand(command); - return handledItem; - - } - - /** - * Finds a command by ID. - * - * @return the {@link MCommand} or null if not found. - */ - public static MCommand findCommand(EModelService modelService, MApplication app, String commandId) { - List cmds = modelService.findElements(app, null, MCommand.class, null); - for (MCommand cmd : cmds) { - if (cmd.getElementId().equals(commandId)) { - return cmd; - } - } - return null; - } - - /** Dynamically creates a direct menu item from a class. */ - public static MDirectMenuItem createDirectMenuItem(EModelService modelService, Class clss, String label) { - MDirectMenuItem dynamicItem = modelService.createModelElement(MDirectMenuItem.class); - dynamicItem.setLabel(label); - Bundle bundle = FrameworkUtil.getBundle(clss); - dynamicItem.setContributionURI("bundleclass://" + bundle.getSymbolicName() + "/" + clss.getName()); - return dynamicItem; - } - - /** Singleton. */ - private CmsE4Utils() { - } - -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/OsgiFilterContextFunction.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/OsgiFilterContextFunction.java deleted file mode 100644 index c42a02a14..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/OsgiFilterContextFunction.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.argeo.cms.e4; - -import org.argeo.cms.CmsException; -import org.eclipse.e4.core.contexts.ContextFunction; -import org.eclipse.e4.core.contexts.IEclipseContext; -import org.eclipse.e4.core.di.IInjector; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.framework.ServiceReference; - -/** An Eclipse 4 {@link ContextFunction} based on an OSGi filter. */ -public class OsgiFilterContextFunction extends ContextFunction { - - private BundleContext bc = FrameworkUtil.getBundle(OsgiFilterContextFunction.class).getBundleContext(); - - @Override - public Object compute(IEclipseContext context, String contextKey) { - ServiceReference[] srs; - try { - srs = bc.getServiceReferences((String) null, contextKey); - } catch (InvalidSyntaxException e) { - throw new CmsException("Context key " + contextKey + " must be a valid osgi filter", e); - } - if (srs == null || srs.length == 0) { - return IInjector.NOT_A_VALUE; - } else { - // return the first one - return bc.getService(srs[0]); - } - } - -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/PrivilegedJob.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/PrivilegedJob.java deleted file mode 100644 index 89055d2ff..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/PrivilegedJob.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.argeo.cms.e4; - -import java.security.AccessControlContext; -import java.security.AccessController; -import java.security.PrivilegedAction; - -import javax.security.auth.Subject; - -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.core.runtime.IStatus; -import org.eclipse.core.runtime.jobs.Job; - -/** - * Propagate authentication to an eclipse job. Typically to execute a privileged - * action outside the UI thread - */ -public abstract class PrivilegedJob extends Job { - private final Subject subject; - - public PrivilegedJob(String jobName) { - this(jobName, AccessController.getContext()); - } - - public PrivilegedJob(String jobName, - AccessControlContext accessControlContext) { - super(jobName); - subject = Subject.getSubject(accessControlContext); - - // Must be called *before* the job is scheduled, - // it is required for the progress window to appear - setUser(true); - } - - @Override - protected IStatus run(final IProgressMonitor progressMonitor) { - PrivilegedAction privilegedAction = new PrivilegedAction() { - public IStatus run() { - return doRun(progressMonitor); - } - }; - return Subject.doAs(subject, privilegedAction); - } - - /** - * Implement here what should be executed with default context - * authentication - */ - protected abstract IStatus doRun(IProgressMonitor progressMonitor); -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/AuthAddon.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/AuthAddon.java deleted file mode 100644 index 3d57e1659..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/AuthAddon.java +++ /dev/null @@ -1,104 +0,0 @@ -package org.argeo.cms.e4.addons; - -import java.security.AccessController; -import java.util.Iterator; - -import javax.annotation.PostConstruct; -import javax.security.auth.Subject; -import javax.servlet.http.HttpServletRequest; - -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.CmsException; -import org.argeo.cms.auth.CurrentUser; -import org.eclipse.e4.ui.model.application.MApplication; -import org.eclipse.e4.ui.model.application.ui.MElementContainer; -import org.eclipse.e4.ui.model.application.ui.MUIElement; -import org.eclipse.e4.ui.model.application.ui.basic.MTrimBar; -import org.eclipse.e4.ui.model.application.ui.basic.MTrimmedWindow; -import org.eclipse.e4.ui.model.application.ui.basic.MWindow; - -public class AuthAddon { - private final static CmsLog log = CmsLog.getLog(AuthAddon.class); - - public final static String AUTH = "auth."; - - @PostConstruct - void init(MApplication application) { - Iterator windows = application.getChildren().iterator(); - boolean atLeastOneTopLevelWindowVisible = false; - windows: while (windows.hasNext()) { - MWindow window = windows.next(); - // main window - boolean windowVisible = process(window); - if (!windowVisible) { -// windows.remove(); - continue windows; - } - atLeastOneTopLevelWindowVisible = true; - // trim bars - if (window instanceof MTrimmedWindow) { - Iterator trimBars = ((MTrimmedWindow) window).getTrimBars().iterator(); - while (trimBars.hasNext()) { - MTrimBar trimBar = trimBars.next(); - if (!process(trimBar)) { - trimBars.remove(); - } - } - } - } - - if (!atLeastOneTopLevelWindowVisible) { - log.warn("No top-level window is authorized for user " + CurrentUser.getUsername() + ", logging out.."); - logout(); - } - } - - protected boolean process(MUIElement element) { - for (String tag : element.getTags()) { - if (tag.startsWith(AUTH)) { - String role = tag.substring(AUTH.length(), tag.length()); - if (!CurrentUser.isInRole(role)) { - element.setVisible(false); - element.setToBeRendered(false); - return false; - } - } - } - - // children - if (element instanceof MElementContainer) { - @SuppressWarnings("unchecked") - MElementContainer container = (MElementContainer) element; - Iterator children = container.getChildren().iterator(); - while (children.hasNext()) { - MUIElement child = children.next(); - boolean visible = process(child); - if (!visible) - children.remove(); - } - - for (Object child : container.getChildren()) { - if (child instanceof MUIElement) { - boolean visible = process((MUIElement) child); - if (!visible) - container.getChildren().remove(child); - } - } - } - - return true; - } - - protected void logout() { - Subject subject = Subject.getSubject(AccessController.getContext()); - try { - CurrentUser.logoutCmsSession(subject); - } catch (Exception e) { - throw new CmsException("Cannot log out", e); - } - HttpServletRequest request = org.argeo.eclipse.ui.specific.UiContext.getHttpRequest(); - if (request != null) - request.getSession().setMaxInactiveInterval(0); - } - -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/LocaleAddon.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/LocaleAddon.java deleted file mode 100644 index 5bc0d6936..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/LocaleAddon.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.argeo.cms.e4.addons; - -import java.security.AccessController; -import java.util.List; -import java.util.Locale; -import java.util.Set; - -import javax.annotation.PostConstruct; -import javax.security.auth.Subject; - -import org.argeo.eclipse.ui.specific.UiContext; -import org.eclipse.e4.core.services.nls.ILocaleChangeService; -import org.eclipse.e4.ui.model.application.MApplication; -import org.eclipse.e4.ui.model.application.ui.basic.MWindow; -import org.eclipse.e4.ui.workbench.modeling.EModelService; -import org.eclipse.e4.ui.workbench.modeling.ElementMatcher; -import org.eclipse.swt.SWT; - -/** Integrate workbench with the locale provided at log in. */ -public class LocaleAddon { - private final static String STYLE_OVERRIDE = "styleOverride"; - - // Right to left languages - private final static String ARABIC = "ar"; - private final static String HEBREW = "he"; - - @PostConstruct - public void init(ILocaleChangeService localeChangeService, EModelService modelService, MApplication application) { - Subject subject = Subject.getSubject(AccessController.getContext()); - Set locales = subject.getPublicCredentials(Locale.class); - if (!locales.isEmpty()) { - Locale locale = locales.iterator().next(); - localeChangeService.changeApplicationLocale(locale); - UiContext.setLocale(locale); - - if (locale.getLanguage().equals(ARABIC) || locale.getLanguage().equals(HEBREW)) { - List windows = modelService.findElements(application, MWindow.class, EModelService.ANYWHERE, - new ElementMatcher(null, null, (String) null)); - for (MWindow window : windows) { - String currentStyle = window.getPersistedState().get(STYLE_OVERRIDE); - int style = 0; - if (currentStyle != null) { - style = Integer.parseInt(currentStyle); - } - style = style | SWT.RIGHT_TO_LEFT; - window.getPersistedState().put(STYLE_OVERRIDE, Integer.toString(style)); - } - } - } - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/package-info.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/package-info.java deleted file mode 100644 index 6367b42d5..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Eclipse 4 addons to integrate with Argeo CMS. */ -package org.argeo.cms.e4.addons; \ No newline at end of file diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/files/NodeFsBrowserView.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/files/NodeFsBrowserView.java deleted file mode 100644 index cb9f9b97a..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/files/NodeFsBrowserView.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.argeo.cms.e4.files; - -import java.net.URI; -import java.nio.file.FileSystem; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.spi.FileSystemProvider; - -import javax.annotation.PostConstruct; -import javax.inject.Inject; - -import org.argeo.cms.CmsException; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.eclipse.ui.fs.AdvancedFsBrowser; -import org.argeo.eclipse.ui.fs.SimpleFsBrowser; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Composite; - -/** Browse the node file system. */ -public class NodeFsBrowserView { - // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + - // ".nodeFsBrowserView"; - - @Inject - FileSystemProvider nodeFileSystemProvider; - - @PostConstruct - public void createPartControl(Composite parent) { - try { - //URI uri = new URI("node://root:demo@localhost:7070/"); - URI uri = new URI("node:///"); - FileSystem fileSystem = nodeFileSystemProvider.getFileSystem(uri); - if (fileSystem == null) - fileSystem = nodeFileSystemProvider.newFileSystem(uri, null); - Path nodePath = fileSystem.getPath("/"); - - Path localPath = Paths.get(System.getProperty("user.home")); - - SimpleFsBrowser browser = new SimpleFsBrowser(parent, SWT.NO_FOCUS); - browser.setInput(nodePath, localPath); -// AdvancedFsBrowser browser = new AdvancedFsBrowser(); -// browser.createUi(parent, localPath); - } catch (Exception e) { - throw new CmsException("Cannot open file system browser", e); - } - } - - public void setFocus() { - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/files/package-info.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/files/package-info.java deleted file mode 100644 index b481dd48a..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/files/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Files browser perspective. */ -package org.argeo.cms.e4.files; \ No newline at end of file diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangeLanguage.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangeLanguage.java deleted file mode 100644 index 416df7df1..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangeLanguage.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.argeo.cms.e4.handlers; - -import java.util.Locale; - -import org.eclipse.e4.core.di.annotations.Execute; -import org.eclipse.e4.core.services.nls.ILocaleChangeService; - -public class ChangeLanguage { - @Execute - public void execute(ILocaleChangeService localeChangeService) { - localeChangeService.changeApplicationLocale(Locale.FRENCH); - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangePassword.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangePassword.java deleted file mode 100644 index 0ecd0a155..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangePassword.java +++ /dev/null @@ -1,137 +0,0 @@ -package org.argeo.cms.e4.handlers; - -import static org.argeo.cms.CmsMsg.changePassword; -import static org.argeo.cms.CmsMsg.currentPassword; -import static org.argeo.cms.CmsMsg.newPassword; -import static org.argeo.cms.CmsMsg.passwordChanged; -import static org.argeo.cms.CmsMsg.repeatNewPassword; - -import java.util.Arrays; - -import javax.inject.Inject; -import javax.naming.InvalidNameException; -import javax.naming.ldap.LdapName; - -import org.argeo.cms.CmsException; -import org.argeo.cms.auth.CurrentUser; -import org.argeo.cms.security.CryptoKeyring; -import org.argeo.cms.swt.dialogs.CmsMessageDialog; -import org.argeo.eclipse.ui.dialogs.ErrorFeedback; -import org.argeo.osgi.transaction.WorkTransaction; -import org.eclipse.e4.core.di.annotations.Execute; -import org.eclipse.e4.core.di.annotations.Optional; -import org.eclipse.jface.dialogs.Dialog; -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.Display; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; -import org.osgi.service.useradmin.User; -import org.osgi.service.useradmin.UserAdmin; - -/** Change the password of the logged-in user. */ -public class ChangePassword { - @Inject - private UserAdmin userAdmin; - @Inject - private WorkTransaction userTransaction; - @Inject - @Optional - private CryptoKeyring keyring = null; - - @Execute - public void execute() { - ChangePasswordDialog dialog = new ChangePasswordDialog(Display.getCurrent().getActiveShell(), userAdmin); - if (dialog.open() == Dialog.OK) { - new CmsMessageDialog(Display.getCurrent().getActiveShell(), passwordChanged.lead(), - CmsMessageDialog.INFORMATION).open(); - } - } - - protected void changePassword(char[] oldPassword, char[] newPassword) { - String name = CurrentUser.getUsername(); - LdapName dn; - try { - dn = new LdapName(name); - } catch (InvalidNameException e) { - throw new CmsException("Invalid user dn " + name, e); - } - User user = (User) userAdmin.getRole(dn.toString()); - if (!user.hasCredential(null, oldPassword)) - throw new CmsException("Invalid password"); - if (Arrays.equals(newPassword, new char[0])) - throw new CmsException("New password empty"); - try { - userTransaction.begin(); - user.getCredentials().put(null, newPassword); - if (keyring != null) { - keyring.changePassword(oldPassword, newPassword); - // TODO change secret keys in the CMS session - } - userTransaction.commit(); - } catch (Exception e) { - try { - userTransaction.rollback(); - } catch (Exception e1) { - e1.printStackTrace(); - } - if (e instanceof RuntimeException) - throw (RuntimeException) e; - else - throw new CmsException("Cannot change password", e); - } - } - - class ChangePasswordDialog extends CmsMessageDialog { - private Text oldPassword, newPassword1, newPassword2; - - public ChangePasswordDialog(Shell parentShell, UserAdmin securityService) { - super(parentShell, changePassword.lead(), CONFIRM); - } - -// protected Point getInitialSize() { -// return new Point(400, 450); -// } - - protected Control createDialogArea(Composite parent) { - Composite dialogarea = (Composite) super.createDialogArea(parent); - dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - Composite composite = new Composite(dialogarea, SWT.NONE); - composite.setLayout(new GridLayout(2, false)); - composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); - oldPassword = createLP(composite, currentPassword.lead()); - newPassword1 = createLP(composite, newPassword.lead()); - newPassword2 = createLP(composite, repeatNewPassword.lead()); - -// parent.pack(); - oldPassword.setFocus(); - return composite; - } - - @Override - protected void okPressed() { - try { - if (!newPassword1.getText().equals(newPassword2.getText())) - throw new CmsException("New passwords are different"); - changePassword(oldPassword.getTextChars(), newPassword1.getTextChars()); - closeShell(OK); - } catch (Exception e) { - ErrorFeedback.show("Cannot change password", e); - } - } - - /** Creates label and password. */ - protected Text createLP(Composite parent, String label) { - new Label(parent, SWT.NONE).setText(label); - Text text = new Text(parent, SWT.SINGLE | SWT.LEAD | SWT.PASSWORD | SWT.BORDER); - text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); - return text; - } - - } - -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseAllParts.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseAllParts.java deleted file mode 100644 index d11c0412c..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseAllParts.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.argeo.cms.e4.handlers; - -import org.eclipse.e4.core.di.annotations.CanExecute; -import org.eclipse.e4.core.di.annotations.Execute; -import org.eclipse.e4.ui.model.application.ui.basic.MPart; -import org.eclipse.e4.ui.workbench.modeling.EPartService; - -public class CloseAllParts { - - @Execute - void execute(EPartService partService) { - for (MPart part : partService.getParts()) { - if (part.isCloseable()) { - if (part.isDirty()) { - if (partService.savePart(part, true)) { - partService.hidePart(part, true); - } - } else { - partService.hidePart(part, true); - } - } - } - } - - @CanExecute - boolean canExecute(EPartService partService) { - boolean atLeastOnePart = false; - for (MPart part : partService.getParts()) { - if (part.isVisible() && part.isCloseable()) { - atLeastOnePart = true; - break; - } - } - return atLeastOnePart; - } - -} \ No newline at end of file diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseWorkbench.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseWorkbench.java deleted file mode 100644 index a365f3d7d..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseWorkbench.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.argeo.cms.e4.handlers; - -import java.security.AccessController; - -import javax.security.auth.Subject; - -import org.argeo.cms.CmsException; -import org.argeo.cms.auth.CurrentUser; -import org.eclipse.e4.core.di.annotations.Execute; -import org.eclipse.e4.ui.workbench.IWorkbench; - -public class CloseWorkbench { - @Execute - public void execute(IWorkbench workbench) { - logout(); - workbench.close(); - } - - protected void logout() { - Subject subject = Subject.getSubject(AccessController.getContext()); - try { - CurrentUser.logoutCmsSession(subject); - } catch (Exception e) { - throw new CmsException("Cannot log out", e); - } - } - -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/DoNothing.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/DoNothing.java deleted file mode 100644 index 358494c5b..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/DoNothing.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.argeo.cms.e4.handlers; - -import org.eclipse.e4.core.di.annotations.Execute; - -public class DoNothing { - @Execute - public void execute() { - - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/LanguageMenuContribution.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/LanguageMenuContribution.java deleted file mode 100644 index ac825bb0d..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/LanguageMenuContribution.java +++ /dev/null @@ -1,29 +0,0 @@ - -package org.argeo.cms.e4.handlers; - -import java.util.Date; -import java.util.List; - -import org.eclipse.e4.ui.di.AboutToHide; -import org.eclipse.e4.ui.di.AboutToShow; -import org.eclipse.e4.ui.model.application.ui.menu.MDirectMenuItem; -import org.eclipse.e4.ui.model.application.ui.menu.MMenuElement; -import org.eclipse.e4.ui.workbench.modeling.EModelService; - -public class LanguageMenuContribution { - @AboutToShow - public void aboutToShow(List items, EModelService modelService) { - MDirectMenuItem dynamicItem = modelService.createModelElement(MDirectMenuItem.class); - dynamicItem.setLabel("Dynamic Menu Item (" + new Date() + ")"); - //dynamicItem.setContributorURI("platform:/plugin/org.argeo.cms.e4"); - //dynamicItem.setContributionURI("bundleclass://org.argeo.cms.e4/" + ChangeLanguage.class.getName()); - dynamicItem.setEnabled(true); - dynamicItem.setContributionURI("bundleclass://org.argeo.cms.e4/org.argeo.cms.e4.handlers.ChangeLanguage"); - items.add(dynamicItem); - } - - @AboutToHide - public void aboutToHide() { - - } -} \ No newline at end of file diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/OpenPerspective.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/OpenPerspective.java deleted file mode 100644 index ac544b107..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/OpenPerspective.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.argeo.cms.e4.handlers; - -import java.util.List; - -import javax.inject.Inject; -import javax.inject.Named; - -import org.eclipse.e4.core.di.annotations.Execute; -import org.eclipse.e4.ui.model.application.MApplication; -import org.eclipse.e4.ui.model.application.ui.advanced.MPerspective; -import org.eclipse.e4.ui.workbench.modeling.EModelService; -import org.eclipse.e4.ui.workbench.modeling.EPartService; - -public class OpenPerspective { - @Inject - MApplication application; - @Inject - EPartService partService; - @Inject - EModelService modelService; - - @Execute - public void execute(@Named("perspectiveId") String perspectiveId) { - List perspectives = modelService.findElements(application, perspectiveId, MPerspective.class, - null); - if (perspectives.size() == 0) - return; - MPerspective perspective = perspectives.get(0); - partService.switchPerspective(perspective); - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SaveAllParts.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SaveAllParts.java deleted file mode 100644 index 3b60abd7e..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SaveAllParts.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.argeo.cms.e4.handlers; - -import org.eclipse.e4.core.di.annotations.CanExecute; -import org.eclipse.e4.core.di.annotations.Execute; -import org.eclipse.e4.ui.workbench.modeling.EPartService; - -public class SaveAllParts { - - @Execute - void execute(EPartService partService) { - partService.saveAll(false); - } - - @CanExecute - boolean canExecute(EPartService partService) { - return partService.getDirtyParts().size() > 0; - } - -} \ No newline at end of file diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SavePart.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SavePart.java deleted file mode 100644 index 73486f363..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SavePart.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.argeo.cms.e4.handlers; - -import org.eclipse.e4.core.di.annotations.CanExecute; -import org.eclipse.e4.core.di.annotations.Execute; -import org.eclipse.e4.ui.model.application.ui.basic.MPart; -import org.eclipse.e4.ui.workbench.modeling.EPartService; - -public class SavePart { - @Execute - void execute(EPartService partService, MPart part) { - partService.savePart(part, false); - } - - @CanExecute - boolean canExecute(MPart part) { - return part.isDirty(); - } -} \ No newline at end of file diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/package-info.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/package-info.java deleted file mode 100644 index a44ca9056..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Generic Eclipse 4 handlers. */ -package org.argeo.cms.e4.handlers; \ No newline at end of file diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/GenericPropertyPage.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/GenericPropertyPage.java deleted file mode 100644 index e17f17bb7..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/GenericPropertyPage.java +++ /dev/null @@ -1,141 +0,0 @@ -package org.argeo.cms.e4.jcr; - -import java.util.ArrayList; -import java.util.List; - -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.PropertyIterator; -import javax.jcr.RepositoryException; - -import org.argeo.cms.ui.jcr.PropertyLabelProvider; -import org.argeo.eclipse.ui.EclipseUiException; -import org.eclipse.jface.layout.TreeColumnLayout; -import org.eclipse.jface.viewers.ColumnWeightData; -import org.eclipse.jface.viewers.IBaseLabelProvider; -import org.eclipse.jface.viewers.ITreeContentProvider; -import org.eclipse.jface.viewers.TreeViewer; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.ScrolledComposite; -import org.eclipse.swt.layout.FillLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Tree; -import org.eclipse.swt.widgets.TreeColumn; - -/** - * Generic editor property page. Lists all properties of current node as a - * complex tree. TODO: enable editing - */ -public class GenericPropertyPage { - - // Main business Objects - private Node currentNode; - - public GenericPropertyPage(Node currentNode) { - this.currentNode = currentNode; - } - - protected void createFormContent(Composite parent) { - Composite innerBox = new Composite(parent, SWT.NONE); - // Composite innerBox = new Composite(body, SWT.NO_FOCUS); - FillLayout layout = new FillLayout(); - layout.marginHeight = 5; - layout.marginWidth = 5; - innerBox.setLayout(layout); - createComplexTree(innerBox); - // TODO TreeColumnLayout triggers a scroll issue with the form: - // The inside body is always to big and a scroll bar is shown - // Composite tableCmp = new Composite(body, SWT.NO_FOCUS); - // createComplexTree(tableCmp); - } - - private TreeViewer createComplexTree(Composite parent) { - int style = SWT.BORDER | SWT.MULTI | SWT.FULL_SELECTION; - Tree tree = new Tree(parent, style); - TreeColumnLayout tableColumnLayout = new TreeColumnLayout(); - - createColumn(tree, tableColumnLayout, "Property", SWT.LEFT, 200, 30); - createColumn(tree, tableColumnLayout, "Value(s)", SWT.LEFT, 300, 60); - createColumn(tree, tableColumnLayout, "Type", SWT.LEFT, 75, 10); - createColumn(tree, tableColumnLayout, "Attributes", SWT.LEFT, 75, 0); - // Do not apply the treeColumnLayout it does not work yet - // parent.setLayout(tableColumnLayout); - - tree.setLinesVisible(true); - tree.setHeaderVisible(true); - - TreeViewer treeViewer = new TreeViewer(tree); - treeViewer.setContentProvider(new TreeContentProvider()); - treeViewer.setLabelProvider((IBaseLabelProvider) new PropertyLabelProvider()); - treeViewer.setInput(currentNode); - treeViewer.expandAll(); - return treeViewer; - } - - private static TreeColumn createColumn(Tree parent, TreeColumnLayout tableColumnLayout, String name, int style, - int width, int weight) { - TreeColumn column = new TreeColumn(parent, style); - column.setText(name); - column.setWidth(width); - column.setMoveable(true); - column.setResizable(true); - tableColumnLayout.setColumnData(column, new ColumnWeightData(weight, width, true)); - return column; - } - - private class TreeContentProvider implements ITreeContentProvider { - private static final long serialVersionUID = -6162736530019406214L; - - public Object[] getElements(Object parent) { - Object[] props = null; - try { - - if (parent instanceof Node) { - Node node = (Node) parent; - PropertyIterator pi; - pi = node.getProperties(); - List propList = new ArrayList(); - while (pi.hasNext()) { - propList.add(pi.nextProperty()); - } - props = propList.toArray(); - } - } catch (RepositoryException e) { - throw new EclipseUiException("Unexpected exception while listing node properties", e); - } - return props; - } - - public Object getParent(Object child) { - return null; - } - - public Object[] getChildren(Object parent) { - if (parent instanceof Property) { - Property prop = (Property) parent; - try { - if (prop.isMultiple()) - return prop.getValues(); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot get multi-prop values on " + prop, e); - } - } - return null; - } - - public boolean hasChildren(Object parent) { - try { - return (parent instanceof Property && ((Property) parent).isMultiple()); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot check if property is multiple for " + parent, e); - } - } - - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - } - - public void dispose() { - } - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrBrowserView.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrBrowserView.java deleted file mode 100644 index 98e80936d..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrBrowserView.java +++ /dev/null @@ -1,349 +0,0 @@ -package org.argeo.cms.e4.jcr; - -import java.util.List; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import javax.inject.Inject; -import javax.jcr.Property; -import javax.jcr.PropertyType; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.RepositoryFactory; -import javax.jcr.Session; -import javax.jcr.Value; -import javax.jcr.observation.Event; -import javax.jcr.observation.EventListener; -import javax.jcr.observation.ObservationManager; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.cms.CmsException; -import org.argeo.cms.security.CryptoKeyring; -import org.argeo.cms.security.Keyring; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.jcr.JcrBrowserUtils; -import org.argeo.cms.ui.jcr.NodeContentProvider; -import org.argeo.cms.ui.jcr.NodeLabelProvider; -import org.argeo.cms.ui.jcr.OsgiRepositoryRegister; -import org.argeo.cms.ui.jcr.PropertiesContentProvider; -import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem; -import org.argeo.eclipse.ui.EclipseUiException; -import org.argeo.eclipse.ui.TreeParent; -import org.argeo.eclipse.ui.jcr.AsyncUiEventListener; -import org.argeo.eclipse.ui.jcr.util.NodeViewerComparer; -import org.argeo.jcr.JcrUtils; -import org.eclipse.e4.core.contexts.IEclipseContext; -import org.eclipse.e4.core.di.annotations.Optional; -import org.eclipse.e4.ui.services.EMenuService; -import org.eclipse.e4.ui.workbench.modeling.EPartService; -import org.eclipse.e4.ui.workbench.modeling.ESelectionService; -import org.eclipse.jface.viewers.ColumnLabelProvider; -import org.eclipse.jface.viewers.IBaseLabelProvider; -import org.eclipse.jface.viewers.ISelectionChangedListener; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.ITreeContentProvider; -import org.eclipse.jface.viewers.SelectionChangedEvent; -import org.eclipse.jface.viewers.StructuredSelection; -import org.eclipse.jface.viewers.TableViewer; -import org.eclipse.jface.viewers.TableViewerColumn; -import org.eclipse.jface.viewers.TreeViewer; -import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.SashForm; -import org.eclipse.swt.layout.FillLayout; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Display; - -/** - * Basic View to display a sash form to browse a JCR compliant multiple - * repository environment - */ -public class JcrBrowserView { - final static String ID = "org.argeo.cms.e4.jcrbrowser"; - final static String NODE_VIEWER_POPUP_MENU_ID = "org.argeo.cms.e4.popupmenu.nodeViewer"; - - private boolean sortChildNodes = true; - - /* DEPENDENCY INJECTION */ - @Inject - @Optional - private Keyring keyring; - @Inject - private RepositoryFactory repositoryFactory; - @Inject - private Repository nodeRepository; - - // Current user session on the home repository default workspace - private Session userSession; - - private OsgiRepositoryRegister repositoryRegister = new OsgiRepositoryRegister(); - - // This page widgets - private TreeViewer nodesViewer; - private NodeContentProvider nodeContentProvider; - private TableViewer propertiesViewer; - private EventListener resultsObserver; - - @PostConstruct - public void createPartControl(Composite parent, IEclipseContext context, EPartService partService, - ESelectionService selectionService, EMenuService menuService) { - repositoryRegister.init(); - - parent.setLayout(new FillLayout()); - SashForm sashForm = new SashForm(parent, SWT.VERTICAL); - // sashForm.setSashWidth(4); - // sashForm.setLayout(new FillLayout()); - - // Create the tree on top of the view - Composite top = new Composite(sashForm, SWT.NONE); - // GridLayout gl = new GridLayout(1, false); - top.setLayout(CmsSwtUtils.noSpaceGridLayout()); - - try { - this.userSession = this.nodeRepository.login(CmsConstants.HOME_WORKSPACE); - } catch (RepositoryException e) { - throw new CmsException("Cannot open user session", e); - } - - nodeContentProvider = new NodeContentProvider(userSession, keyring, repositoryRegister, repositoryFactory, - sortChildNodes); - - // nodes viewer - nodesViewer = createNodeViewer(top, nodeContentProvider); - - // context menu : it is completely defined in the plugin.xml file. - // MenuManager menuManager = new MenuManager(); - // Menu menu = menuManager.createContextMenu(nodesViewer.getTree()); - - // nodesViewer.getTree().setMenu(menu); - - nodesViewer.setInput(""); - - // Create the property viewer on the bottom - Composite bottom = new Composite(sashForm, SWT.NONE); - bottom.setLayout(CmsSwtUtils.noSpaceGridLayout()); - propertiesViewer = createPropertiesViewer(bottom); - - sashForm.setWeights(getWeights()); - nodesViewer.setComparer(new NodeViewerComparer()); - nodesViewer.addSelectionChangedListener(new ISelectionChangedListener() { - public void selectionChanged(SelectionChangedEvent event) { - IStructuredSelection selection = (IStructuredSelection) event.getSelection(); - selectionService.setSelection(selection.toList()); - } - }); - nodesViewer.addDoubleClickListener(new JcrE4DClickListener(nodesViewer, partService)); - menuService.registerContextMenu(nodesViewer.getControl(), NODE_VIEWER_POPUP_MENU_ID); - // getSite().registerContextMenu(menuManager, nodesViewer); - // getSite().setSelectionProvider(nodesViewer); - } - - @PreDestroy - public void dispose() { - JcrUtils.logoutQuietly(userSession); - repositoryRegister.destroy(); - } - - public void refresh(Object obj) { - // Enable full refresh from a command when no element of the tree is - // selected - if (obj == null) { - Object[] elements = nodeContentProvider.getElements(null); - for (Object el : elements) { - if (el instanceof TreeParent) - JcrBrowserUtils.forceRefreshIfNeeded((TreeParent) el); - getNodeViewer().refresh(el); - } - } else - getNodeViewer().refresh(obj); - } - - /** - * To be overridden to adapt size of form and result frames. - */ - protected int[] getWeights() { - return new int[] { 70, 30 }; - } - - protected TreeViewer createNodeViewer(Composite parent, final ITreeContentProvider nodeContentProvider) { - - final TreeViewer tmpNodeViewer = new TreeViewer(parent, SWT.MULTI); - - tmpNodeViewer.getTree().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - - tmpNodeViewer.setContentProvider(nodeContentProvider); - tmpNodeViewer.setLabelProvider((IBaseLabelProvider) new NodeLabelProvider()); - tmpNodeViewer.addSelectionChangedListener(new ISelectionChangedListener() { - public void selectionChanged(SelectionChangedEvent event) { - if (!event.getSelection().isEmpty()) { - IStructuredSelection sel = (IStructuredSelection) event.getSelection(); - Object firstItem = sel.getFirstElement(); - if (firstItem instanceof SingleJcrNodeElem) - propertiesViewer.setInput(((SingleJcrNodeElem) firstItem).getNode()); - } else { - propertiesViewer.setInput(""); - } - } - }); - - resultsObserver = new TreeObserver(tmpNodeViewer.getTree().getDisplay()); - if (keyring != null) - try { - ObservationManager observationManager = userSession.getWorkspace().getObservationManager(); - observationManager.addEventListener(resultsObserver, Event.PROPERTY_ADDED | Event.PROPERTY_CHANGED, "/", - true, null, null, false); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot register listeners", e); - } - - // tmpNodeViewer.addDoubleClickListener(new JcrDClickListener(tmpNodeViewer)); - return tmpNodeViewer; - } - - protected TableViewer createPropertiesViewer(Composite parent) { - propertiesViewer = new TableViewer(parent, SWT.NONE); - propertiesViewer.getTable().setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - propertiesViewer.getTable().setHeaderVisible(true); - propertiesViewer.setContentProvider(new PropertiesContentProvider()); - TableViewerColumn col = new TableViewerColumn(propertiesViewer, SWT.NONE); - col.getColumn().setText("Name"); - col.getColumn().setWidth(200); - col.setLabelProvider(new ColumnLabelProvider() { - private static final long serialVersionUID = -6684361063107478595L; - - public String getText(Object element) { - try { - return ((Property) element).getName(); - } catch (RepositoryException e) { - throw new EclipseUiException("Unexpected exception in label provider", e); - } - } - }); - col = new TableViewerColumn(propertiesViewer, SWT.NONE); - col.getColumn().setText("Value"); - col.getColumn().setWidth(400); - col.setLabelProvider(new ColumnLabelProvider() { - private static final long serialVersionUID = -8201994187693336657L; - - public String getText(Object element) { - try { - Property property = (Property) element; - if (property.getType() == PropertyType.BINARY) - return ""; - else if (property.isMultiple()) { - StringBuffer buf = new StringBuffer("["); - Value[] values = property.getValues(); - for (int i = 0; i < values.length; i++) { - if (i != 0) - buf.append(", "); - buf.append(values[i].getString()); - } - buf.append(']'); - return buf.toString(); - } else - return property.getValue().getString(); - } catch (RepositoryException e) { - throw new EclipseUiException("Unexpected exception in label provider", e); - } - } - }); - col = new TableViewerColumn(propertiesViewer, SWT.NONE); - col.getColumn().setText("Type"); - col.getColumn().setWidth(200); - col.setLabelProvider(new ColumnLabelProvider() { - private static final long serialVersionUID = -6009599998150286070L; - - public String getText(Object element) { - return JcrBrowserUtils.getPropertyTypeAsString((Property) element); - } - }); - propertiesViewer.setInput(""); - return propertiesViewer; - } - - protected TreeViewer getNodeViewer() { - return nodesViewer; - } - - /** - * Resets the tree content provider - * - * @param sortChildNodes if true the content provider will use a comparer to - * sort nodes that might slow down the display - */ - public void setSortChildNodes(boolean sortChildNodes) { - this.sortChildNodes = sortChildNodes; - ((NodeContentProvider) nodesViewer.getContentProvider()).setSortChildren(sortChildNodes); - nodesViewer.setInput(""); - } - - /** Notifies the current view that a node has been added */ - public void nodeAdded(TreeParent parentNode) { - // insure that Ui objects have been correctly created: - JcrBrowserUtils.forceRefreshIfNeeded(parentNode); - getNodeViewer().refresh(parentNode); - getNodeViewer().expandToLevel(parentNode, 1); - } - - /** Notifies the current view that a node has been removed */ - public void nodeRemoved(TreeParent parentNode) { - IStructuredSelection newSel = new StructuredSelection(parentNode); - getNodeViewer().setSelection(newSel, true); - // Force refresh - IStructuredSelection tmpSel = (IStructuredSelection) getNodeViewer().getSelection(); - getNodeViewer().refresh(tmpSel.getFirstElement()); - } - - class TreeObserver extends AsyncUiEventListener { - - public TreeObserver(Display display) { - super(display); - } - - @Override - protected Boolean willProcessInUiThread(List events) throws RepositoryException { - for (Event event : events) { - if (getLog().isTraceEnabled()) - getLog().debug("Received event " + event); - String path = event.getPath(); - int index = path.lastIndexOf('/'); - String propertyName = path.substring(index + 1); - if (getLog().isTraceEnabled()) - getLog().debug("Concerned property " + propertyName); - } - return false; - } - - protected void onEventInUiThread(List events) throws RepositoryException { - if (getLog().isTraceEnabled()) - getLog().trace("Refresh result list"); - nodesViewer.refresh(); - } - - } - - public boolean getSortChildNodes() { - return sortChildNodes; - } - - public void setFocus() { - getNodeViewer().getTree().setFocus(); - } - - /* DEPENDENCY INJECTION */ - // public void setRepositoryRegister(RepositoryRegister repositoryRegister) { - // this.repositoryRegister = repositoryRegister; - // } - - public void setKeyring(CryptoKeyring keyring) { - this.keyring = keyring; - } - - public void setRepositoryFactory(RepositoryFactory repositoryFactory) { - this.repositoryFactory = repositoryFactory; - } - - public void setNodeRepository(Repository nodeRepository) { - this.nodeRepository = nodeRepository; - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrE4DClickListener.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrE4DClickListener.java deleted file mode 100644 index ad6a547da..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrE4DClickListener.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.argeo.cms.e4.jcr; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.cms.CmsException; -import org.argeo.cms.ui.jcr.JcrDClickListener; -import org.eclipse.e4.ui.model.application.ui.basic.MPart; -import org.eclipse.e4.ui.workbench.modeling.EPartService; -import org.eclipse.e4.ui.workbench.modeling.EPartService.PartState; -import org.eclipse.jface.viewers.TreeViewer; - -public class JcrE4DClickListener extends JcrDClickListener { - EPartService partService; - - public JcrE4DClickListener(TreeViewer nodeViewer, EPartService partService) { - super(nodeViewer); - this.partService = partService; - } - - @Override - protected void openNode(Node node) { - MPart part = partService.createPart(JcrNodeEditor.DESCRIPTOR_ID); - try { - part.setLabel(node.getName()); - part.getPersistedState().put("nodeWorkspace", node.getSession().getWorkspace().getName()); - part.getPersistedState().put("nodePath", node.getPath()); - } catch (RepositoryException e) { - throw new CmsException("Cannot open " + node, e); - } - - // the provided part is be shown - partService.showPart(part, PartState.ACTIVATE); - } - -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrNodeEditor.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrNodeEditor.java deleted file mode 100644 index ae2b325f5..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/JcrNodeEditor.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.argeo.cms.e4.jcr; - -import java.util.List; - -import javax.annotation.PostConstruct; -import javax.jcr.Node; - -import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem; -import org.eclipse.e4.ui.model.application.ui.basic.MPart; -import org.eclipse.e4.ui.workbench.modeling.ESelectionService; -import org.eclipse.swt.layout.FillLayout; -import org.eclipse.swt.widgets.Composite; - -public class JcrNodeEditor { - final static String DESCRIPTOR_ID = "org.argeo.cms.e4.partdescriptor.nodeEditor"; - - @PostConstruct - public void createUi(Composite parent, MPart part, ESelectionService selectionService) { - parent.setLayout(new FillLayout()); - List selection = (List) selectionService.getSelection(); - Node node = ((SingleJcrNodeElem) selection.get(0)).getNode(); - GenericPropertyPage propertyPage = new GenericPropertyPage(node); - propertyPage.createFormContent(parent); - } - -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/SimplePart.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/SimplePart.java deleted file mode 100644 index 17d8d2a23..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/SimplePart.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.argeo.cms.e4.jcr; - -import javax.annotation.PostConstruct; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Label; - -public class SimplePart { - - @PostConstruct - void init(Composite parent) { - parent.setLayout(new GridLayout()); - Label label = new Label(parent, SWT.NONE); - label.setText("Hello e4 World"); - } - -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/AddFolderNode.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/AddFolderNode.java deleted file mode 100644 index 8f5bc36e1..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/AddFolderNode.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.argeo.cms.e4.jcr.handlers; - -import java.util.List; - -import javax.inject.Named; -import javax.jcr.Node; -import javax.jcr.RepositoryException; -import javax.jcr.nodetype.NodeType; - -import org.argeo.cms.e4.jcr.JcrBrowserView; -import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem; -import org.argeo.cms.ui.jcr.model.WorkspaceElem; -import org.argeo.eclipse.ui.TreeParent; -import org.argeo.eclipse.ui.dialogs.ErrorFeedback; -import org.argeo.eclipse.ui.dialogs.SingleValue; -import org.eclipse.e4.core.di.annotations.Execute; -import org.eclipse.e4.ui.model.application.ui.basic.MPart; -import org.eclipse.e4.ui.services.IServiceConstants; -import org.eclipse.e4.ui.workbench.modeling.ESelectionService; - -/** - * Adds a node of type nt:folder, only on {@link SingleJcrNodeElem} and - * {@link WorkspaceElem} TreeObject types. - * - * This handler assumes that a selection provider is available and picks only - * first selected item. It is UI's job to enable the command only when the - * selection contains one and only one element. Thus no parameter is passed - * through the command. - */ -public class AddFolderNode { - @Execute - public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part, ESelectionService selectionService) { - List selection = (List) selectionService.getSelection(); - JcrBrowserView view = (JcrBrowserView) part.getObject(); - - if (selection != null && selection.size() == 1) { - TreeParent treeParentNode = null; - Node jcrParentNode = null; - Object obj = selection.get(0); - - if (obj instanceof SingleJcrNodeElem) { - treeParentNode = (TreeParent) obj; - jcrParentNode = ((SingleJcrNodeElem) treeParentNode).getNode(); - } else if (obj instanceof WorkspaceElem) { - treeParentNode = (TreeParent) obj; - jcrParentNode = ((WorkspaceElem) treeParentNode).getRootNode(); - } else - return; - - String folderName = SingleValue.ask("Folder name", "Enter folder name"); - if (folderName != null) { - try { - jcrParentNode.addNode(folderName, NodeType.NT_FOLDER); - jcrParentNode.getSession().save(); - view.nodeAdded(treeParentNode); - } catch (RepositoryException e) { - ErrorFeedback.show("Cannot create folder " + folderName + " under " + treeParentNode, e); - } - } - } else { - // ErrorFeedback.show(WorkbenchUiPlugin - // .getMessage("errorUnvalidNtFolderNodeType")); - ErrorFeedback.show("Invalid NT folder node type"); - } - } - -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/AddRemoteRepository.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/AddRemoteRepository.java deleted file mode 100644 index dc47f6edf..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/AddRemoteRepository.java +++ /dev/null @@ -1,210 +0,0 @@ -package org.argeo.cms.e4.jcr.handlers; - -import java.net.URI; -import java.util.Hashtable; - -import javax.inject.Inject; -import javax.inject.Named; -import javax.jcr.Node; -import javax.jcr.Repository; -import javax.jcr.RepositoryFactory; -import javax.jcr.Session; -import javax.jcr.SimpleCredentials; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.cms.ArgeoNames; -import org.argeo.cms.ArgeoTypes; -import org.argeo.cms.e4.jcr.JcrBrowserView; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.cms.security.Keyring; -import org.argeo.eclipse.ui.EclipseUiException; -import org.argeo.eclipse.ui.dialogs.ErrorFeedback; -import org.argeo.jcr.JcrUtils; -import org.eclipse.e4.core.di.annotations.Execute; -import org.eclipse.e4.core.di.annotations.Optional; -import org.eclipse.e4.ui.model.application.ui.basic.MPart; -import org.eclipse.e4.ui.services.IServiceConstants; -import org.eclipse.jface.dialogs.Dialog; -import org.eclipse.jface.dialogs.IMessageProvider; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.jface.dialogs.TitleAreaDialog; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.graphics.Point; -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.Display; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; - -/** - * Connect to a remote repository and, if successful publish it as an OSGi - * service. - */ -public class AddRemoteRepository { - - @Inject - private RepositoryFactory repositoryFactory; - @Inject - private Repository nodeRepository; - @Inject - @Optional - private Keyring keyring; - - @Execute - public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part) { - JcrBrowserView view = (JcrBrowserView) part.getObject(); - RemoteRepositoryLoginDialog dlg = new RemoteRepositoryLoginDialog(Display.getDefault().getActiveShell()); - if (dlg.open() == Dialog.OK) { - view.refresh(null); - } - } - - // public void setRepositoryFactory(RepositoryFactory repositoryFactory) { - // this.repositoryFactory = repositoryFactory; - // } - // - // public void setKeyring(Keyring keyring) { - // this.keyring = keyring; - // } - // - // public void setNodeRepository(Repository nodeRepository) { - // this.nodeRepository = nodeRepository; - // } - - class RemoteRepositoryLoginDialog extends TitleAreaDialog { - private static final long serialVersionUID = 2234006887750103399L; - private Text name; - private Text uri; - private Text username; - private Text password; - private Button saveInKeyring; - - public RemoteRepositoryLoginDialog(Shell parentShell) { - super(parentShell); - } - - protected Point getInitialSize() { - return new Point(600, 400); - } - - protected Control createDialogArea(Composite parent) { - Composite dialogarea = (Composite) super.createDialogArea(parent); - dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - Composite composite = new Composite(dialogarea, SWT.NONE); - composite.setLayout(new GridLayout(2, false)); - composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); - setMessage("Login to remote repository", IMessageProvider.NONE); - name = createLT(composite, "Name", "remoteRepository"); - uri = createLT(composite, "URI", "http://localhost:7070/jcr/node"); - username = createLT(composite, "User", ""); - password = createLP(composite, "Password"); - - saveInKeyring = createLC(composite, "Remember password", false); - parent.pack(); - return composite; - } - - @Override - protected void createButtonsForButtonBar(Composite parent) { - super.createButtonsForButtonBar(parent); - Button test = createButton(parent, 2, "Test", false); - test.addSelectionListener(new SelectionAdapter() { - private static final long serialVersionUID = -1829962269440419560L; - - public void widgetSelected(SelectionEvent arg0) { - testConnection(); - } - }); - } - - void testConnection() { - Session session = null; - try { - URI checkedUri = new URI(uri.getText()); - String checkedUriStr = checkedUri.toString(); - - Hashtable params = new Hashtable(); - params.put(CmsConstants.LABELED_URI, checkedUriStr); - Repository repository = repositoryFactory.getRepository(params); - if (username.getText().trim().equals("")) {// anonymous - // FIXME make it more generic - session = repository.login(CmsConstants.SYS_WORKSPACE); - } else { - // FIXME use getTextChars() when upgrading to 3.7 - // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=297412 - char[] pwd = password.getText().toCharArray(); - SimpleCredentials sc = new SimpleCredentials(username.getText(), pwd); - session = repository.login(sc, "main"); - MessageDialog.openInformation(getParentShell(), "Success", - "Connection to '" + uri.getText() + "' successful"); - } - } catch (Exception e) { - ErrorFeedback.show("Connection test failed for " + uri.getText(), e); - } finally { - JcrUtils.logoutQuietly(session); - } - } - - @Override - protected void okPressed() { - Session nodeSession = null; - try { - nodeSession = nodeRepository.login(); - Node home = CmsJcrUtils.getUserHome(nodeSession); - - Node remote = home.hasNode(ArgeoNames.ARGEO_REMOTE) ? home.getNode(ArgeoNames.ARGEO_REMOTE) - : home.addNode(ArgeoNames.ARGEO_REMOTE); - if (remote.hasNode(name.getText())) - throw new EclipseUiException("There is already a remote repository named " + name.getText()); - Node remoteRepository = remote.addNode(name.getText(), ArgeoTypes.ARGEO_REMOTE_REPOSITORY); - remoteRepository.setProperty(ArgeoNames.ARGEO_URI, uri.getText()); - remoteRepository.setProperty(ArgeoNames.ARGEO_USER_ID, username.getText()); - nodeSession.save(); - if (saveInKeyring.getSelection()) { - String pwdPath = remoteRepository.getPath() + '/' + ArgeoNames.ARGEO_PASSWORD; - keyring.set(pwdPath, password.getText().toCharArray()); - } - nodeSession.save(); - MessageDialog.openInformation(getParentShell(), "Repository Added", - "Remote repository '" + username.getText() + "@" + uri.getText() + "' added"); - - super.okPressed(); - } catch (Exception e) { - ErrorFeedback.show("Cannot add remote repository", e); - } finally { - JcrUtils.logoutQuietly(nodeSession); - } - } - - /** Creates label and text. */ - protected Text createLT(Composite parent, String label, String initial) { - new Label(parent, SWT.NONE).setText(label); - Text text = new Text(parent, SWT.SINGLE | SWT.LEAD | SWT.BORDER); - text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - text.setText(initial); - return text; - } - - /** Creates label and check. */ - protected Button createLC(Composite parent, String label, Boolean initial) { - new Label(parent, SWT.NONE).setText(label); - Button check = new Button(parent, SWT.CHECK); - check.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - check.setSelection(initial); - return check; - } - - protected Text createLP(Composite parent, String label) { - new Label(parent, SWT.NONE).setText(label); - Text text = new Text(parent, SWT.SINGLE | SWT.LEAD | SWT.BORDER | SWT.PASSWORD); - text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - return text; - } - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/DeleteNodes.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/DeleteNodes.java deleted file mode 100644 index 1974e4d44..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/DeleteNodes.java +++ /dev/null @@ -1,95 +0,0 @@ -package org.argeo.cms.e4.jcr.handlers; - -import java.util.List; - -import javax.inject.Named; -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.cms.e4.jcr.JcrBrowserView; -import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem; -import org.argeo.cms.ui.jcr.model.WorkspaceElem; -import org.argeo.eclipse.ui.EclipseUiException; -import org.argeo.eclipse.ui.TreeParent; -import org.argeo.eclipse.ui.dialogs.ErrorFeedback; -import org.eclipse.e4.core.di.annotations.Execute; -import org.eclipse.e4.ui.model.application.ui.basic.MPart; -import org.eclipse.e4.ui.services.IServiceConstants; -import org.eclipse.e4.ui.workbench.modeling.ESelectionService; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.swt.widgets.Display; - -/** - * Delete the selected nodes: both in the JCR repository and in the UI view. - * Warning no check is done, except implementation dependent native checks, - * handle with care. - * - * This handler is still 'hard linked' to a GenericJcrBrowser view to enable - * correct tree refresh when a node is added. This must be corrected in future - * versions. - */ -public class DeleteNodes { - @Execute - public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part, ESelectionService selectionService) { - List selection = (List) selectionService.getSelection(); - if (selection == null) - return; - - JcrBrowserView view = (JcrBrowserView) part.getObject(); - - // confirmation - StringBuffer buf = new StringBuffer(""); - for (Object o : selection) { - SingleJcrNodeElem sjn = (SingleJcrNodeElem) o; - buf.append(sjn.getName()).append(' '); - } - Boolean doRemove = MessageDialog.openConfirm(Display.getCurrent().getActiveShell(), "Confirm deletion", - "Do you want to delete " + buf + "?"); - - // operation - if (doRemove) { - SingleJcrNodeElem ancestor = null; - WorkspaceElem rootAncestor = null; - try { - for (Object obj : selection) { - if (obj instanceof SingleJcrNodeElem) { - // Cache objects - SingleJcrNodeElem sjn = (SingleJcrNodeElem) obj; - TreeParent tp = (TreeParent) sjn.getParent(); - Node node = sjn.getNode(); - - // Jcr Remove - node.remove(); - node.getSession().save(); - // UI remove - tp.removeChild(sjn); - - // Check if the parent is the root node - if (tp instanceof WorkspaceElem) - rootAncestor = (WorkspaceElem) tp; - else - ancestor = getOlder(ancestor, (SingleJcrNodeElem) tp); - } - } - if (rootAncestor != null) - view.nodeRemoved(rootAncestor); - else if (ancestor != null) - view.nodeRemoved(ancestor); - } catch (Exception e) { - ErrorFeedback.show("Cannot delete selected node ", e); - } - } - } - - private SingleJcrNodeElem getOlder(SingleJcrNodeElem A, SingleJcrNodeElem B) { - try { - if (A == null) - return B == null ? null : B; - // Todo enhanced this method - else - return A.getNode().getDepth() <= B.getNode().getDepth() ? A : B; - } catch (RepositoryException re) { - throw new EclipseUiException("Cannot find ancestor", re); - } - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/Refresh.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/Refresh.java deleted file mode 100644 index 4ae072cb5..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/Refresh.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.argeo.cms.e4.jcr.handlers; - -import java.util.List; - -import javax.inject.Named; - -import org.argeo.cms.e4.jcr.JcrBrowserView; -import org.argeo.cms.ui.jcr.JcrBrowserUtils; -import org.argeo.eclipse.ui.TreeParent; -import org.eclipse.e4.core.di.annotations.Execute; -import org.eclipse.e4.ui.model.application.ui.basic.MPart; -import org.eclipse.e4.ui.services.IServiceConstants; -import org.eclipse.e4.ui.workbench.modeling.EPartService; -import org.eclipse.e4.ui.workbench.modeling.ESelectionService; - -/** - * Force the selected objects of the active view to be refreshed doing the - * following: - *
    - *
  1. The model objects are recomputed
  2. - *
  3. the view is refreshed
  4. - *
- */ -public class Refresh { - - @Execute - public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part, EPartService partService, - ESelectionService selectionService) { - - JcrBrowserView view = (JcrBrowserView) part.getObject(); - List selection = (List) selectionService.getSelection(); - - if (selection != null && !selection.isEmpty()) { - for (Object obj : selection) - if (obj instanceof TreeParent) { - TreeParent tp = (TreeParent) obj; - JcrBrowserUtils.forceRefreshIfNeeded(tp); - view.refresh(obj); - } - } else if (view instanceof JcrBrowserView) - view.refresh(null); // force full refresh - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/RenameNode.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/RenameNode.java deleted file mode 100644 index 97674abc2..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/RenameNode.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.argeo.cms.e4.jcr.handlers; - -import java.util.List; - -import javax.inject.Named; -import javax.jcr.Node; -import javax.jcr.RepositoryException; -import javax.jcr.Session; - -import org.argeo.cms.e4.jcr.JcrBrowserView; -import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem; -import org.argeo.eclipse.ui.EclipseUiException; -import org.argeo.eclipse.ui.dialogs.SingleValue; -import org.argeo.jcr.JcrUtils; -import org.eclipse.e4.core.di.annotations.Execute; -import org.eclipse.e4.ui.model.application.ui.basic.MPart; -import org.eclipse.e4.ui.services.IServiceConstants; -import org.eclipse.e4.ui.workbench.modeling.EPartService; -import org.eclipse.e4.ui.workbench.modeling.ESelectionService; - -/** - * Canonically call JCR Session#move(String, String) on the first element - * returned by HandlerUtil#getActiveWorkbenchWindow() - * (...getActivePage().getSelection()), if it is a {@link SingleJcrNodeElem}. - * The user must then fill a new name in and confirm - */ -public class RenameNode { - @Execute - public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part, EPartService partService, - ESelectionService selectionService) { - List selection = (List) selectionService.getSelection(); - if (selection == null || selection.size() != 1) - return; - JcrBrowserView view = (JcrBrowserView) part.getObject(); - - Object element = selection.get(0); - if (element instanceof SingleJcrNodeElem) { - SingleJcrNodeElem sjn = (SingleJcrNodeElem) element; - Node node = sjn.getNode(); - Session session = null; - String newName = null; - String oldPath = null; - try { - newName = SingleValue.ask("New node name", "Please provide a new name for [" + node.getName() + "]"); - // TODO sanity check and user feedback - newName = JcrUtils.replaceInvalidChars(newName); - oldPath = node.getPath(); - session = node.getSession(); - session.move(oldPath, JcrUtils.parentPath(oldPath) + "/" + newName); - session.save(); - - // Manually refresh the browser view. Must be enhanced - view.refresh(sjn); - } catch (RepositoryException e) { - throw new EclipseUiException("Unable to rename " + node + " to " + newName, e); - } - } - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/package-info.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/package-info.java deleted file mode 100644 index 4e075e2b1..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/handlers/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** JCR browser handlers. */ -package org.argeo.cms.e4.jcr.handlers; \ No newline at end of file diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/package-info.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/package-info.java deleted file mode 100644 index 3e92fb04d..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/jcr/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** JCR browser perspective. */ -package org.argeo.cms.e4.jcr; \ No newline at end of file diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/AbstractOsgiComposite.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/AbstractOsgiComposite.java deleted file mode 100644 index 4fd1d68dc..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/AbstractOsgiComposite.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.argeo.cms.e4.maintenance; - -import java.util.Collection; - -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.swt.CmsSwtUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Composite; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.framework.ServiceReference; - -abstract class AbstractOsgiComposite extends Composite { - private static final long serialVersionUID = -4097415973477517137L; - protected final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); - protected final CmsLog log = CmsLog.getLog(getClass()); - - public AbstractOsgiComposite(Composite parent, int style) { - super(parent, style); - parent.setLayout(CmsSwtUtils.noSpaceGridLayout()); - setLayout(CmsSwtUtils.noSpaceGridLayout()); - setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); - initUi(style); - } - - protected abstract void initUi(int style); - - protected T getService(Class clazz) { - return bc.getService(bc.getServiceReference(clazz)); - } - - protected Collection> getServiceReferences(Class clazz, String filter) { - try { - return bc.getServiceReferences(clazz, filter); - } catch (InvalidSyntaxException e) { - throw new IllegalArgumentException("Filter " + filter + " is invalid", e); - } - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/Browse.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/Browse.java deleted file mode 100644 index 260a114cd..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/Browse.java +++ /dev/null @@ -1,576 +0,0 @@ -package org.argeo.cms.e4.maintenance; - -import static org.eclipse.swt.SWT.RIGHT; - -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.LinkedHashMap; - -import javax.jcr.ItemNotFoundException; -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.Property; -import javax.jcr.PropertyIterator; -import javax.jcr.PropertyType; -import javax.jcr.RepositoryException; -import javax.jcr.Value; - -import org.argeo.api.cms.Cms2DSize; -import org.argeo.cms.CmsException; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.CmsUiProvider; -import org.argeo.cms.ui.util.CmsLink; -import org.argeo.cms.ui.widgets.EditableImage; -import org.argeo.cms.ui.widgets.Img; -import org.argeo.jcr.JcrUtils; -import org.eclipse.jface.viewers.ColumnLabelProvider; -import org.eclipse.jface.viewers.ILazyContentProvider; -import org.eclipse.jface.viewers.ISelectionChangedListener; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.SelectionChangedEvent; -import org.eclipse.jface.viewers.StructuredSelection; -import org.eclipse.jface.viewers.TableViewer; -import org.eclipse.jface.viewers.TableViewerColumn; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.ScrolledComposite; -import org.eclipse.swt.events.ControlAdapter; -import org.eclipse.swt.events.ControlEvent; -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.graphics.Point; -import org.eclipse.swt.graphics.Rectangle; -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.Label; -import org.eclipse.swt.widgets.Table; -import org.eclipse.swt.widgets.TableColumn; -import org.eclipse.swt.widgets.Text; - -public class Browse implements CmsUiProvider { - - // Some local constants to experiment. should be cleaned - private final static String BROWSE_PREFIX = "browse#"; - private final static int THUMBNAIL_WIDTH = 400; - private final static int COLUMN_WIDTH = 160; - private DateFormat timeFormatter = new SimpleDateFormat("dd-MM-yyyy', 'HH:mm"); - - // keep a cache of the opened nodes - // Key is the path - private LinkedHashMap browserCols = new LinkedHashMap(); - private Composite nodeDisplayParent; - private Composite colViewer; - private ScrolledComposite scrolledCmp; - private Text parentPathTxt; - private Text filterTxt; - private Node currEdited; - - private String initialPath; - - @Override - public Control createUi(Composite parent, Node context) throws RepositoryException { - if (context == null) - // return null; - throw new CmsException("Context cannot be null"); - GridLayout layout = CmsSwtUtils.noSpaceGridLayout(); - layout.numColumns = 2; - parent.setLayout(layout); - - // Left - Composite leftCmp = new Composite(parent, SWT.NO_FOCUS); - leftCmp.setLayoutData(CmsSwtUtils.fillAll()); - createBrowserPart(leftCmp, context); - - // Right - nodeDisplayParent = new Composite(parent, SWT.NO_FOCUS | SWT.BORDER); - GridData gd = new GridData(SWT.RIGHT, SWT.FILL, false, true); - gd.widthHint = THUMBNAIL_WIDTH; - nodeDisplayParent.setLayoutData(gd); - createNodeView(nodeDisplayParent, context); - - // INIT - setEdited(context); - initialPath = context.getPath(); - - // Workaround we don't yet manage the delete to display parent of the - // initial context node - - return null; - } - - private void createBrowserPart(Composite parent, Node context) throws RepositoryException { - GridLayout layout = CmsSwtUtils.noSpaceGridLayout(); - parent.setLayout(layout); - Composite filterCmp = new Composite(parent, SWT.NO_FOCUS); - filterCmp.setLayoutData(CmsSwtUtils.fillWidth()); - - // top filter - addFilterPanel(filterCmp); - - // scrolled composite - scrolledCmp = new ScrolledComposite(parent, SWT.H_SCROLL | SWT.BORDER | SWT.NO_FOCUS); - scrolledCmp.setLayoutData(CmsSwtUtils.fillAll()); - scrolledCmp.setExpandVertical(true); - scrolledCmp.setExpandHorizontal(true); - scrolledCmp.setShowFocusedControl(true); - - colViewer = new Composite(scrolledCmp, SWT.NO_FOCUS); - scrolledCmp.setContent(colViewer); - scrolledCmp.addControlListener(new ControlAdapter() { - private static final long serialVersionUID = 6589392045145698201L; - - @Override - public void controlResized(ControlEvent e) { - Rectangle r = scrolledCmp.getClientArea(); - scrolledCmp.setMinSize(colViewer.computeSize(SWT.DEFAULT, r.height)); - } - }); - initExplorer(colViewer, context); - } - - private Control initExplorer(Composite parent, Node context) throws RepositoryException { - parent.setLayout(CmsSwtUtils.noSpaceGridLayout()); - createBrowserColumn(parent, context); - return null; - } - - private Control createBrowserColumn(Composite parent, Node context) throws RepositoryException { - // TODO style is not correctly managed. - FilterEntitiesVirtualTable table = new FilterEntitiesVirtualTable(parent, SWT.BORDER | SWT.NO_FOCUS, context); - // CmsUiUtils.style(table, ArgeoOrgStyle.browserColumn.style()); - table.filterList("*"); - table.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, false, true)); - browserCols.put(context.getPath(), table); - return null; - } - - public void addFilterPanel(Composite parent) { - - parent.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(2, false))); - - // Text Area for the filter - parentPathTxt = new Text(parent, SWT.NO_FOCUS); - parentPathTxt.setEditable(false); - filterTxt = new Text(parent, SWT.SEARCH | SWT.ICON_CANCEL); - filterTxt.setMessage("Filter current list"); - filterTxt.setLayoutData(CmsSwtUtils.fillWidth()); - filterTxt.addModifyListener(new ModifyListener() { - private static final long serialVersionUID = 7709303319740056286L; - - public void modifyText(ModifyEvent event) { - modifyFilter(false); - } - }); - - 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; - FilterEntitiesVirtualTable currTable = null; - if (currEdited != null) { - FilterEntitiesVirtualTable table = browserCols.get(getPath(currEdited)); - if (table != null && !table.isDisposed()) - currTable = table; - } - - try { - if (e.keyCode == SWT.ARROW_DOWN) - currTable.setFocus(); - else if (e.keyCode == SWT.BS) { - if (filterTxt.getText().equals("") - && !(getPath(currEdited).equals("/") || getPath(currEdited).equals(initialPath))) { - setEdited(currEdited.getParent()); - e.doit = false; - filterTxt.setFocus(); - } - } else if (e.keyCode == SWT.TAB && !shiftPressed) { - if (currEdited.getNodes(filterTxt.getText() + "*").getSize() == 1) { - setEdited(currEdited.getNodes(filterTxt.getText() + "*").nextNode()); - } - filterTxt.setFocus(); - e.doit = false; - } - } catch (RepositoryException e1) { - throw new CmsException("Unexpected error in key management for " + currEdited + "with filter " - + filterTxt.getText(), e1); - } - - } - }); - } - - private void setEdited(Node node) { - try { - currEdited = node; - CmsSwtUtils.clear(nodeDisplayParent); - createNodeView(nodeDisplayParent, currEdited); - nodeDisplayParent.layout(); - refreshFilters(node); - refreshBrowser(node); - } catch (RepositoryException re) { - throw new CmsException("Unable to update browser for " + node, re); - } - } - - private void refreshFilters(Node node) throws RepositoryException { - String currNodePath = node.getPath(); - parentPathTxt.setText(currNodePath); - filterTxt.setText(""); - filterTxt.getParent().layout(); - } - - private void refreshBrowser(Node node) throws RepositoryException { - - // Retrieve - String currNodePath = node.getPath(); - String currParPath = ""; - if (!"/".equals(currNodePath)) - currParPath = JcrUtils.parentPath(currNodePath); - if ("".equals(currParPath)) - currParPath = "/"; - - Object[][] colMatrix = new Object[browserCols.size()][2]; - - int i = 0, j = -1, k = -1; - for (String path : browserCols.keySet()) { - colMatrix[i][0] = path; - colMatrix[i][1] = browserCols.get(path); - if (j >= 0 && k < 0 && !currNodePath.equals("/")) { - boolean leaveOpened = path.startsWith(currNodePath); - - // workaround for same name siblings - // fix me weird side effect when we go left or click on anb - // already selected, unfocused node - if (leaveOpened && (path.lastIndexOf("/") == 0 && currNodePath.lastIndexOf("/") == 0 - || JcrUtils.parentPath(path).equals(JcrUtils.parentPath(currNodePath)))) - leaveOpened = JcrUtils.lastPathElement(path).equals(JcrUtils.lastPathElement(currNodePath)); - - if (!leaveOpened) - k = i; - } - if (currParPath.equals(path)) - j = i; - i++; - } - - if (j >= 0 && k >= 0) - // remove useless cols - for (int l = i - 1; l >= k; l--) { - browserCols.remove(colMatrix[l][0]); - ((FilterEntitiesVirtualTable) colMatrix[l][1]).dispose(); - } - - // Remove disposed columns - // TODO investigate and fix the mechanism that leave them there after - // disposal - if (browserCols.containsKey(currNodePath)) { - FilterEntitiesVirtualTable currCol = browserCols.get(currNodePath); - if (currCol.isDisposed()) - browserCols.remove(currNodePath); - } - - if (!browserCols.containsKey(currNodePath)) - createBrowserColumn(colViewer, node); - - colViewer.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(browserCols.size(), false))); - // colViewer.pack(); - colViewer.layout(); - // also resize the scrolled composite - scrolledCmp.layout(); - scrolledCmp.getShowFocusedControl(); - // colViewer.getParent().layout(); - // if (JcrUtils.parentPath(currNodePath).equals(currBrowserKey)) { - // } else { - // } - } - - private void modifyFilter(boolean fromOutside) { - if (!fromOutside) - if (currEdited != null) { - String filter = filterTxt.getText() + "*"; - FilterEntitiesVirtualTable table = browserCols.get(getPath(currEdited)); - if (table != null && !table.isDisposed()) - table.filterList(filter); - } - - } - - private String getPath(Node node) { - try { - return node.getPath(); - } catch (RepositoryException e) { - throw new CmsException("Unable to get path for node " + node, e); - } - } - - private Cms2DSize imageWidth = new Cms2DSize(250, 0); - - /** - * Recreates the content of the box that displays information about the current - * selected node. - */ - private Control createNodeView(Composite parent, Node context) throws RepositoryException { - - parent.setLayout(new GridLayout(2, false)); - - if (isImg(context)) { - EditableImage image = new Img(parent, RIGHT, context, imageWidth); - image.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false, 2, 1)); - } - - // Name and primary type - Label contextL = new Label(parent, SWT.NONE); - CmsSwtUtils.markup(contextL); - contextL.setText("" + context.getName() + ""); - new Label(parent, SWT.NONE).setText(context.getPrimaryNodeType().getName()); - - // Children - for (NodeIterator nIt = context.getNodes(); nIt.hasNext();) { - Node child = nIt.nextNode(); - new CmsLink(child.getName(), BROWSE_PREFIX + child.getPath()).createUi(parent, context); - new Label(parent, SWT.NONE).setText(child.getPrimaryNodeType().getName()); - } - - // Properties - for (PropertyIterator pIt = context.getProperties(); pIt.hasNext();) { - Property property = pIt.nextProperty(); - Label label = new Label(parent, SWT.NONE); - label.setText(property.getName()); - label.setToolTipText(JcrUtils.getPropertyDefinitionAsString(property)); - new Label(parent, SWT.NONE).setText(getPropAsString(property)); - } - - return null; - } - - private boolean isImg(Node node) throws RepositoryException { - // TODO support images - return false; -// return node.hasNode(JCR_CONTENT) && node.isNodeType(CmsTypes.CMS_IMAGE); - } - - private String getPropAsString(Property property) throws RepositoryException { - String result = ""; - if (property.isMultiple()) { - result = getMultiAsString(property, ", "); - } else { - Value value = property.getValue(); - if (value.getType() == PropertyType.BINARY) - result = ""; - else if (value.getType() == PropertyType.DATE) - result = timeFormatter.format(value.getDate().getTime()); - else - result = value.getString(); - } - return result; - } - - private String getMultiAsString(Property property, String separator) throws RepositoryException { - if (separator == null) - separator = "; "; - Value[] values = property.getValues(); - StringBuilder builder = new StringBuilder(); - for (Value val : values) { - String currStr = val.getString(); - if (!"".equals(currStr.trim())) - builder.append(currStr).append(separator); - } - if (builder.lastIndexOf(separator) >= 0) - return builder.substring(0, builder.length() - separator.length()); - else - return builder.toString(); - } - - /** Almost canonical implementation of a table that display entities */ - private class FilterEntitiesVirtualTable extends Composite { - private static final long serialVersionUID = 8798147431706283824L; - - // Context - private Node context; - - // UI Objects - private TableViewer entityViewer; - - // enable management of multiple columns - Node getNode() { - return context; - } - - @Override - public boolean setFocus() { - if (entityViewer.getTable().isDisposed()) - return false; - if (entityViewer.getSelection().isEmpty()) { - Object first = entityViewer.getElementAt(0); - if (first != null) { - entityViewer.setSelection(new StructuredSelection(first), true); - } - } - return entityViewer.getTable().setFocus(); - } - - void filterList(String filter) { - try { - NodeIterator nit = context.getNodes(filter); - refreshFilteredList(nit); - } catch (RepositoryException e) { - throw new CmsException("Unable to filter " + getNode() + " children with filter " + filter, e); - } - - } - - public FilterEntitiesVirtualTable(Composite parent, int style, Node context) { - super(parent, SWT.NO_FOCUS); - this.context = context; - populate(); - } - - protected void populate() { - Composite parent = this; - GridLayout layout = CmsSwtUtils.noSpaceGridLayout(); - - this.setLayout(layout); - createTableViewer(parent); - } - - private void createTableViewer(final Composite parent) { - // the list - // We must limit the size of the table otherwise the full list is - // loaded - // before the layout happens - Composite listCmp = new Composite(parent, SWT.NO_FOCUS); - GridData gd = new GridData(SWT.LEFT, SWT.FILL, false, true); - gd.widthHint = COLUMN_WIDTH; - listCmp.setLayoutData(gd); - listCmp.setLayout(CmsSwtUtils.noSpaceGridLayout()); - - entityViewer = new TableViewer(listCmp, SWT.VIRTUAL | SWT.SINGLE); - Table table = entityViewer.getTable(); - - table.setLayoutData(CmsSwtUtils.fillAll()); - table.setLinesVisible(true); - table.setHeaderVisible(false); - CmsSwtUtils.markup(table); - - CmsSwtUtils.style(table, MaintenanceStyles.BROWSER_COLUMN); - - // first column - TableViewerColumn column = new TableViewerColumn(entityViewer, SWT.NONE); - TableColumn tcol = column.getColumn(); - tcol.setWidth(COLUMN_WIDTH); - tcol.setResizable(true); - column.setLabelProvider(new SimpleNameLP()); - - entityViewer.setContentProvider(new MyLazyCP(entityViewer)); - entityViewer.addSelectionChangedListener(new ISelectionChangedListener() { - - @Override - public void selectionChanged(SelectionChangedEvent event) { - IStructuredSelection selection = (IStructuredSelection) entityViewer.getSelection(); - if (selection.isEmpty()) - return; - else - setEdited((Node) selection.getFirstElement()); - - } - }); - - table.addKeyListener(new KeyListener() { - private static final long serialVersionUID = -330694313896036230L; - - @Override - public void keyReleased(KeyEvent e) { - } - - @Override - public void keyPressed(KeyEvent e) { - - IStructuredSelection selection = (IStructuredSelection) entityViewer.getSelection(); - Node selected = null; - if (!selection.isEmpty()) - selected = ((Node) selection.getFirstElement()); - try { - if (e.keyCode == SWT.ARROW_RIGHT) { - if (selected != null) { - setEdited(selected); - browserCols.get(selected.getPath()).setFocus(); - } - } else if (e.keyCode == SWT.ARROW_LEFT) { - try { - selected = getNode().getParent(); - String newPath = selected.getPath(); // getNode().getParent() - setEdited(selected); - if (browserCols.containsKey(newPath)) - browserCols.get(newPath).setFocus(); - } catch (ItemNotFoundException ie) { - // root silent - } - } - } catch (RepositoryException ie) { - throw new CmsException("Error while managing arrow " + "events in the browser for " + selected, - ie); - } - } - }); - } - - private class MyLazyCP implements ILazyContentProvider { - private static final long serialVersionUID = 1L; - private TableViewer viewer; - private Object[] elements; - - public MyLazyCP(TableViewer viewer) { - this.viewer = viewer; - } - - public void dispose() { - } - - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - // IMPORTANT: don't forget this: an exception will be thrown if - // a selected object is not part of the results anymore. - viewer.setSelection(null); - this.elements = (Object[]) newInput; - } - - public void updateElement(int index) { - viewer.replace(elements[index], index); - } - } - - protected void refreshFilteredList(NodeIterator children) { - Object[] rows = JcrUtils.nodeIteratorToList(children).toArray(); - entityViewer.setInput(rows); - entityViewer.setItemCount(rows.length); - entityViewer.refresh(); - } - - public class SimpleNameLP extends ColumnLabelProvider { - private static final long serialVersionUID = 2465059387875338553L; - - @Override - public String getText(Object element) { - if (element instanceof Node) { - Node curr = ((Node) element); - try { - return curr.getName(); - } catch (RepositoryException e) { - throw new CmsException("Unable to get name for" + curr); - } - } - return super.getText(element); - } - } - } -} \ No newline at end of file diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/ConnectivityDeploymentUi.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/ConnectivityDeploymentUi.java deleted file mode 100644 index 97f3e6713..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/ConnectivityDeploymentUi.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.argeo.cms.e4.maintenance; - -import org.argeo.cms.swt.CmsSwtUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Label; -import org.osgi.framework.ServiceReference; -import org.osgi.service.http.HttpService; -import org.osgi.service.useradmin.UserAdmin; - -class ConnectivityDeploymentUi extends AbstractOsgiComposite { - private static final long serialVersionUID = 590221539553514693L; - - public ConnectivityDeploymentUi(Composite parent, int style) { - super(parent, style); - } - - @Override - protected void initUi(int style) { - StringBuffer text = new StringBuffer(); - text.append("Provided Servers
"); - - ServiceReference userAdminRef = bc.getServiceReference(HttpService.class); - if (userAdminRef != null) { - // FIXME use constants - Object httpPort = userAdminRef.getProperty("http.port"); - Object httpsPort = userAdminRef.getProperty("https.port"); - if (httpPort != null) - text.append("http ").append(httpPort).append("
"); - if (httpsPort != null) - text.append("https ").append(httpsPort).append("
"); - - } - - text.append("
"); - text.append("Referenced Servers
"); - - Label label = new Label(this, SWT.NONE); - label.setData(new GridData(SWT.FILL, SWT.FILL, false, false)); - CmsSwtUtils.markup(label); - label.setText(text.toString()); - } - - protected boolean isDeployed() { - return bc.getServiceReference(UserAdmin.class) != null; - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DataDeploymentUi.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DataDeploymentUi.java deleted file mode 100644 index ef95bde64..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DataDeploymentUi.java +++ /dev/null @@ -1,139 +0,0 @@ -package org.argeo.cms.e4.maintenance; - -import java.io.File; -import java.io.IOException; -import java.nio.file.FileStore; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Collection; - -import org.apache.jackrabbit.core.RepositoryContext; -import org.apache.jackrabbit.core.config.RepositoryConfig; -import org.argeo.api.cms.CmsConstants; -import org.argeo.cms.swt.CmsSwtUtils; -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.Label; -import org.osgi.framework.ServiceReference; - -class DataDeploymentUi extends AbstractOsgiComposite { - private static final long serialVersionUID = 590221539553514693L; - - public DataDeploymentUi(Composite parent, int style) { - super(parent, style); - } - - @Override - protected void initUi(int style) { - if (isDeployed()) { - initCurrentUi(this); - } else { - initNewUi(this); - } - } - - private void initNewUi(Composite parent) { -// try { -// ConfigurationAdmin confAdmin = bc.getService(bc.getServiceReference(ConfigurationAdmin.class)); -// Configuration[] confs = confAdmin.listConfigurations( -// "(" + ConfigurationAdmin.SERVICE_FACTORYPID + "=" + NodeConstants.NODE_REPOS_FACTORY_PID + ")"); -// if (confs == null || confs.length == 0) { -// Group buttonGroup = new Group(parent, SWT.NONE); -// buttonGroup.setText("Repository Type"); -// buttonGroup.setLayout(new GridLayout(2, true)); -// buttonGroup.setLayoutData(new GridData(GridData.FILL_VERTICAL)); -// -// SelectionListener selectionListener = new SelectionAdapter() { -// private static final long serialVersionUID = 6247064348421088092L; -// -// public void widgetSelected(SelectionEvent event) { -// Button radio = (Button) event.widget; -// if (!radio.getSelection()) -// return; -// log.debug(event); -// JackrabbitType nodeType = (JackrabbitType) radio.getData(); -// if (log.isDebugEnabled()) -// log.debug(" selected = " + nodeType.name()); -// }; -// }; -// -// for (JackrabbitType nodeType : JackrabbitType.values()) { -// Button radio = new Button(buttonGroup, SWT.RADIO); -// radio.setText(nodeType.name()); -// radio.setData(nodeType); -// if (nodeType.equals(JackrabbitType.localfs)) -// radio.setSelection(true); -// radio.addSelectionListener(selectionListener); -// } -// -// } else if (confs.length == 1) { -// -// } else { -// throw new CmsException("Multiple repos not yet supported"); -// } -// } catch (Exception e) { -// throw new CmsException("Cannot initialize UI", e); -// } - - } - - private void initCurrentUi(Composite parent) { - parent.setLayout(new GridLayout()); - Collection> contexts = getServiceReferences(RepositoryContext.class, - "(" + CmsConstants.CN + "=*)"); - StringBuffer text = new StringBuffer(); - text.append("Jackrabbit Repositories
"); - for (ServiceReference sr : contexts) { - RepositoryContext repositoryContext = bc.getService(sr); - String alias = sr.getProperty(CmsConstants.CN).toString(); - String rootNodeId = repositoryContext.getRootNodeId().toString(); - RepositoryConfig repositoryConfig = repositoryContext.getRepositoryConfig(); - Path repoHomePath = new File(repositoryConfig.getHomeDir()).toPath().toAbsolutePath(); - // TODO check data store - - text.append("" + alias + "
"); - text.append("rootNodeId: " + rootNodeId + "
"); - try { - FileStore fileStore = Files.getFileStore(repoHomePath); - text.append("partition: " + fileStore.toString() + "
"); - text.append( - percentUsed(fileStore) + " used (" + humanReadable(fileStore.getUsableSpace()) + " free)
"); - } catch (IOException e) { - log.error("Cannot check fileStore for " + repoHomePath, e); - } - } - Label label = new Label(parent, SWT.NONE); - label.setData(new GridData(SWT.FILL, SWT.FILL, false, false)); - CmsSwtUtils.markup(label); - label.setText("" + text.toString() + ""); - } - - private String humanReadable(long bytes) { - long mb = bytes / (1024 * 1024); - return mb >= 2048 ? Long.toString(mb / 1024) + " GB" : Long.toString(mb) + " MB"; - } - - private String percentUsed(FileStore fs) throws IOException { - long used = fs.getTotalSpace() - fs.getUnallocatedSpace(); - long percent = used * 100 / fs.getTotalSpace(); - if (log.isTraceEnabled()) { - // output identical to `df -B 1`) - log.trace(fs.getTotalSpace() + "," + used + "," + fs.getUsableSpace()); - } - String span; - if (percent < 80) - span = ""; - else if (percent < 95) - span = ""; - else - span = ""; - return span + percent + "%"; - } - - protected boolean isDeployed() { - return bc.getServiceReference(RepositoryContext.class) != null; - } - -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DeploymentEntryPoint.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DeploymentEntryPoint.java deleted file mode 100644 index e713f53e1..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/DeploymentEntryPoint.java +++ /dev/null @@ -1,96 +0,0 @@ -package org.argeo.cms.e4.maintenance; - -import java.util.GregorianCalendar; -import java.util.TimeZone; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsContext; -import org.argeo.api.cms.CmsDeployment; -import org.argeo.api.cms.CmsState; -import org.argeo.cms.swt.CmsSwtUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.FillLayout; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Group; -import org.eclipse.swt.widgets.Label; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; -import org.osgi.framework.ServiceReference; - -class DeploymentEntryPoint { - private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); - - protected void createContents(Composite parent) { - // FIXME manage authentication if needed - // if (!CurrentUser.roles().contains(AuthConstants.ROLE_ADMIN)) - // return; - - // parent.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - if (isDesktop()) { - parent.setLayout(new GridLayout(2, true)); - } else { - // TODO add scrolling - parent.setLayout(new GridLayout(1, true)); - } - - initHighLevelSummary(parent); - - Group securityGroup = createHighLevelGroup(parent, "Security"); - securityGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); - new SecurityDeploymentUi(securityGroup, SWT.NONE); - - Group dataGroup = createHighLevelGroup(parent, "Data"); - dataGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); - new DataDeploymentUi(dataGroup, SWT.NONE); - - Group logGroup = createHighLevelGroup(parent, "Notifications"); - logGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true)); - new LogDeploymentUi(logGroup, SWT.NONE); - - Group connectivityGroup = createHighLevelGroup(parent, "Connectivity"); - new ConnectivityDeploymentUi(connectivityGroup, SWT.NONE); - connectivityGroup.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, true)); - - } - - private void initHighLevelSummary(Composite parent) { - Composite composite = new Composite(parent, SWT.NONE); - GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false); - if (isDesktop()) - gridData.horizontalSpan = 3; - composite.setLayoutData(gridData); - composite.setLayout(new FillLayout()); - - ServiceReference nodeStateRef = bc.getServiceReference(CmsState.class); - if (nodeStateRef == null) - throw new IllegalStateException("No CMS state available"); - CmsState nodeState = bc.getService(nodeStateRef); - ServiceReference nodeDeploymentRef = bc.getServiceReference(CmsContext.class); - Label label = new Label(composite, SWT.WRAP); - CmsSwtUtils.markup(label); - if (nodeDeploymentRef == null) { - label.setText("Not yet deployed on
" + nodeState.getHostname() + "
, please configure below."); - } else { - Object stateUuid = nodeStateRef.getProperty(CmsConstants.CN); - CmsContext nodeDeployment = bc.getService(nodeDeploymentRef); - GregorianCalendar calendar = new GregorianCalendar(); - calendar.setTimeInMillis(nodeDeployment.getAvailableSince()); - calendar.setTimeZone(TimeZone.getDefault()); - label.setText("[" + "" + nodeState.getHostname() + "]# " + "Deployment state " + stateUuid - + ", available since " + calendar.getTime() + ""); - } - } - - private static Group createHighLevelGroup(Composite parent, String text) { - Group group = new Group(parent, SWT.NONE); - group.setText(text); - CmsSwtUtils.markup(group); - return group; - } - - private boolean isDesktop() { - return true; - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/LogDeploymentUi.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/LogDeploymentUi.java deleted file mode 100644 index fa5d3dae3..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/LogDeploymentUi.java +++ /dev/null @@ -1,73 +0,0 @@ -package org.argeo.cms.e4.maintenance; - -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Enumeration; -import java.util.GregorianCalendar; -import java.util.TimeZone; - -import org.argeo.cms.swt.CmsSwtUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Text; -import org.osgi.service.log.LogEntry; -import org.osgi.service.log.LogListener; -import org.osgi.service.log.LogReaderService; - -class LogDeploymentUi extends AbstractOsgiComposite implements LogListener { - private static final long serialVersionUID = 590221539553514693L; - - private DateFormat dateFormat = new SimpleDateFormat("MMdd HH:mm"); - - private Display display; - private Text logDisplay; - - public LogDeploymentUi(Composite parent, int style) { - super(parent, style); - } - - @Override - protected void initUi(int style) { - LogReaderService logReader = getService(LogReaderService.class); - // FIXME use server push - // logReader.addLogListener(this); - this.display = getDisplay(); - this.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - logDisplay = new Text(this, SWT.WRAP | SWT.MULTI | SWT.READ_ONLY); - logDisplay.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - CmsSwtUtils.markup(logDisplay); - Enumeration logEntries = (Enumeration) logReader.getLog(); - while (logEntries.hasMoreElements()) - logDisplay.append(printEntry(logEntries.nextElement())); - } - - private String printEntry(LogEntry entry) { - StringBuilder sb = new StringBuilder(); - GregorianCalendar calendar = new GregorianCalendar(TimeZone.getDefault()); - calendar.setTimeInMillis(entry.getTime()); - sb.append(dateFormat.format(calendar.getTime())).append(' '); - sb.append(entry.getMessage()); - sb.append('\n'); - return sb.toString(); - } - - @Override - public void logged(LogEntry entry) { - if (display.isDisposed()) - return; - display.asyncExec(() -> { - if (logDisplay.isDisposed()) - return; - logDisplay.append(printEntry(entry)); - }); - display.wake(); - } - - // @Override - // public void dispose() { - // super.dispose(); - // getService(LogReaderService.class).removeLogListener(this); - // } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/MaintenanceStyles.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/MaintenanceStyles.java deleted file mode 100644 index df1be51ad..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/MaintenanceStyles.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.argeo.cms.e4.maintenance; - -/** Specific styles used by the various maintenance pages . */ -public interface MaintenanceStyles { - // General - public final static String PREFIX = "maintenance_"; - - // Browser - public final static String BROWSER_COLUMN = "browser_column"; - } diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/NonAdminPage.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/NonAdminPage.java deleted file mode 100644 index cb38ce899..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/NonAdminPage.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.argeo.cms.e4.maintenance; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.CmsUiProvider; -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.Label; - -public class NonAdminPage implements CmsUiProvider{ - - @Override - public Control createUi(Composite parent, Node context) - throws RepositoryException { - Composite body = new Composite(parent, SWT.NO_FOCUS); - body.setLayoutData(CmsSwtUtils.fillAll()); - body.setLayout(new GridLayout()); - Label label = new Label(body, SWT.NONE); - label.setText("You should be an admin to perform maintenance operations. " - + "Are you sure you are logged in?"); - label.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true)); - return null; - } - -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/SecurityDeploymentUi.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/SecurityDeploymentUi.java deleted file mode 100644 index 3492c5499..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/SecurityDeploymentUi.java +++ /dev/null @@ -1,85 +0,0 @@ -package org.argeo.cms.e4.maintenance; - -import java.net.URI; - -import org.argeo.cms.swt.CmsSwtUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Label; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.framework.ServiceReference; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.UserAdmin; - -class SecurityDeploymentUi extends AbstractOsgiComposite { - private static final long serialVersionUID = 590221539553514693L; - - public SecurityDeploymentUi(Composite parent, int style) { - super(parent, style); - } - - @Override - protected void initUi(int style) { - if (isDeployed()) { - initCurrentUi(this); - } else { - initNewUi(this); - } - } - - private void initNewUi(Composite parent) { - new Label(parent, SWT.NONE).setText("Security is not configured"); - } - - private void initCurrentUi(Composite parent) { - ServiceReference userAdminRef = bc.getServiceReference(UserAdmin.class); - UserAdmin userAdmin = bc.getService(userAdminRef); - StringBuffer text = new StringBuffer(); - text.append("Domains
"); - domains: for (String key : userAdminRef.getPropertyKeys()) { - if (!key.startsWith("/")) - continue domains; - URI uri; - try { - uri = new URI(key); - } catch (Exception e) { - // ignore non URI keys - continue domains; - } - - String rootDn = uri.getPath().substring(1, uri.getPath().length()); - // FIXME make reading query options more robust, using utils - boolean readOnly = uri.getQuery().equals("readOnly=true"); - if (readOnly) - text.append(""); - else - text.append(""); - - text.append(rootDn); - text.append("
"); - try { - Role[] roles = userAdmin.getRoles("(dn=*," + rootDn + ")"); - long userCount = 0; - long groupCount = 0; - for (Role role : roles) { - if (role.getType() == Role.USER) - userCount++; - else - groupCount++; - } - text.append(" " + userCount + " users, " + groupCount +" groups.
"); - } catch (InvalidSyntaxException e) { - log.error("Invalid syntax", e); - } - } - Label label = new Label(parent, SWT.NONE); - label.setData(new GridData(SWT.FILL, SWT.FILL, false, false)); - CmsSwtUtils.markup(label); - label.setText(text.toString()); - } - - protected boolean isDeployed() { - return bc.getServiceReference(UserAdmin.class) != null; - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/package-info.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/package-info.java deleted file mode 100644 index e4d2ad4c3..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/maintenance/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Maintenance perspective. */ -package org.argeo.cms.e4.maintenance; \ No newline at end of file diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/BundleNode.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/BundleNode.java deleted file mode 100644 index 962ad386e..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/BundleNode.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.argeo.cms.e4.monitoring; - -import org.argeo.eclipse.ui.TreeParent; -import org.osgi.framework.Bundle; -import org.osgi.framework.ServiceReference; - -/** A tree element representing a {@link Bundle} */ -class BundleNode extends TreeParent { - private final Bundle bundle; - - public BundleNode(Bundle bundle) { - this(bundle, false); - } - - @SuppressWarnings("rawtypes") - public BundleNode(Bundle bundle, boolean hasChildren) { - super(bundle.getSymbolicName()); - this.bundle = bundle; - - if (hasChildren) { - // REFERENCES - ServiceReference[] usedServices = bundle.getServicesInUse(); - if (usedServices != null) { - for (ServiceReference sr : usedServices) { - if (sr != null) - addChild(new ServiceReferenceNode(sr, false)); - } - } - - // SERVICES - ServiceReference[] registeredServices = bundle - .getRegisteredServices(); - if (registeredServices != null) { - for (ServiceReference sr : registeredServices) { - if (sr != null) - addChild(new ServiceReferenceNode(sr, true)); - } - } - } - - } - - Bundle getBundle() { - return bundle; - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/BundlesView.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/BundlesView.java deleted file mode 100644 index c639255b6..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/BundlesView.java +++ /dev/null @@ -1,114 +0,0 @@ -//package org.argeo.eclipse.ui.workbench.osgi; -//public class BundlesView {} - -package org.argeo.cms.e4.monitoring; - -import javax.annotation.PostConstruct; - -import org.argeo.eclipse.ui.ColumnViewerComparator; -import org.argeo.eclipse.ui.specific.EclipseUiSpecificUtils; -import org.eclipse.e4.ui.di.Focus; -import org.eclipse.jface.viewers.ColumnLabelProvider; -import org.eclipse.jface.viewers.IStructuredContentProvider; -import org.eclipse.jface.viewers.TableViewer; -import org.eclipse.jface.viewers.TableViewerColumn; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Composite; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; - -/** - * Overview of the bundles as a table. Equivalent to Equinox 'ss' console - * command. - */ -public class BundlesView { - private final static BundleContext bc = FrameworkUtil.getBundle(BundlesView.class).getBundleContext(); - private TableViewer viewer; - - @PostConstruct - public void createPartControl(Composite parent) { - viewer = new TableViewer(parent); - viewer.setContentProvider(new BundleContentProvider()); - viewer.getTable().setHeaderVisible(true); - - EclipseUiSpecificUtils.enableToolTipSupport(viewer); - - // ID - TableViewerColumn column = new TableViewerColumn(viewer, SWT.NONE); - column.getColumn().setWidth(30); - column.getColumn().setText("ID"); - column.getColumn().setAlignment(SWT.RIGHT); - column.setLabelProvider(new ColumnLabelProvider() { - private static final long serialVersionUID = -3122136344359358605L; - - public String getText(Object element) { - return Long.toString(((Bundle) element).getBundleId()); - } - }); - new ColumnViewerComparator(column); - - // State - column = new TableViewerColumn(viewer, SWT.NONE); - column.getColumn().setWidth(18); - column.getColumn().setText("State"); - column.setLabelProvider(new StateLabelProvider()); - new ColumnViewerComparator(column); - - // Symbolic name - column = new TableViewerColumn(viewer, SWT.NONE); - column.getColumn().setWidth(250); - column.getColumn().setText("Symbolic Name"); - column.setLabelProvider(new ColumnLabelProvider() { - private static final long serialVersionUID = -4280840684440451080L; - - public String getText(Object element) { - return ((Bundle) element).getSymbolicName(); - } - }); - new ColumnViewerComparator(column); - - // Version - column = new TableViewerColumn(viewer, SWT.NONE); - column.getColumn().setWidth(250); - column.getColumn().setText("Version"); - column.setLabelProvider(new ColumnLabelProvider() { - private static final long serialVersionUID = 6871926308708629989L; - - public String getText(Object element) { - Bundle bundle = (org.osgi.framework.Bundle) element; - return bundle.getVersion().toString(); - } - }); - new ColumnViewerComparator(column); - - viewer.setInput(bc); - - } - - @Focus - public void setFocus() { - if (viewer != null) - viewer.getControl().setFocus(); - } - - /** Content provider managing the array of bundles */ - private static class BundleContentProvider implements IStructuredContentProvider { - private static final long serialVersionUID = -8533792785725875977L; - - public Object[] getElements(Object inputElement) { - if (inputElement instanceof BundleContext) { - BundleContext bc = (BundleContext) inputElement; - return bc.getBundles(); - } - return null; - } - - public void dispose() { - } - - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - } - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/CmsSessionsView.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/CmsSessionsView.java deleted file mode 100644 index 8a3605095..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/CmsSessionsView.java +++ /dev/null @@ -1,173 +0,0 @@ -//package org.argeo.eclipse.ui.workbench.osgi; -//public class BundlesView {} - -package org.argeo.cms.e4.monitoring; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; - -import javax.annotation.PostConstruct; -import javax.naming.ldap.LdapName; - -import org.argeo.api.cms.CmsSession; -import org.argeo.eclipse.ui.ColumnViewerComparator; -import org.argeo.eclipse.ui.specific.EclipseUiSpecificUtils; -import org.argeo.util.LangUtils; -import org.eclipse.e4.ui.di.Focus; -import org.eclipse.jface.viewers.ColumnLabelProvider; -import org.eclipse.jface.viewers.IStructuredContentProvider; -import org.eclipse.jface.viewers.TableViewer; -import org.eclipse.jface.viewers.TableViewerColumn; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Composite; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.framework.ServiceReference; - -/** - * Overview of the active CMS sessions. - */ -public class CmsSessionsView { - private final static BundleContext bc = FrameworkUtil.getBundle(CmsSessionsView.class).getBundleContext(); - - private TableViewer viewer; - - @PostConstruct - public void createPartControl(Composite parent) { - viewer = new TableViewer(parent); - viewer.setContentProvider(new CmsSessionContentProvider()); - viewer.getTable().setHeaderVisible(true); - - EclipseUiSpecificUtils.enableToolTipSupport(viewer); - - int longColWidth = 150; - int smallColWidth = 100; - - // Display name - TableViewerColumn column = new TableViewerColumn(viewer, SWT.NONE); - column.getColumn().setWidth(longColWidth); - column.getColumn().setText("User"); - column.setLabelProvider(new ColumnLabelProvider() { - private static final long serialVersionUID = -5234573509093747505L; - - public String getText(Object element) { - return ((CmsSession) element).getDisplayName(); - } - - public String getToolTipText(Object element) { - return ((CmsSession) element).getUserDn().toString(); - } - }); - new ColumnViewerComparator(column); - - // Creation time - column = new TableViewerColumn(viewer, SWT.NONE); - column.getColumn().setWidth(smallColWidth); - column.getColumn().setText("Since"); - column.setLabelProvider(new ColumnLabelProvider() { - private static final long serialVersionUID = -5234573509093747505L; - - public String getText(Object element) { - return LangUtils.since(((CmsSession) element).getCreationTime()); - } - - public String getToolTipText(Object element) { - return ((CmsSession) element).getCreationTime().toString(); - } - }); - new ColumnViewerComparator(column); - - // Username - column = new TableViewerColumn(viewer, SWT.NONE); - column.getColumn().setWidth(smallColWidth); - column.getColumn().setText("Username"); - column.setLabelProvider(new ColumnLabelProvider() { - private static final long serialVersionUID = -5234573509093747505L; - - public String getText(Object element) { - LdapName userDn = ((CmsSession) element).getUserDn(); - return userDn.getRdn(userDn.size() - 1).getValue().toString(); - } - - public String getToolTipText(Object element) { - return ((CmsSession) element).getUserDn().toString(); - } - }); - new ColumnViewerComparator(column); - - // UUID - column = new TableViewerColumn(viewer, SWT.NONE); - column.getColumn().setWidth(smallColWidth); - column.getColumn().setText("UUID"); - column.setLabelProvider(new ColumnLabelProvider() { - private static final long serialVersionUID = -5234573509093747505L; - - public String getText(Object element) { - return ((CmsSession) element).getUuid().toString(); - } - - public String getToolTipText(Object element) { - return getText(element); - } - }); - new ColumnViewerComparator(column); - - // Local ID - column = new TableViewerColumn(viewer, SWT.NONE); - column.getColumn().setWidth(smallColWidth); - column.getColumn().setText("Local ID"); - column.setLabelProvider(new ColumnLabelProvider() { - private static final long serialVersionUID = -5234573509093747505L; - - public String getText(Object element) { - return ((CmsSession) element).getLocalId(); - } - - public String getToolTipText(Object element) { - return getText(element); - } - }); - new ColumnViewerComparator(column); - - viewer.setInput(bc); - - } - - @Focus - public void setFocus() { - if (viewer != null) - viewer.getControl().setFocus(); - } - - /** Content provider managing the array of bundles */ - private static class CmsSessionContentProvider implements IStructuredContentProvider { - private static final long serialVersionUID = -8533792785725875977L; - - public Object[] getElements(Object inputElement) { - if (inputElement instanceof BundleContext) { - BundleContext bc = (BundleContext) inputElement; - Collection> srs; - try { - srs = bc.getServiceReferences(CmsSession.class, null); - } catch (InvalidSyntaxException e) { - throw new IllegalArgumentException("Cannot retrieve CMS sessions", e); - } - List res = new ArrayList<>(); - for (ServiceReference sr : srs) { - res.add(bc.getService(sr)); - } - return res.toArray(); - } - return null; - } - - public void dispose() { - } - - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - } - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/ModulesView.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/ModulesView.java deleted file mode 100644 index f0d8c2952..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/ModulesView.java +++ /dev/null @@ -1,91 +0,0 @@ -package org.argeo.cms.e4.monitoring; - -import java.util.ArrayList; -import java.util.List; - -import javax.annotation.PostConstruct; - -import org.argeo.eclipse.ui.TreeParent; -import org.eclipse.e4.ui.di.Focus; -import org.eclipse.jface.viewers.ITreeContentProvider; -import org.eclipse.jface.viewers.TreeViewer; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Composite; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; - -/** The OSGi runtime from a module perspective. */ -public class ModulesView { - private final static BundleContext bc = FrameworkUtil.getBundle(ModulesView.class).getBundleContext(); - private TreeViewer viewer; - - @PostConstruct - public void createPartControl(Composite parent) { - viewer = new TreeViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); - viewer.setContentProvider(new ModulesContentProvider()); - viewer.setLabelProvider(new ModulesLabelProvider()); - viewer.setInput(bc); - } - - @Focus - public void setFocus() { - viewer.getTree().setFocus(); - } - - private class ModulesContentProvider implements ITreeContentProvider { - private static final long serialVersionUID = 3819934804640641721L; - - public Object[] getElements(Object inputElement) { - return getChildren(inputElement); - } - - public Object[] getChildren(Object parentElement) { - if (parentElement instanceof BundleContext) { - BundleContext bundleContext = (BundleContext) parentElement; - Bundle[] bundles = bundleContext.getBundles(); - - List modules = new ArrayList(); - for (Bundle bundle : bundles) { - if (bundle.getState() == Bundle.ACTIVE) - modules.add(new BundleNode(bundle, true)); - } - return modules.toArray(); - } else if (parentElement instanceof TreeParent) { - return ((TreeParent) parentElement).getChildren(); - } else { - return null; - } - } - - public Object getParent(Object element) { - // TODO Auto-generated method stub - return null; - } - - public boolean hasChildren(Object element) { - if (element instanceof TreeParent) { - return ((TreeParent) element).hasChildren(); - } - return false; - } - - public void dispose() { - } - - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - } - } - - private class ModulesLabelProvider extends StateLabelProvider { - private static final long serialVersionUID = 5290046145534824722L; - - @Override - public String getText(Object element) { - if (element instanceof BundleNode) - return element.toString() + " [" + ((BundleNode) element).getBundle().getBundleId() + "]"; - return element.toString(); - } - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/OsgiConfigurationsView.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/OsgiConfigurationsView.java deleted file mode 100644 index 759b3e955..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/OsgiConfigurationsView.java +++ /dev/null @@ -1,163 +0,0 @@ -package org.argeo.cms.e4.monitoring; - -import java.io.IOException; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.Dictionary; -import java.util.List; - -import javax.annotation.PostConstruct; - -import org.argeo.cms.CmsException; -import org.argeo.util.LangUtils; -import org.eclipse.jface.viewers.ColumnLabelProvider; -import org.eclipse.jface.viewers.ITreeContentProvider; -import org.eclipse.jface.viewers.TreeViewer; -import org.eclipse.jface.viewers.TreeViewerColumn; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.swt.SWT; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.widgets.Composite; -import org.osgi.framework.BundleContext; -import org.osgi.framework.Constants; -import org.osgi.framework.FrameworkUtil; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.service.cm.Configuration; -import org.osgi.service.cm.ConfigurationAdmin; - -public class OsgiConfigurationsView { - private final static BundleContext bc = FrameworkUtil.getBundle(OsgiConfigurationsView.class).getBundleContext(); - - @PostConstruct - public void createPartControl(Composite parent) { - ConfigurationAdmin configurationAdmin = bc.getService(bc.getServiceReference(ConfigurationAdmin.class)); - - TreeViewer viewer = new TreeViewer(parent); - // viewer.getTree().setHeaderVisible(true); - - TreeViewerColumn tvc = new TreeViewerColumn(viewer, SWT.NONE); - tvc.getColumn().setWidth(400); - tvc.setLabelProvider(new ColumnLabelProvider() { - private static final long serialVersionUID = 835407996597566763L; - - @Override - public String getText(Object element) { - if (element instanceof Configuration) { - return ((Configuration) element).getPid(); - } else if (element instanceof Prop) { - return ((Prop) element).key; - } - return super.getText(element); - } - - @Override - public Image getImage(Object element) { - if (element instanceof Configuration) - return OsgiExplorerImages.CONFIGURATION; - return null; - } - - }); - - tvc = new TreeViewerColumn(viewer, SWT.NONE); - tvc.getColumn().setWidth(400); - tvc.setLabelProvider(new ColumnLabelProvider() { - private static final long serialVersionUID = 6999659261190014687L; - - @Override - public String getText(Object element) { - if (element instanceof Configuration) { - // return ((Configuration) element).getFactoryPid(); - return null; - } else if (element instanceof Prop) { - return ((Prop) element).value.toString(); - } - return super.getText(element); - } - }); - - viewer.setContentProvider(new ConfigurationsContentProvider()); - viewer.setInput(configurationAdmin); - } - - static class ConfigurationsContentProvider implements ITreeContentProvider { - private static final long serialVersionUID = -4892768279440981042L; - private ConfigurationComparator configurationComparator = new ConfigurationComparator(); - - @Override - public void dispose() { - } - - @Override - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - } - - @Override - public Object[] getElements(Object inputElement) { - ConfigurationAdmin configurationAdmin = (ConfigurationAdmin) inputElement; - try { - Configuration[] configurations = configurationAdmin.listConfigurations(null); - Arrays.sort(configurations, configurationComparator); - return configurations; - } catch (IOException | InvalidSyntaxException e) { - throw new CmsException("Cannot list configurations", e); - } - } - - @Override - public Object[] getChildren(Object parentElement) { - if (parentElement instanceof Configuration) { - List res = new ArrayList<>(); - Configuration configuration = (Configuration) parentElement; - Dictionary props = configuration.getProperties(); - keys: for (String key : LangUtils.keys(props)) { - if (Constants.SERVICE_PID.equals(key)) - continue keys; - if (ConfigurationAdmin.SERVICE_FACTORYPID.equals(key)) - continue keys; - res.add(new Prop(configuration, key, props.get(key))); - } - return res.toArray(new Prop[res.size()]); - } - return null; - } - - @Override - public Object getParent(Object element) { - if (element instanceof Prop) - return ((Prop) element).configuration; - return null; - } - - @Override - public boolean hasChildren(Object element) { - if (element instanceof Configuration) - return true; - return false; - } - - } - - static class Prop { - final Configuration configuration; - final String key; - final Object value; - - public Prop(Configuration configuration, String key, Object value) { - this.configuration = configuration; - this.key = key; - this.value = value; - } - - } - - static class ConfigurationComparator implements Comparator { - - @Override - public int compare(Configuration o1, Configuration o2) { - return o1.getPid().compareTo(o2.getPid()); - } - - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/OsgiExplorerImages.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/OsgiExplorerImages.java deleted file mode 100644 index 7217fe612..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/OsgiExplorerImages.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.argeo.cms.e4.monitoring; - -import org.argeo.cms.ui.theme.CmsImages; -import org.eclipse.swt.graphics.Image; - -/** Shared icons. */ -public class OsgiExplorerImages extends CmsImages { - public final static Image INSTALLED = createIcon("installed.gif"); - public final static Image RESOLVED = createIcon("resolved.gif"); - public final static Image STARTING = createIcon("starting.gif"); - public final static Image ACTIVE = createIcon("active.gif"); - public final static Image SERVICE_PUBLISHED = createIcon("service_published.gif"); - public final static Image SERVICE_REFERENCED = createIcon("service_referenced.gif"); - public final static Image CONFIGURATION = createIcon("node.gif"); -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/ServiceReferenceNode.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/ServiceReferenceNode.java deleted file mode 100644 index d9c45fe11..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/ServiceReferenceNode.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.argeo.cms.e4.monitoring; - -import org.argeo.eclipse.ui.TreeParent; -import org.osgi.framework.Bundle; -import org.osgi.framework.ServiceReference; - -/** A tree element representing a {@link ServiceReference} */ -@SuppressWarnings({ "rawtypes" }) -class ServiceReferenceNode extends TreeParent { - private final ServiceReference serviceReference; - private final boolean published; - - public ServiceReferenceNode(ServiceReference serviceReference, - boolean published) { - super(serviceReference.toString()); - this.serviceReference = serviceReference; - this.published = published; - - if (isPublished()) { - Bundle[] usedBundles = serviceReference.getUsingBundles(); - if (usedBundles != null) { - for (Bundle b : usedBundles) { - if (b != null) - addChild(new BundleNode(b)); - } - } - } else { - Bundle provider = serviceReference.getBundle(); - addChild(new BundleNode(provider)); - } - - for (String key : serviceReference.getPropertyKeys()) { - addChild(new TreeParent(key + "=" - + serviceReference.getProperty(key))); - } - - } - - public ServiceReference getServiceReference() { - return serviceReference; - } - - public boolean isPublished() { - return published; - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/StateLabelProvider.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/StateLabelProvider.java deleted file mode 100644 index 5cb5b6563..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/StateLabelProvider.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.argeo.cms.e4.monitoring; - -import org.eclipse.jface.viewers.ColumnLabelProvider; -import org.eclipse.swt.graphics.Image; -import org.osgi.framework.Bundle; -import org.osgi.framework.Constants; - -/** Label provider showing the sate of bundles */ -class StateLabelProvider extends ColumnLabelProvider { - private static final long serialVersionUID = -7885583135316000733L; - - @Override - public Image getImage(Object element) { - int state; - if (element instanceof Bundle) - state = ((Bundle) element).getState(); - else if (element instanceof BundleNode) - state = ((BundleNode) element).getBundle().getState(); - else if (element instanceof ServiceReferenceNode) - if (((ServiceReferenceNode) element).isPublished()) - return OsgiExplorerImages.SERVICE_PUBLISHED; - else - return OsgiExplorerImages.SERVICE_REFERENCED; - else - return null; - - switch (state) { - case Bundle.UNINSTALLED: - return OsgiExplorerImages.INSTALLED; - case Bundle.INSTALLED: - return OsgiExplorerImages.INSTALLED; - case Bundle.RESOLVED: - return OsgiExplorerImages.RESOLVED; - case Bundle.STARTING: - return OsgiExplorerImages.STARTING; - case Bundle.STOPPING: - return OsgiExplorerImages.STARTING; - case Bundle.ACTIVE: - return OsgiExplorerImages.ACTIVE; - default: - return null; - } - } - - @Override - public String getText(Object element) { - return null; - } - - @Override - public String getToolTipText(Object element) { - Bundle bundle = (Bundle) element; - Integer state = bundle.getState(); - switch (state) { - case Bundle.UNINSTALLED: - return "UNINSTALLED"; - case Bundle.INSTALLED: - return "INSTALLED"; - case Bundle.RESOLVED: - return "RESOLVED"; - case Bundle.STARTING: - String activationPolicy = bundle.getHeaders() - .get(Constants.BUNDLE_ACTIVATIONPOLICY).toString(); - - // .get("Bundle-ActivationPolicy").toString(); - // FIXME constant triggers the compilation failure - if (activationPolicy != null - && activationPolicy.equals(Constants.ACTIVATION_LAZY)) - // && activationPolicy.equals("lazy")) - // FIXME constant triggers the compilation failure - // && activationPolicy.equals(Constants.ACTIVATION_LAZY)) - return "<>"; - return "STARTING"; - case Bundle.STOPPING: - return "STOPPING"; - case Bundle.ACTIVE: - return "ACTIVE"; - default: - return null; - } - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/package-info.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/package-info.java deleted file mode 100644 index 873bf3118..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/monitoring/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Monitoring perspective. */ -package org.argeo.cms.e4.monitoring; \ No newline at end of file diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/package-info.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/package-info.java deleted file mode 100644 index 233119c0d..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Eclipse 4 user interfaces. */ -package org.argeo.cms.e4; \ No newline at end of file diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/parts/EgoDashboard.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/parts/EgoDashboard.java deleted file mode 100644 index 5a805d1e9..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/parts/EgoDashboard.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.argeo.cms.e4.parts; - -import java.security.AccessController; -import java.time.ZonedDateTime; - -import javax.annotation.PostConstruct; -import javax.security.auth.Subject; - -import org.argeo.api.cms.CmsSession; -import org.argeo.cms.auth.CurrentUser; -import org.argeo.cms.osgi.CmsOsgiUtils; -import org.argeo.cms.swt.CmsSwtUtils; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; - -/** A canonical view of the logged in user. */ -public class EgoDashboard { - private BundleContext bc = FrameworkUtil.getBundle(EgoDashboard.class).getBundleContext(); - - @PostConstruct - public void createPartControl(Composite p) { - p.setLayout(new GridLayout()); - String username = CurrentUser.getUsername(); - - CmsSwtUtils.lbl(p, "" + CurrentUser.getDisplayName() + ""); - CmsSwtUtils.txt(p, username); - CmsSwtUtils.lbl(p, "Roles:"); - roles: for (String role : CurrentUser.roles()) { - if (username.equals(role)) - continue roles; - CmsSwtUtils.txt(p, role); - } - - Subject subject = Subject.getSubject(AccessController.getContext()); - if (subject != null) { - CmsSession cmsSession = CmsOsgiUtils.getCmsSession(bc, subject); - ZonedDateTime loggedIndSince = cmsSession.getCreationTime(); - CmsSwtUtils.lbl(p, "Session:"); - CmsSwtUtils.txt(p, cmsSession.getUuid().toString()); - CmsSwtUtils.lbl(p, "Logged in since:"); - CmsSwtUtils.txt(p, loggedIndSince.toString()); - } - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/AbstractRoleEditor.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/AbstractRoleEditor.java deleted file mode 100644 index 137f76242..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/AbstractRoleEditor.java +++ /dev/null @@ -1,287 +0,0 @@ -package org.argeo.cms.e4.users; - -import java.util.ArrayList; -import java.util.List; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import javax.inject.Inject; - -import org.argeo.cms.auth.UserAdminUtils; -import org.argeo.cms.ui.eclipse.forms.AbstractFormPart; -import org.argeo.cms.ui.eclipse.forms.IManagedForm; -import org.argeo.cms.ui.eclipse.forms.ManagedForm; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.argeo.util.naming.LdapAttrs; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.e4.ui.di.Persist; -import org.eclipse.e4.ui.model.application.ui.basic.MPart; -import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.ScrolledComposite; -import org.eclipse.swt.events.ModifyEvent; -import org.eclipse.swt.events.ModifyListener; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; -import org.osgi.service.useradmin.Authorization; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.User; -import org.osgi.service.useradmin.UserAdmin; -import org.osgi.service.useradmin.UserAdminEvent; - -/** Editor for a user, might be a user or a group. */ -public abstract class AbstractRoleEditor { - - // public final static String USER_EDITOR_ID = WorkbenchUiPlugin.PLUGIN_ID + - // ".userEditor"; - // public final static String GROUP_EDITOR_ID = WorkbenchUiPlugin.PLUGIN_ID + - // ".groupEditor"; - - /* DEPENDENCY INJECTION */ - @Inject - protected UserAdminWrapper userAdminWrapper; - - @Inject - private MPart mPart; - - // @Inject - // Composite parent; - - private UserAdmin userAdmin; - - // Context - private User user; - private String username; - - private NameChangeListener listener; - - private ManagedForm managedForm; - - // public void init(IEditorSite site, IEditorInput input) throws - // PartInitException { - @PostConstruct - public void init(Composite parent) { - this.userAdmin = userAdminWrapper.getUserAdmin(); - username = mPart.getPersistedState().get(LdapAttrs.uid.name()); - user = (User) userAdmin.getRole(username); - - listener = new NameChangeListener(Display.getCurrent()); - userAdminWrapper.addListener(listener); - updateEditorTitle(null); - - managedForm = new ManagedForm(parent) { - - @Override - public void staleStateChanged() { - refresh(); - } - }; - ScrolledComposite scrolled = managedForm.getForm(); - Composite body = new Composite(scrolled, SWT.NONE); - scrolled.setContent(body); - createUi(body); - managedForm.refresh(); - } - - abstract void createUi(Composite parent); - - /** - * returns the list of all authorizations for the given user or of the current - * displayed user if parameter is null - */ - protected List getFlatGroups(User aUser) { - Authorization currAuth; - if (aUser == null) - currAuth = userAdmin.getAuthorization(this.user); - else - currAuth = userAdmin.getAuthorization(aUser); - - String[] roles = currAuth.getRoles(); - - List groups = new ArrayList(); - for (String roleStr : roles) { - User currRole = (User) userAdmin.getRole(roleStr); - if (currRole != null && !groups.contains(currRole)) - groups.add(currRole); - } - return groups; - } - - protected IManagedForm getManagedForm() { - return managedForm; - } - - /** Exposes the user (or group) that is displayed by the current editor */ - protected User getDisplayedUser() { - return user; - } - - private void setDisplayedUser(User user) { - this.user = user; - } - - void updateEditorTitle(String title) { - if (title == null) { - String commonName = UserAdminUtils.getProperty(user, LdapAttrs.cn.name()); - title = "".equals(commonName) ? user.getName() : commonName; - } - setPartName(title); - } - - protected void setPartName(String name) { - mPart.setLabel(name); - } - - // protected void addPages() { - // try { - // if (user.getType() == Role.GROUP) - // addPage(new GroupMainPage(this, userAdminWrapper, repository, nodeInstance)); - // else - // addPage(new UserMainPage(this, userAdminWrapper)); - // } catch (Exception e) { - // throw new CmsException("Cannot add pages", e); - // } - // } - - @Persist - public void doSave(IProgressMonitor monitor) { - userAdminWrapper.beginTransactionIfNeeded(); - commitPages(true); - userAdminWrapper.commitOrNotifyTransactionStateChange(); - // firePropertyChange(PROP_DIRTY); - userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_REMOVED, user)); - } - - protected void commitPages(boolean b) { - managedForm.commit(b); - } - - @PreDestroy - public void dispose() { - userAdminWrapper.removeListener(listener); - managedForm.dispose(); - } - - // CONTROLERS FOR THIS EDITOR AND ITS PAGES - - class NameChangeListener extends UiUserAdminListener { - public NameChangeListener(Display display) { - super(display); - } - - @Override - public void roleChangedToUiThread(UserAdminEvent event) { - Role changedRole = event.getRole(); - if (changedRole == null || changedRole.equals(user)) { - updateEditorTitle(null); - User reloadedUser = (User) userAdminWrapper.getUserAdmin().getRole(user.getName()); - setDisplayedUser(reloadedUser); - } - } - } - - class MainInfoListener extends UiUserAdminListener { - private final AbstractFormPart part; - - public MainInfoListener(Display display, AbstractFormPart part) { - super(display); - this.part = part; - } - - @Override - public void roleChangedToUiThread(UserAdminEvent event) { - // Rollback - if (event.getRole() == null) - part.markStale(); - } - } - - class GroupChangeListener extends UiUserAdminListener { - private final AbstractFormPart part; - - public GroupChangeListener(Display display, AbstractFormPart part) { - super(display); - this.part = part; - } - - @Override - public void roleChangedToUiThread(UserAdminEvent event) { - // always mark as stale - part.markStale(); - } - } - - /** Registers a listener that will notify this part */ - class FormPartML implements ModifyListener { - private static final long serialVersionUID = 6299808129505381333L; - private AbstractFormPart formPart; - - public FormPartML(AbstractFormPart generalPart) { - this.formPart = generalPart; - } - - public void modifyText(ModifyEvent e) { - // Discard event when the control does not have the focus, typically - // to avoid all editors being marked as dirty during a Rollback - if (((Control) e.widget).isFocusControl()) - formPart.markDirty(); - } - } - - /* DEPENDENCY INJECTION */ - public void setUserAdminWrapper(UserAdminWrapper userAdminWrapper) { - this.userAdminWrapper = userAdminWrapper; - } - - /** Creates label and multiline text. */ - Text createLMT(Composite parent, String label, String value) { - Label lbl = new Label(parent, SWT.NONE); - lbl.setText(label); - lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false)); - Text text = new Text(parent, SWT.NONE); - text.setText(value); - text.setLayoutData(new GridData(SWT.LEAD, SWT.FILL, true, true)); - return text; - } - - /** Creates label and password. */ - Text createLP(Composite parent, String label, String value) { - Label lbl = new Label(parent, SWT.NONE); - lbl.setText(label); - lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false)); - Text text = new Text(parent, SWT.PASSWORD | SWT.BORDER); - text.setText(value); - text.setLayoutData(new GridData(SWT.LEAD, SWT.FILL, true, false)); - return text; - } - - /** Creates label and text. */ - Text createLT(Composite parent, String label, String value) { - Label lbl = new Label(parent, SWT.NONE); - lbl.setText(label); - lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false)); - lbl.setFont(EclipseUiUtils.getBoldFont(parent)); - Text text = new Text(parent, SWT.BORDER); - text.setText(value); - text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); - // CmsUiUtils.style(text, CmsWorkbenchStyles.WORKBENCH_FORM_TEXT); - return text; - } - - Text createReadOnlyLT(Composite parent, String label, String value) { - Label lbl = new Label(parent, SWT.NONE); - lbl.setText(label); - lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false)); - lbl.setFont(EclipseUiUtils.getBoldFont(parent)); - Text text = new Text(parent, SWT.NONE); - text.setText(value); - text.setLayoutData(new GridData(SWT.LEAD, SWT.FILL, true, false)); - text.setEditable(false); - // CmsUiUtils.style(text, CmsWorkbenchStyles.WORKBENCH_FORM_TEXT); - return text; - } - -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/CmsWorkbenchStyles.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/CmsWorkbenchStyles.java deleted file mode 100644 index 07df312e1..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/CmsWorkbenchStyles.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.argeo.cms.e4.users; - -/** Centralize the declaration of Workbench specific CSS Styles */ -interface CmsWorkbenchStyles { - - // Specific People layouting - String WORKBENCH_FORM_TEXT = "workbench_form_text"; -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupEditor.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupEditor.java deleted file mode 100644 index a011c5f62..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupEditor.java +++ /dev/null @@ -1,566 +0,0 @@ -package org.argeo.cms.e4.users; - -import static org.argeo.api.cms.CmsContext.WORKGROUP; -import static org.argeo.cms.auth.UserAdminUtils.setProperty; -import static org.argeo.util.naming.LdapAttrs.businessCategory; -import static org.argeo.util.naming.LdapAttrs.description; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import javax.annotation.PreDestroy; -import javax.inject.Inject; -import javax.jcr.Node; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsContext; -import org.argeo.cms.auth.UserAdminUtils; -import org.argeo.cms.e4.users.providers.CommonNameLP; -import org.argeo.cms.e4.users.providers.MailLP; -import org.argeo.cms.e4.users.providers.RoleIconLP; -import org.argeo.cms.e4.users.providers.UserFilter; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.eclipse.forms.AbstractFormPart; -import org.argeo.cms.ui.eclipse.forms.IManagedForm; -import org.argeo.eclipse.ui.ColumnDefinition; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.argeo.eclipse.ui.parts.LdifUsersTable; -import org.argeo.jcr.JcrException; -import org.argeo.jcr.JcrUtils; -import org.argeo.osgi.transaction.WorkTransaction; -import org.argeo.util.naming.LdapAttrs; -import org.eclipse.e4.ui.workbench.modeling.EPartService; -import org.eclipse.jface.action.Action; -import org.eclipse.jface.action.ToolBarManager; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.jface.resource.ImageDescriptor; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.TableViewer; -import org.eclipse.jface.viewers.ViewerDropAdapter; -import org.eclipse.swt.SWT; -import org.eclipse.swt.dnd.DND; -import org.eclipse.swt.dnd.DropTargetEvent; -import org.eclipse.swt.dnd.TextTransfer; -import org.eclipse.swt.dnd.Transfer; -import org.eclipse.swt.dnd.TransferData; -import org.eclipse.swt.events.ModifyListener; -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.Composite; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Link; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; -import org.eclipse.swt.widgets.ToolBar; -import org.osgi.service.useradmin.Group; -import org.osgi.service.useradmin.Role; -//import org.eclipse.ui.forms.AbstractFormPart; -//import org.eclipse.ui.forms.IManagedForm; -//import org.eclipse.ui.forms.SectionPart; -//import org.eclipse.ui.forms.editor.FormEditor; -//import org.eclipse.ui.forms.editor.FormPage; -//import org.eclipse.ui.forms.widgets.FormToolkit; -//import org.eclipse.ui.forms.widgets.ScrolledForm; -//import org.eclipse.ui.forms.widgets.Section; -import org.osgi.service.useradmin.User; -import org.osgi.service.useradmin.UserAdmin; -import org.osgi.service.useradmin.UserAdminEvent; - -/** Display/edit main properties of a given group */ -public class GroupEditor extends AbstractRoleEditor { - // final static String ID = "GroupEditor.mainPage"; - - @Inject - private EPartService partService; - - // private final UserEditor editor; - @Inject - private Repository repository; - @Inject - private CmsContext nodeInstance; - // private final UserAdminWrapper userAdminWrapper; - private Session groupsSession; - - // public GroupMainPage(FormEditor editor, UserAdminWrapper userAdminWrapper, - // Repository repository, - // NodeInstance nodeInstance) { - // super(editor, ID, "Main"); - // try { - // session = repository.login(); - // } catch (RepositoryException e) { - // throw new CmsException("Cannot retrieve session of in MainGroupPage - // constructor", e); - // } - // this.editor = (UserEditor) editor; - // this.userAdminWrapper = userAdminWrapper; - // this.nodeInstance = nodeInstance; - // } - - // protected void createFormContent(final IManagedForm mf) { - // ScrolledForm form = mf.getForm(); - // Composite body = form.getBody(); - // GridLayout mainLayout = new GridLayout(); - // body.setLayout(mainLayout); - // Group group = (Group) editor.getDisplayedUser(); - // appendOverviewPart(body, group); - // appendMembersPart(body, group); - // } - - @Override - protected void createUi(Composite parent) { - try { - groupsSession = repository.login(CmsConstants.SRV_WORKSPACE); - } catch (RepositoryException e) { - throw new JcrException("Cannot retrieve session", e); - } - // ScrolledForm form = mf.getForm(); - // Composite body = form.getBody(); - // Composite body = new Composite(parent, SWT.NONE); - Composite body = parent; - GridLayout mainLayout = new GridLayout(); - body.setLayout(mainLayout); - Group group = (Group) getDisplayedUser(); - appendOverviewPart(body, group); - appendMembersPart(body, group); - } - - @PreDestroy - public void dispose() { - JcrUtils.logoutQuietly(groupsSession); - super.dispose(); - } - - /** Creates the general section */ - protected void appendOverviewPart(final Composite parent, final Group group) { - Composite body = new Composite(parent, SWT.NONE); - // GridLayout layout = new GridLayout(5, false); - GridLayout layout = new GridLayout(2, false); - body.setLayout(layout); - body.setLayoutData(CmsSwtUtils.fillWidth()); - - String cn = UserAdminUtils.getProperty(group, LdapAttrs.cn.name()); - createReadOnlyLT(body, "Name", cn); - createReadOnlyLT(body, "DN", group.getName()); - createReadOnlyLT(body, "Domain", UserAdminUtils.getDomainName(group)); - - // Description - Label descLbl = new Label(body, SWT.LEAD); - descLbl.setFont(EclipseUiUtils.getBoldFont(body)); - descLbl.setText("Description"); - descLbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, true, false, 2, 1)); - final Text descTxt = new Text(body, SWT.LEAD | SWT.MULTI | SWT.WRAP | SWT.BORDER); - GridData gd = EclipseUiUtils.fillWidth(); - gd.heightHint = 50; - gd.horizontalSpan = 2; - descTxt.setLayoutData(gd); - - // Mark as workgroup - Link markAsWorkgroupLk = new Link(body, SWT.NONE); - markAsWorkgroupLk.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 2, 1)); - - // create form part (controller) - final AbstractFormPart part = new AbstractFormPart() { - - private MainInfoListener listener; - - @Override - public void initialize(IManagedForm form) { - super.initialize(form); - listener = new MainInfoListener(parent.getDisplay(), this); - userAdminWrapper.addListener(listener); - } - - @Override - public void dispose() { - userAdminWrapper.removeListener(listener); - super.dispose(); - } - - public void commit(boolean onSave) { - // group.getProperties().put(LdapAttrs.description.name(), descTxt.getText()); - setProperty(group, description, descTxt.getText()); - super.commit(onSave); - } - - @Override - public void refresh() { - // dnTxt.setText(group.getName()); - // cnTxt.setText(UserAdminUtils.getProperty(group, LdapAttrs.cn.name())); - descTxt.setText(UserAdminUtils.getProperty(group, LdapAttrs.description.name())); - Node workgroupHome = CmsJcrUtils.getGroupHome(groupsSession, cn); - if (workgroupHome == null) - markAsWorkgroupLk.setText("Mark as workgroup"); - else - markAsWorkgroupLk.setText("Configured as workgroup"); - parent.layout(true, true); - super.refresh(); - } - }; - - markAsWorkgroupLk.addSelectionListener(new SelectionAdapter() { - private static final long serialVersionUID = -6439340898096365078L; - - @Override - public void widgetSelected(SelectionEvent e) { - - boolean confirmed = MessageDialog.openConfirm(parent.getShell(), "Mark as workgroup", - "Are you sure you want to mark " + cn + " as being a workgroup? "); - if (confirmed) { - Node workgroupHome = CmsJcrUtils.getGroupHome(groupsSession, cn); - if (workgroupHome != null) - return; // already marked as workgroup, do nothing - else { - // improve transaction management - userAdminWrapper.beginTransactionIfNeeded(); - nodeInstance.createWorkgroup(group.getName()); - setProperty(group, businessCategory, WORKGROUP); - userAdminWrapper.commitOrNotifyTransactionStateChange(); - userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, group)); - part.refresh(); - } - } - } - }); - - ModifyListener defaultListener = new FormPartML(part); - descTxt.addModifyListener(defaultListener); - getManagedForm().addPart(part); - } - - /** Filtered table with members. Has drag and drop ability */ - protected void appendMembersPart(Composite parent, Group group) { - // Section section = tk.createSection(parent, Section.TITLE_BAR); - // section.setText("Members"); - // section.setLayoutData(EclipseUiUtils.fillAll()); - - Composite body = new Composite(parent, SWT.BORDER); - body.setLayout(new GridLayout()); - // section.setClient(body); - body.setLayoutData(EclipseUiUtils.fillAll()); - - // Define the displayed columns - List columnDefs = new ArrayList(); - columnDefs.add(new ColumnDefinition(new RoleIconLP(), "", 0, 24)); - columnDefs.add(new ColumnDefinition(new CommonNameLP(), "Name", 150)); - columnDefs.add(new ColumnDefinition(new MailLP(), "Mail", 150)); - // columnDefs.add(new ColumnDefinition(new UserNameLP(), "Distinguished Name", - // 240)); - - // Create and configure the table - LdifUsersTable userViewerCmp = new MyUserTableViewer(body, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL, - userAdminWrapper.getUserAdmin()); - - userViewerCmp.setColumnDefinitions(columnDefs); - userViewerCmp.populate(true, false); - userViewerCmp.setLayoutData(EclipseUiUtils.fillAll()); - - // Controllers - TableViewer userViewer = userViewerCmp.getTableViewer(); - userViewer.addDoubleClickListener(new UserTableDefaultDClickListener(partService)); - int operations = DND.DROP_COPY | DND.DROP_MOVE; - Transfer[] tt = new Transfer[] { TextTransfer.getInstance() }; - userViewer.addDropSupport(operations, tt, - new GroupDropListener(userAdminWrapper, userViewerCmp, (Group) getDisplayedUser())); - - AbstractFormPart part = new GroupMembersPart(userViewerCmp); - getManagedForm().addPart(part); - - // remove button - // addRemoveAbility(toolBarManager, userViewerCmp.getTableViewer(), group); - Action action = new RemoveMembershipAction(userViewer, group, "Remove selected items from this group", - SecurityAdminImages.ICON_REMOVE_DESC); - - ToolBarManager toolBarManager = new ToolBarManager(SWT.FLAT); - ToolBar toolBar = toolBarManager.createControl(body); - toolBar.setLayoutData(CmsSwtUtils.fillWidth()); - - toolBarManager.add(action); - toolBarManager.update(true); - - } - - // private LdifUsersTable createMemberPart(Composite parent, Group group) { - // - // // Define the displayed columns - // List columnDefs = new ArrayList(); - // columnDefs.add(new ColumnDefinition(new RoleIconLP(), "", 0, 24)); - // columnDefs.add(new ColumnDefinition(new CommonNameLP(), "Name", 150)); - // columnDefs.add(new ColumnDefinition(new MailLP(), "Mail", 150)); - // // columnDefs.add(new ColumnDefinition(new UserNameLP(), "Distinguished - // Name", - // // 240)); - // - // // Create and configure the table - // LdifUsersTable userViewerCmp = new MyUserTableViewer(parent, SWT.MULTI | - // SWT.H_SCROLL | SWT.V_SCROLL, - // userAdminWrapper.getUserAdmin()); - // - // userViewerCmp.setColumnDefinitions(columnDefs); - // userViewerCmp.populate(true, false); - // userViewerCmp.setLayoutData(EclipseUiUtils.fillAll()); - // - // // Controllers - // TableViewer userViewer = userViewerCmp.getTableViewer(); - // userViewer.addDoubleClickListener(new - // UserTableDefaultDClickListener(partService)); - // int operations = DND.DROP_COPY | DND.DROP_MOVE; - // Transfer[] tt = new Transfer[] { TextTransfer.getInstance() }; - // userViewer.addDropSupport(operations, tt, - // new GroupDropListener(userAdminWrapper, userViewerCmp, (Group) - // getDisplayedUser())); - // - // // userViewerCmp.refresh(); - // return userViewerCmp; - // } - - // Local viewers - private class MyUserTableViewer extends LdifUsersTable { - private static final long serialVersionUID = 8467999509931900367L; - - private final UserFilter userFilter; - - public MyUserTableViewer(Composite parent, int style, UserAdmin userAdmin) { - super(parent, style, true); - userFilter = new UserFilter(); - - } - - @Override - protected List listFilteredElements(String filter) { - // reload user and set it in the editor - Group group = (Group) getDisplayedUser(); - Role[] roles = group.getMembers(); - List users = new ArrayList(); - userFilter.setSearchText(filter); - // userFilter.setShowSystemRole(true); - for (Role role : roles) - // if (role.getType() == Role.GROUP) - if (userFilter.select(null, null, role)) - users.add((User) role); - return users; - } - } - - // private void addRemoveAbility(ToolBarManager toolBarManager, TableViewer - // userViewer, Group group) { - // // Section section = sectionPart.getSection(); - // // ToolBarManager toolBarManager = new ToolBarManager(SWT.FLAT); - // // ToolBar toolbar = toolBarManager.createControl(parent); - // // ToolBar toolbar = toolBarManager.getControl(); - // // final Cursor handCursor = new Cursor(toolbar.getDisplay(), - // SWT.CURSOR_HAND); - // // toolbar.setCursor(handCursor); - // // toolbar.addDisposeListener(new DisposeListener() { - // // private static final long serialVersionUID = 3882131405820522925L; - // // - // // public void widgetDisposed(DisposeEvent e) { - // // if ((handCursor != null) && (handCursor.isDisposed() == false)) { - // // handCursor.dispose(); - // // } - // // } - // // }); - // - // Action action = new RemoveMembershipAction(userViewer, group, "Remove - // selected items from this group", - // SecurityAdminImages.ICON_REMOVE_DESC); - // toolBarManager.add(action); - // toolBarManager.update(true); - // // section.setTextClient(toolbar); - // } - - private class RemoveMembershipAction extends Action { - private static final long serialVersionUID = -1337713097184522588L; - - private final TableViewer userViewer; - private final Group group; - - RemoveMembershipAction(TableViewer userViewer, Group group, String name, ImageDescriptor img) { - super(name, img); - this.userViewer = userViewer; - this.group = group; - } - - @Override - public void run() { - ISelection selection = userViewer.getSelection(); - if (selection.isEmpty()) - return; - - @SuppressWarnings("unchecked") - Iterator it = ((IStructuredSelection) selection).iterator(); - List users = new ArrayList(); - while (it.hasNext()) { - User currUser = it.next(); - users.add(currUser); - } - - userAdminWrapper.beginTransactionIfNeeded(); - for (User user : users) { - group.removeMember(user); - } - userAdminWrapper.commitOrNotifyTransactionStateChange(); - userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, group)); - } - } - - // LOCAL CONTROLLERS - private class GroupMembersPart extends AbstractFormPart { - private final LdifUsersTable userViewer; - // private final Group group; - - private GroupChangeListener listener; - - public GroupMembersPart(LdifUsersTable userViewer) { - // super(section); - this.userViewer = userViewer; - // this.group = group; - } - - @Override - public void initialize(IManagedForm form) { - super.initialize(form); - listener = new GroupChangeListener(userViewer.getDisplay(), GroupMembersPart.this); - userAdminWrapper.addListener(listener); - } - - @Override - public void dispose() { - userAdminWrapper.removeListener(listener); - super.dispose(); - } - - @Override - public void refresh() { - userViewer.refresh(); - super.refresh(); - } - } - - /** - * Defines this table as being a potential target to add group membership - * (roles) to this group - */ - private class GroupDropListener extends ViewerDropAdapter { - private static final long serialVersionUID = 2893468717831451621L; - - private final UserAdminWrapper userAdminWrapper; - // private final LdifUsersTable myUserViewerCmp; - private final Group myGroup; - - public GroupDropListener(UserAdminWrapper userAdminWrapper, LdifUsersTable userTableViewerCmp, Group group) { - super(userTableViewerCmp.getTableViewer()); - this.userAdminWrapper = userAdminWrapper; - this.myGroup = group; - // this.myUserViewerCmp = userTableViewerCmp; - } - - @Override - public boolean validateDrop(Object target, int operation, TransferData transferType) { - // Target is always OK in a list only view - // TODO check if not a string - boolean validDrop = true; - return validDrop; - } - - @Override - public void drop(DropTargetEvent event) { - // TODO Is there an opportunity to perform the check before? - String newUserName = (String) event.data; - UserAdmin myUserAdmin = userAdminWrapper.getUserAdmin(); - Role role = myUserAdmin.getRole(newUserName); - if (role.getType() == Role.GROUP) { - Group newGroup = (Group) role; - Shell shell = getViewer().getControl().getShell(); - // Sanity checks - if (myGroup == newGroup) { // Equality - MessageDialog.openError(shell, "Forbidden addition ", "A group cannot be a member of itself."); - return; - } - - // Cycle - String myName = myGroup.getName(); - List myMemberships = getFlatGroups(myGroup); - if (myMemberships.contains(newGroup)) { - MessageDialog.openError(shell, "Forbidden addition: cycle", - "Cannot add " + newUserName + " to group " + myName + ". This would create a cycle"); - return; - } - - // Already member - List newGroupMemberships = getFlatGroups(newGroup); - if (newGroupMemberships.contains(myGroup)) { - MessageDialog.openError(shell, "Forbidden addition", - "Cannot add " + newUserName + " to group " + myName + ", this membership already exists"); - return; - } - userAdminWrapper.beginTransactionIfNeeded(); - myGroup.addMember(newGroup); - userAdminWrapper.commitOrNotifyTransactionStateChange(); - userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, myGroup)); - } else if (role.getType() == Role.USER) { - // TODO check if the group is already member of this group - WorkTransaction transaction = userAdminWrapper.beginTransactionIfNeeded(); - User user = (User) role; - myGroup.addMember(user); - if (UserAdminWrapper.COMMIT_ON_SAVE) - try { - transaction.commit(); - } catch (Exception e) { - throw new IllegalStateException( - "Cannot commit transaction " + "after user group membership update", e); - } - userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, myGroup)); - } - super.drop(event); - } - - @Override - public boolean performDrop(Object data) { - // myUserViewerCmp.refresh(); - return true; - } - } - - // LOCAL HELPERS - // private Composite addSection(FormToolkit tk, Composite parent) { - // Section section = tk.createSection(parent, SWT.NO_FOCUS); - // section.setLayoutData(EclipseUiUtils.fillWidth()); - // Composite body = tk.createComposite(section, SWT.WRAP); - // body.setLayoutData(EclipseUiUtils.fillAll()); - // section.setClient(body); - // return body; - // } - - /** Creates label and text. */ - // private Text createLT(Composite parent, String label, String value) { - // FormToolkit toolkit = getManagedForm().getToolkit(); - // Label lbl = toolkit.createLabel(parent, label); - // lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false)); - // lbl.setFont(EclipseUiUtils.getBoldFont(parent)); - // Text text = toolkit.createText(parent, value, SWT.BORDER); - // text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); - // CmsUiUtils.style(text, CmsWorkbenchStyles.WORKBENCH_FORM_TEXT); - // return text; - // } - // - // Text createReadOnlyLT(Composite parent, String label, String value) { - // FormToolkit toolkit = getManagedForm().getToolkit(); - // Label lbl = toolkit.createLabel(parent, label); - // lbl.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false)); - // lbl.setFont(EclipseUiUtils.getBoldFont(parent)); - // Text text = toolkit.createText(parent, value, SWT.NONE); - // text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); - // text.setEditable(false); - // CmsUiUtils.style(text, CmsWorkbenchStyles.WORKBENCH_FORM_TEXT); - // return text; - // } - -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupsView.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupsView.java deleted file mode 100644 index ddad34fd1..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/GroupsView.java +++ /dev/null @@ -1,251 +0,0 @@ -package org.argeo.cms.e4.users; - -import java.util.ArrayList; -import java.util.List; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import javax.inject.Inject; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.CmsException; -import org.argeo.cms.auth.CurrentUser; -import org.argeo.cms.e4.users.providers.CommonNameLP; -import org.argeo.cms.e4.users.providers.DomainNameLP; -import org.argeo.cms.e4.users.providers.RoleIconLP; -import org.argeo.cms.e4.users.providers.UserDragListener; -//import org.argeo.cms.ui.workbench.WorkbenchUiPlugin; -//import org.argeo.cms.ui.workbench.internal.useradmin.UiUserAdminListener; -//import org.argeo.cms.ui.workbench.internal.useradmin.UserAdminWrapper; -//import org.argeo.cms.ui.workbench.internal.useradmin.providers.CommonNameLP; -//import org.argeo.cms.ui.workbench.internal.useradmin.providers.DomainNameLP; -//import org.argeo.cms.ui.workbench.internal.useradmin.providers.RoleIconLP; -//import org.argeo.cms.ui.workbench.internal.useradmin.providers.UserDragListener; -//import org.argeo.cms.ui.workbench.internal.useradmin.providers.UserTableDefaultDClickListener; -import org.argeo.eclipse.ui.ColumnDefinition; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.argeo.eclipse.ui.parts.LdifUsersTable; -import org.argeo.util.naming.LdapAttrs; -import org.argeo.util.naming.LdapObjs; -import org.eclipse.e4.ui.di.Focus; -import org.eclipse.e4.ui.workbench.modeling.EPartService; -import org.eclipse.e4.ui.workbench.modeling.ESelectionService; -import org.eclipse.jface.viewers.ISelectionChangedListener; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.SelectionChangedEvent; -import org.eclipse.jface.viewers.TableViewer; -import org.eclipse.swt.SWT; -import org.eclipse.swt.dnd.DND; -import org.eclipse.swt.dnd.TextTransfer; -import org.eclipse.swt.dnd.Transfer; -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.Display; -//import org.eclipse.ui.part.ViewPart; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.User; -import org.osgi.service.useradmin.UserAdminEvent; -import org.osgi.service.useradmin.UserAdminListener; - -/** List all groups with filter */ -public class GroupsView { - private final static CmsLog log = CmsLog.getLog(GroupsView.class); - // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + ".groupsView"; - - @Inject - private EPartService partService; - @Inject - private UserAdminWrapper userAdminWrapper; - - // UI Objects - private LdifUsersTable groupTableViewerCmp; - private TableViewer userViewer; - private List columnDefs = new ArrayList(); - - private UserAdminListener listener; - - @PostConstruct - public void createPartControl(Composite parent, ESelectionService selectionService) { - parent.setLayout(EclipseUiUtils.noSpaceGridLayout()); - - // boolean isAdmin = CurrentUser.isInRole(NodeConstants.ROLE_ADMIN); - - // Define the displayed columns - columnDefs.add(new ColumnDefinition(new RoleIconLP(), "", 19)); - columnDefs.add(new ColumnDefinition(new CommonNameLP(), "Name", 150)); - columnDefs.add(new ColumnDefinition(new DomainNameLP(), "Domain", 100)); - // Only show technical DN to admin - // if (isAdmin) - // columnDefs.add(new ColumnDefinition(new UserNameLP(), - // "Distinguished Name", 300)); - - // Create and configure the table - groupTableViewerCmp = new MyUserTableViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); - - groupTableViewerCmp.setColumnDefinitions(columnDefs); - // if (isAdmin) - // groupTableViewerCmp.populateWithStaticFilters(false, false); - // else - groupTableViewerCmp.populate(true, false); - - groupTableViewerCmp.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - - // Links - userViewer = groupTableViewerCmp.getTableViewer(); - userViewer.addDoubleClickListener(new UserTableDefaultDClickListener(partService)); - // getViewSite().setSelectionProvider(userViewer); - userViewer.addSelectionChangedListener(new ISelectionChangedListener() { - - @Override - public void selectionChanged(SelectionChangedEvent event) { - IStructuredSelection selection = (IStructuredSelection) event.getSelection(); - selectionService.setSelection(selection.toList()); - } - }); - - // Really? - groupTableViewerCmp.refresh(); - - // Drag and drop - int operations = DND.DROP_COPY | DND.DROP_MOVE; - Transfer[] tt = new Transfer[] { TextTransfer.getInstance() }; - userViewer.addDragSupport(operations, tt, new UserDragListener(userViewer)); - - // // Register a useradmin listener - // listener = new UserAdminListener() { - // @Override - // public void roleChanged(UserAdminEvent event) { - // if (userViewer != null && !userViewer.getTable().isDisposed()) - // refresh(); - // } - // }; - // userAdminWrapper.addListener(listener); - // } - - // Register a useradmin listener - listener = new MyUiUAListener(parent.getDisplay()); - userAdminWrapper.addListener(listener); - } - - private class MyUiUAListener extends UiUserAdminListener { - public MyUiUAListener(Display display) { - super(display); - } - - @Override - public void roleChangedToUiThread(UserAdminEvent event) { - if (userViewer != null && !userViewer.getTable().isDisposed()) - refresh(); - } - } - - private class MyUserTableViewer extends LdifUsersTable { - private static final long serialVersionUID = 8467999509931900367L; - - private boolean showSystemRoles = true; - - private final String[] knownProps = { LdapAttrs.uid.name(), LdapAttrs.cn.name(), LdapAttrs.DN }; - - public MyUserTableViewer(Composite parent, int style) { - super(parent, style); - showSystemRoles = CurrentUser.isInRole(CmsConstants.ROLE_ADMIN); - } - - protected void populateStaticFilters(Composite staticFilterCmp) { - staticFilterCmp.setLayout(new GridLayout()); - final Button showSystemRoleBtn = new Button(staticFilterCmp, SWT.CHECK); - showSystemRoleBtn.setText("Show system roles"); - showSystemRoles = CurrentUser.isInRole(CmsConstants.ROLE_ADMIN); - showSystemRoleBtn.setSelection(showSystemRoles); - - showSystemRoleBtn.addSelectionListener(new SelectionAdapter() { - private static final long serialVersionUID = -7033424592697691676L; - - @Override - public void widgetSelected(SelectionEvent e) { - showSystemRoles = showSystemRoleBtn.getSelection(); - refresh(); - } - - }); - } - - @Override - protected List listFilteredElements(String filter) { - Role[] roles; - try { - StringBuilder builder = new StringBuilder(); - StringBuilder tmpBuilder = new StringBuilder(); - if (EclipseUiUtils.notEmpty(filter)) - for (String prop : knownProps) { - tmpBuilder.append("("); - tmpBuilder.append(prop); - tmpBuilder.append("=*"); - tmpBuilder.append(filter); - tmpBuilder.append("*)"); - } - if (tmpBuilder.length() > 1) { - builder.append("(&(").append(LdapAttrs.objectClass.name()).append("=") - .append(LdapObjs.groupOfNames.name()).append(")"); - // hide tokens - builder.append("(!(").append(LdapAttrs.DN).append("=*").append(CmsConstants.TOKENS_BASEDN) - .append("))"); - - if (!showSystemRoles) - builder.append("(!(").append(LdapAttrs.DN).append("=*").append(CmsConstants.ROLES_BASEDN) - .append("))"); - builder.append("(|"); - builder.append(tmpBuilder.toString()); - builder.append("))"); - } else { - if (!showSystemRoles) - builder.append("(&(").append(LdapAttrs.objectClass.name()).append("=") - .append(LdapObjs.groupOfNames.name()).append(")(!(").append(LdapAttrs.DN).append("=*") - .append(CmsConstants.ROLES_BASEDN).append("))(!(").append(LdapAttrs.DN).append("=*") - .append(CmsConstants.TOKENS_BASEDN).append(")))"); - else - builder.append("(&(").append(LdapAttrs.objectClass.name()).append("=") - .append(LdapObjs.groupOfNames.name()).append(")(!(").append(LdapAttrs.DN).append("=*") - .append(CmsConstants.TOKENS_BASEDN).append(")))"); - - } - roles = userAdminWrapper.getUserAdmin().getRoles(builder.toString()); - } catch (InvalidSyntaxException e) { - throw new CmsException("Unable to get roles with filter: " + filter, e); - } - List users = new ArrayList(); - for (Role role : roles) - if (!users.contains(role)) - users.add((User) role); - else - log.warn("Duplicated role: " + role); - - return users; - } - } - - public void refresh() { - groupTableViewerCmp.refresh(); - } - - @PreDestroy - public void dispose() { - userAdminWrapper.removeListener(listener); - } - - @Focus - public void setFocus() { - groupTableViewerCmp.setFocus(); - } - - /* DEPENDENCY INJECTION */ - public void setUserAdminWrapper(UserAdminWrapper userAdminWrapper) { - this.userAdminWrapper = userAdminWrapper; - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/SecurityAdminImages.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/SecurityAdminImages.java deleted file mode 100644 index 7bbe3c727..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/SecurityAdminImages.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.argeo.cms.e4.users; - -import org.argeo.cms.ui.theme.CmsImages; -import org.eclipse.jface.resource.ImageDescriptor; -import org.eclipse.swt.graphics.Image; - -/** Shared icons that must be declared programmatically . */ -public class SecurityAdminImages extends CmsImages { - private final static String PREFIX = "icons/"; - - public final static ImageDescriptor ICON_REMOVE_DESC = createDesc(PREFIX + "delete.png"); - public final static ImageDescriptor ICON_USER_DESC = createDesc(PREFIX + "person.png"); - - public final static Image ICON_USER = ICON_USER_DESC.createImage(); - public final static Image ICON_GROUP = createImg(PREFIX + "group.png"); - public final static Image ICON_WORKGROUP = createImg(PREFIX + "workgroup.png"); - public final static Image ICON_ROLE = createImg(PREFIX + "role.gif"); - -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UiAdminUtils.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UiAdminUtils.java deleted file mode 100644 index f85649260..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UiAdminUtils.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.argeo.cms.e4.users; - -import org.argeo.osgi.transaction.WorkTransaction; - -/** First effort to centralize back end methods used by the user admin UI */ -public class UiAdminUtils { - /* - * INTERNAL METHODS: Below methods are meant to stay here and are not part - * of a potential generic backend to manage the useradmin - */ - /** Easily notify the ActiveWindow that the transaction had a state change */ - public final static void notifyTransactionStateChange( - WorkTransaction userTransaction) { -// try { -// IWorkbenchWindow aww = PlatformUI.getWorkbench() -// .getActiveWorkbenchWindow(); -// ISourceProviderService sourceProviderService = (ISourceProviderService) aww -// .getService(ISourceProviderService.class); -// UserTransactionProvider esp = (UserTransactionProvider) sourceProviderService -// .getSourceProvider(UserTransactionProvider.TRANSACTION_STATE); -// esp.fireTransactionStateChange(); -// } catch (Exception e) { -// throw new CmsException("Unable to begin transaction", e); -// } - } - - /** - * Email addresses must match this regexp pattern ({@value #EMAIL_PATTERN}. - * Thanks to this tip. - */ - public final static String EMAIL_PATTERN = "^[_A-Za-z0-9-]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\\.[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$"; -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UiUserAdminListener.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UiUserAdminListener.java deleted file mode 100644 index eb64aba0e..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UiUserAdminListener.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.argeo.cms.e4.users; - -import org.eclipse.swt.widgets.Display; -import org.osgi.service.useradmin.UserAdminEvent; -import org.osgi.service.useradmin.UserAdminListener; - -/** Convenience class to insure the call to refresh is done in the UI thread */ -public abstract class UiUserAdminListener implements UserAdminListener { - - private final Display display; - - public UiUserAdminListener(Display display) { - this.display = display; - } - - @Override - public void roleChanged(final UserAdminEvent event) { - display.asyncExec(new Runnable() { - @Override - public void run() { - roleChangedToUiThread(event); - } - }); - } - - public abstract void roleChangedToUiThread(UserAdminEvent event); -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserAdminWrapper.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserAdminWrapper.java deleted file mode 100644 index 16aa78316..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserAdminWrapper.java +++ /dev/null @@ -1,153 +0,0 @@ -package org.argeo.cms.e4.users; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.cms.CmsException; -import org.argeo.osgi.transaction.WorkTransaction; -import org.argeo.osgi.useradmin.UserAdminConf; -import org.argeo.osgi.useradmin.UserDirectory; -import org.osgi.service.useradmin.UserAdmin; -import org.osgi.service.useradmin.UserAdminEvent; -import org.osgi.service.useradmin.UserAdminListener; - -/** Centralise interaction with the UserAdmin in this bundle */ -public class UserAdminWrapper { - - private UserAdmin userAdmin; - // private ServiceReference userAdminServiceReference; -// private Set uris; - private Map> userDirectories = Collections - .synchronizedMap(new LinkedHashMap<>()); - private WorkTransaction userTransaction; - - // First effort to simplify UX while managing users and groups - public final static boolean COMMIT_ON_SAVE = true; - - // Registered listeners - List listeners = new ArrayList(); - - /** - * Starts a transaction if necessary. Should always been called together with - * {@link UserAdminWrapper#commitOrNotifyTransactionStateChange()} once the - * security model changes have been performed. - */ - public WorkTransaction beginTransactionIfNeeded() { - try { - // UserTransaction userTransaction = getUserTransaction(); - if (userTransaction.isNoTransactionStatus()) { - userTransaction.begin(); - // UiAdminUtils.notifyTransactionStateChange(userTransaction); - } - return userTransaction; - } catch (Exception e) { - throw new CmsException("Unable to begin transaction", e); - } - } - - /** - * Depending on the current application configuration, it will either commit the - * current transaction or throw a notification that the transaction state has - * changed (In the later case, it must be called from the UI thread). - */ - public void commitOrNotifyTransactionStateChange() { - try { - // UserTransaction userTransaction = getUserTransaction(); - if (userTransaction.isNoTransactionStatus()) - return; - - if (UserAdminWrapper.COMMIT_ON_SAVE) - userTransaction.commit(); - else - UiAdminUtils.notifyTransactionStateChange(userTransaction); - } catch (Exception e) { - throw new CmsException("Unable to clean transaction", e); - } - } - - // TODO implement safer mechanism - public void addListener(UserAdminListener userAdminListener) { - if (!listeners.contains(userAdminListener)) - listeners.add(userAdminListener); - } - - public void removeListener(UserAdminListener userAdminListener) { - if (listeners.contains(userAdminListener)) - listeners.remove(userAdminListener); - } - - public void notifyListeners(UserAdminEvent event) { - for (UserAdminListener listener : listeners) - listener.roleChanged(event); - } - - public Map getKnownBaseDns(boolean onlyWritable) { - Map dns = new HashMap(); - for (UserDirectory userDirectory : userDirectories.keySet()) { - Boolean readOnly = userDirectory.isReadOnly(); - String baseDn = userDirectory.getBaseDn().toString(); - - if (onlyWritable && readOnly) - continue; - if (baseDn.equalsIgnoreCase(CmsConstants.ROLES_BASEDN)) - continue; - if (baseDn.equalsIgnoreCase(CmsConstants.TOKENS_BASEDN)) - continue; - dns.put(baseDn, UserAdminConf.propertiesAsUri(userDirectories.get(userDirectory)).toString()); - - } -// for (String uri : uris) { -// if (!uri.startsWith("/")) -// continue; -// Dictionary props = UserAdminConf.uriAsProperties(uri); -// String readOnly = UserAdminConf.readOnly.getValue(props); -// String baseDn = UserAdminConf.baseDn.getValue(props); -// -// if (onlyWritable && "true".equals(readOnly)) -// continue; -// if (baseDn.equalsIgnoreCase(NodeConstants.ROLES_BASEDN)) -// continue; -// if (baseDn.equalsIgnoreCase(NodeConstants.TOKENS_BASEDN)) -// continue; -// dns.put(baseDn, uri); -// } - return dns; - } - - public UserAdmin getUserAdmin() { - return userAdmin; - } - - public WorkTransaction getUserTransaction() { - return userTransaction; - } - - /* DEPENDENCY INJECTION */ - public void setUserAdmin(UserAdmin userAdmin, Map properties) { - this.userAdmin = userAdmin; -// this.uris = Collections.unmodifiableSortedSet(new TreeSet<>(properties.keySet())); - } - - public void setUserTransaction(WorkTransaction userTransaction) { - this.userTransaction = userTransaction; - } - - public void addUserDirectory(UserDirectory userDirectory, Map properties) { - userDirectories.put(userDirectory, new Hashtable<>(properties)); - } - - public void removeUserDirectory(UserDirectory userDirectory, Map properties) { - userDirectories.remove(userDirectory); - } - - // public void setUserAdminServiceReference( - // ServiceReference userAdminServiceReference) { - // this.userAdminServiceReference = userAdminServiceReference; - // } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserBatchUpdateWizard.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserBatchUpdateWizard.java deleted file mode 100644 index a38d171ef..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserBatchUpdateWizard.java +++ /dev/null @@ -1,606 +0,0 @@ -package org.argeo.cms.e4.users; - -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.CmsException; -import org.argeo.cms.auth.CurrentUser; -import org.argeo.cms.auth.UserAdminUtils; -import org.argeo.cms.e4.users.providers.CommonNameLP; -import org.argeo.cms.e4.users.providers.DomainNameLP; -import org.argeo.cms.e4.users.providers.MailLP; -import org.argeo.cms.e4.users.providers.UserNameLP; -import org.argeo.eclipse.ui.ColumnDefinition; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.argeo.eclipse.ui.parts.LdifUsersTable; -import org.argeo.osgi.transaction.WorkTransaction; -import org.argeo.util.naming.LdapAttrs; -import org.argeo.util.naming.LdapObjs; -import org.eclipse.jface.dialogs.IPageChangeProvider; -import org.eclipse.jface.dialogs.IPageChangedListener; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.jface.dialogs.PageChangedEvent; -import org.eclipse.jface.wizard.IWizardContainer; -import org.eclipse.jface.wizard.Wizard; -import org.eclipse.jface.wizard.WizardPage; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.ModifyEvent; -import org.eclipse.swt.events.ModifyListener; -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.Combo; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Text; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.User; -import org.osgi.service.useradmin.UserAdminEvent; - -/** Wizard to update users */ -public class UserBatchUpdateWizard extends Wizard { - - private final static CmsLog log = CmsLog.getLog(UserBatchUpdateWizard.class); - private UserAdminWrapper userAdminWrapper; - - // pages - private ChooseCommandWizardPage chooseCommandPage; - private ChooseUsersWizardPage userListPage; - private ValidateAndLaunchWizardPage validatePage; - - // Various implemented commands keys - private final static String CMD_UPDATE_PASSWORD = "resetPassword"; - private final static String CMD_UPDATE_EMAIL = "resetEmail"; - private final static String CMD_GROUP_MEMBERSHIP = "groupMembership"; - - private final Map commands = new HashMap() { - private static final long serialVersionUID = 1L; - { - put("Reset password(s)", CMD_UPDATE_PASSWORD); - put("Reset email(s)", CMD_UPDATE_EMAIL); - // TODO implement role / group management - // put("Add/Remove from group", CMD_GROUP_MEMBERSHIP); - } - }; - - public UserBatchUpdateWizard(UserAdminWrapper userAdminWrapper) { - this.userAdminWrapper = userAdminWrapper; - } - - @Override - public void addPages() { - chooseCommandPage = new ChooseCommandWizardPage(); - addPage(chooseCommandPage); - userListPage = new ChooseUsersWizardPage(); - addPage(userListPage); - validatePage = new ValidateAndLaunchWizardPage(); - addPage(validatePage); - } - - @Override - public boolean performFinish() { - if (!canFinish()) - return false; - WorkTransaction ut = userAdminWrapper.getUserTransaction(); - if (!ut.isNoTransactionStatus() && !MessageDialog.openConfirm(getShell(), "Existing Transaction", - "A user transaction is already existing, " + "are you sure you want to proceed ?")) - return false; - - // We cannot use jobs, user modifications are still meant to be done in - // the UIThread - // UpdateJob job = null; - // if (job != null) - // job.schedule(); - - if (CMD_UPDATE_PASSWORD.equals(chooseCommandPage.getCommand())) { - char[] newValue = chooseCommandPage.getPwdValue(); - if (newValue == null) - throw new CmsException("Password cannot be null or an empty string"); - ResetPassword job = new ResetPassword(userAdminWrapper, userListPage.getSelectedUsers(), newValue); - job.doUpdate(); - } else if (CMD_UPDATE_EMAIL.equals(chooseCommandPage.getCommand())) { - String newValue = chooseCommandPage.getEmailValue(); - if (newValue == null) - throw new CmsException("Password cannot be null or an empty string"); - ResetEmail job = new ResetEmail(userAdminWrapper, userListPage.getSelectedUsers(), newValue); - job.doUpdate(); - } - return true; - } - - public boolean canFinish() { - if (this.getContainer().getCurrentPage() == validatePage) - return true; - return false; - } - - private class ResetPassword { - private char[] newPwd; - private UserAdminWrapper userAdminWrapper; - private List usersToUpdate; - - public ResetPassword(UserAdminWrapper userAdminWrapper, List usersToUpdate, char[] newPwd) { - this.newPwd = newPwd; - this.usersToUpdate = usersToUpdate; - this.userAdminWrapper = userAdminWrapper; - } - - @SuppressWarnings("unchecked") - protected void doUpdate() { - userAdminWrapper.beginTransactionIfNeeded(); - try { - for (User user : usersToUpdate) { - // the char array is emptied after being used. - user.getCredentials().put(null, newPwd.clone()); - } - userAdminWrapper.commitOrNotifyTransactionStateChange(); - } catch (Exception e) { - throw new CmsException("Cannot perform batch update on users", e); - } finally { - WorkTransaction ut = userAdminWrapper.getUserTransaction(); - if (!ut.isNoTransactionStatus()) - ut.rollback(); - } - } - } - - private class ResetEmail { - private String newEmail; - private UserAdminWrapper userAdminWrapper; - private List usersToUpdate; - - public ResetEmail(UserAdminWrapper userAdminWrapper, List usersToUpdate, String newEmail) { - this.newEmail = newEmail; - this.usersToUpdate = usersToUpdate; - this.userAdminWrapper = userAdminWrapper; - } - - @SuppressWarnings("unchecked") - protected void doUpdate() { - userAdminWrapper.beginTransactionIfNeeded(); - try { - for (User user : usersToUpdate) { - // the char array is emptied after being used. - user.getProperties().put(LdapAttrs.mail.name(), newEmail); - } - - userAdminWrapper.commitOrNotifyTransactionStateChange(); - if (!usersToUpdate.isEmpty()) - userAdminWrapper.notifyListeners( - new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, usersToUpdate.get(0))); - } catch (Exception e) { - throw new CmsException("Cannot perform batch update on users", e); - } finally { - WorkTransaction ut = userAdminWrapper.getUserTransaction(); - if (!ut.isNoTransactionStatus()) - ut.rollback(); - } - } - } - - // @SuppressWarnings("unused") - // private class AddToGroup extends UpdateJob { - // private String groupID; - // private Session session; - // - // public AddToGroup(Session session, List nodesToUpdate, - // String groupID) { - // super(session, nodesToUpdate); - // this.session = session; - // this.groupID = groupID; - // } - // - // protected void doUpdate(Node node) { - // log.info("Add/Remove to group actions are not yet implemented"); - // // TODO implement this - // // try { - // // throw new CmsException("Not yet implemented"); - // // } catch (RepositoryException re) { - // // throw new CmsException( - // // "Unable to update boolean value for node " + node, re); - // // } - // } - // } - - // /** - // * Base privileged job that will be run asynchronously to perform the - // batch - // * update - // */ - // private abstract class UpdateJob extends PrivilegedJob { - // - // private final UserAdminWrapper userAdminWrapper; - // private final List usersToUpdate; - // - // protected abstract void doUpdate(User user); - // - // public UpdateJob(UserAdminWrapper userAdminWrapper, - // List usersToUpdate) { - // super("Perform update"); - // this.usersToUpdate = usersToUpdate; - // this.userAdminWrapper = userAdminWrapper; - // } - // - // @Override - // protected IStatus doRun(IProgressMonitor progressMonitor) { - // try { - // JcrMonitor monitor = new EclipseJcrMonitor(progressMonitor); - // int total = usersToUpdate.size(); - // monitor.beginTask("Performing change", total); - // userAdminWrapper.beginTransactionIfNeeded(); - // for (User user : usersToUpdate) { - // doUpdate(user); - // monitor.worked(1); - // } - // userAdminWrapper.getUserTransaction().commit(); - // } catch (Exception e) { - // throw new CmsException( - // "Cannot perform batch update on users", e); - // } finally { - // UserTransaction ut = userAdminWrapper.getUserTransaction(); - // try { - // if (ut.getStatus() != javax.transaction.Status.STATUS_NO_TRANSACTION) - // ut.rollback(); - // } catch (IllegalStateException | SecurityException - // | SystemException e) { - // log.error("Unable to rollback session in 'finally', " - // + "the system might be in a dirty state"); - // e.printStackTrace(); - // } - // } - // return Status.OK_STATUS; - // } - // } - - // PAGES - /** - * Displays a combo box that enables user to choose which action to perform - */ - private class ChooseCommandWizardPage extends WizardPage { - private static final long serialVersionUID = -8069434295293996633L; - private Combo chooseCommandCmb; - private Button trueChk; - private Text valueTxt; - private Text pwdTxt; - private Text pwd2Txt; - - public ChooseCommandWizardPage() { - super("Choose a command to run."); - setTitle("Choose a command to run."); - } - - @Override - public void createControl(Composite parent) { - GridLayout gl = new GridLayout(); - Composite container = new Composite(parent, SWT.NO_FOCUS); - container.setLayout(gl); - - chooseCommandCmb = new Combo(container, SWT.READ_ONLY); - chooseCommandCmb.setLayoutData(EclipseUiUtils.fillWidth()); - String[] values = commands.keySet().toArray(new String[0]); - chooseCommandCmb.setItems(values); - - final Composite bottomPart = new Composite(container, SWT.NO_FOCUS); - bottomPart.setLayoutData(EclipseUiUtils.fillAll()); - bottomPart.setLayout(EclipseUiUtils.noSpaceGridLayout()); - - chooseCommandCmb.addSelectionListener(new SelectionAdapter() { - private static final long serialVersionUID = 1L; - - @Override - public void widgetSelected(SelectionEvent e) { - if (getCommand().equals(CMD_UPDATE_PASSWORD)) - populatePasswordCmp(bottomPart); - else if (getCommand().equals(CMD_UPDATE_EMAIL)) - populateEmailCmp(bottomPart); - else if (getCommand().equals(CMD_GROUP_MEMBERSHIP)) - populateGroupCmp(bottomPart); - else - populateBooleanFlagCmp(bottomPart); - checkPageComplete(); - bottomPart.layout(true, true); - } - }); - setControl(container); - } - - private void populateBooleanFlagCmp(Composite parent) { - EclipseUiUtils.clear(parent); - trueChk = new Button(parent, SWT.CHECK); - trueChk.setText("Do it. (It will to the contrary if unchecked)"); - trueChk.setSelection(true); - trueChk.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false)); - } - - private void populatePasswordCmp(Composite parent) { - EclipseUiUtils.clear(parent); - Composite body = new Composite(parent, SWT.NO_FOCUS); - - ModifyListener ml = new ModifyListener() { - private static final long serialVersionUID = -1558726363536729634L; - - @Override - public void modifyText(ModifyEvent event) { - checkPageComplete(); - } - }; - - body.setLayout(new GridLayout(2, false)); - body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - pwdTxt = EclipseUiUtils.createGridLP(body, "New password", ml); - pwd2Txt = EclipseUiUtils.createGridLP(body, "Repeat password", ml); - } - - private void populateEmailCmp(Composite parent) { - EclipseUiUtils.clear(parent); - Composite body = new Composite(parent, SWT.NO_FOCUS); - - ModifyListener ml = new ModifyListener() { - private static final long serialVersionUID = 2147704227294268317L; - - @Override - public void modifyText(ModifyEvent event) { - checkPageComplete(); - } - }; - - body.setLayout(new GridLayout(2, false)); - body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - valueTxt = EclipseUiUtils.createGridLT(body, "New e-mail", ml); - } - - private void checkPageComplete() { - String errorMsg = null; - if (chooseCommandCmb.getSelectionIndex() < 0) - errorMsg = "Please select an action"; - else if (CMD_UPDATE_EMAIL.equals(getCommand())) { - if (!valueTxt.getText().matches(UiAdminUtils.EMAIL_PATTERN)) - errorMsg = "Not a valid e-mail address"; - } else if (CMD_UPDATE_PASSWORD.equals(getCommand())) { - if (EclipseUiUtils.isEmpty(pwdTxt.getText()) || pwdTxt.getText().length() < 4) - errorMsg = "Please enter a password that is at least 4 character long"; - else if (!pwdTxt.getText().equals(pwd2Txt.getText())) - errorMsg = "Passwords are different"; - } - if (EclipseUiUtils.notEmpty(errorMsg)) { - setMessage(errorMsg, WizardPage.ERROR); - setPageComplete(false); - } else { - setMessage("Page complete, you can proceed to user choice", WizardPage.INFORMATION); - setPageComplete(true); - } - - getContainer().updateButtons(); - } - - private void populateGroupCmp(Composite parent) { - EclipseUiUtils.clear(parent); - trueChk = new Button(parent, SWT.CHECK); - trueChk.setText("Add to group. (It will remove user(s) from the " + "corresponding group if unchecked)"); - trueChk.setSelection(true); - trueChk.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false)); - } - - protected String getCommand() { - return commands.get(chooseCommandCmb.getItem(chooseCommandCmb.getSelectionIndex())); - } - - protected String getCommandLbl() { - return chooseCommandCmb.getItem(chooseCommandCmb.getSelectionIndex()); - } - - @SuppressWarnings("unused") - protected boolean getBoleanValue() { - // FIXME this is not consistent and will lead to errors. - if ("argeo:enabled".equals(getCommand())) - return trueChk.getSelection(); - else - return !trueChk.getSelection(); - } - - @SuppressWarnings("unused") - protected String getStringValue() { - String value = null; - if (valueTxt != null) { - value = valueTxt.getText(); - if ("".equals(value.trim())) - value = null; - } - return value; - } - - protected char[] getPwdValue() { - // We do not directly reset the password text fields: There is no - // need to over secure this process: setting a pwd to multi users - // at the same time is anyhow a bad practice and should be used only - // in test environment or for temporary access - if (pwdTxt == null || pwdTxt.isDisposed()) - return null; - else - return pwdTxt.getText().toCharArray(); - } - - protected String getEmailValue() { - // We do not directly reset the password text fields: There is no - // need to over secure this process: setting a pwd to multi users - // at the same time is anyhow a bad practice and should be used only - // in test environment or for temporary access - if (valueTxt == null || valueTxt.isDisposed()) - return null; - else - return valueTxt.getText(); - } - } - - /** - * Displays a list of users with a check box to be able to choose some of them - */ - private class ChooseUsersWizardPage extends WizardPage implements IPageChangedListener { - private static final long serialVersionUID = 7651807402211214274L; - private ChooseUserTableViewer userTableCmp; - - public ChooseUsersWizardPage() { - super("Choose Users"); - setTitle("Select users who will be impacted"); - } - - @Override - public void createControl(Composite parent) { - Composite pageCmp = new Composite(parent, SWT.NONE); - pageCmp.setLayout(EclipseUiUtils.noSpaceGridLayout()); - - // Define the displayed columns - List columnDefs = new ArrayList(); - columnDefs.add(new ColumnDefinition(new CommonNameLP(), "Common Name", 150)); - columnDefs.add(new ColumnDefinition(new MailLP(), "E-mail", 150)); - columnDefs.add(new ColumnDefinition(new DomainNameLP(), "Domain", 200)); - - // Only show technical DN to admin - if (CurrentUser.isInRole(CmsConstants.ROLE_ADMIN)) - columnDefs.add(new ColumnDefinition(new UserNameLP(), "Distinguished Name", 300)); - - userTableCmp = new ChooseUserTableViewer(pageCmp, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); - userTableCmp.setLayoutData(EclipseUiUtils.fillAll()); - userTableCmp.setColumnDefinitions(columnDefs); - userTableCmp.populate(true, true); - userTableCmp.refresh(); - - setControl(pageCmp); - - // Add listener to update message when shown - final IWizardContainer wContainer = this.getContainer(); - if (wContainer instanceof IPageChangeProvider) { - ((IPageChangeProvider) wContainer).addPageChangedListener(this); - } - - } - - @Override - public void pageChanged(PageChangedEvent event) { - if (event.getSelectedPage() == this) { - String msg = "Chosen batch action: " + chooseCommandPage.getCommandLbl(); - ((WizardPage) event.getSelectedPage()).setMessage(msg); - } - } - - protected List getSelectedUsers() { - return userTableCmp.getSelectedUsers(); - } - - private class ChooseUserTableViewer extends LdifUsersTable { - private static final long serialVersionUID = 5080437561015853124L; - private final String[] knownProps = { LdapAttrs.uid.name(), LdapAttrs.DN, LdapAttrs.cn.name(), - LdapAttrs.givenName.name(), LdapAttrs.sn.name(), LdapAttrs.mail.name() }; - - public ChooseUserTableViewer(Composite parent, int style) { - super(parent, style); - } - - @Override - protected List listFilteredElements(String filter) { - Role[] roles; - - try { - StringBuilder builder = new StringBuilder(); - - StringBuilder tmpBuilder = new StringBuilder(); - if (EclipseUiUtils.notEmpty(filter)) - for (String prop : knownProps) { - tmpBuilder.append("("); - tmpBuilder.append(prop); - tmpBuilder.append("=*"); - tmpBuilder.append(filter); - tmpBuilder.append("*)"); - } - if (tmpBuilder.length() > 1) { - builder.append("(&(").append(LdapAttrs.objectClass.name()).append("=") - .append(LdapObjs.inetOrgPerson.name()).append(")(|"); - builder.append(tmpBuilder.toString()); - builder.append("))"); - } else - builder.append("(").append(LdapAttrs.objectClass.name()).append("=") - .append(LdapObjs.inetOrgPerson.name()).append(")"); - roles = userAdminWrapper.getUserAdmin().getRoles(builder.toString()); - } catch (InvalidSyntaxException e) { - throw new CmsException("Unable to get roles with filter: " + filter, e); - } - List users = new ArrayList(); - for (Role role : roles) - // Prevent current logged in user to perform batch on - // himself - if (!UserAdminUtils.isCurrentUser((User) role)) - users.add((User) role); - return users; - } - } - } - - /** Summary of input data before launching the process */ - private class ValidateAndLaunchWizardPage extends WizardPage implements IPageChangedListener { - private static final long serialVersionUID = 7098918351451743853L; - private ChosenUsersTableViewer userTableCmp; - - public ValidateAndLaunchWizardPage() { - super("Validate and launch"); - setTitle("Validate and launch"); - } - - @Override - public void createControl(Composite parent) { - Composite pageCmp = new Composite(parent, SWT.NO_FOCUS); - pageCmp.setLayout(EclipseUiUtils.noSpaceGridLayout()); - - List columnDefs = new ArrayList(); - columnDefs.add(new ColumnDefinition(new CommonNameLP(), "Common Name", 150)); - columnDefs.add(new ColumnDefinition(new MailLP(), "E-mail", 150)); - columnDefs.add(new ColumnDefinition(new DomainNameLP(), "Domain", 200)); - // Only show technical DN to admin - if (CurrentUser.isInRole(CmsConstants.ROLE_ADMIN)) - columnDefs.add(new ColumnDefinition(new UserNameLP(), "Distinguished Name", 300)); - userTableCmp = new ChosenUsersTableViewer(pageCmp, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); - userTableCmp.setLayoutData(EclipseUiUtils.fillAll()); - userTableCmp.setColumnDefinitions(columnDefs); - userTableCmp.populate(false, false); - userTableCmp.refresh(); - setControl(pageCmp); - // Add listener to update message when shown - final IWizardContainer wContainer = this.getContainer(); - if (wContainer instanceof IPageChangeProvider) { - ((IPageChangeProvider) wContainer).addPageChangedListener(this); - } - } - - @Override - public void pageChanged(PageChangedEvent event) { - if (event.getSelectedPage() == this) { - @SuppressWarnings({ "unchecked", "rawtypes" }) - Object[] values = ((ArrayList) userListPage.getSelectedUsers()) - .toArray(new Object[userListPage.getSelectedUsers().size()]); - userTableCmp.getTableViewer().setInput(values); - String msg = "Following batch action: [" + chooseCommandPage.getCommandLbl() - + "] will be perfomed on the users listed below.\n"; - // + "Are you sure you want to proceed?"; - setMessage(msg); - } - } - - private class ChosenUsersTableViewer extends LdifUsersTable { - private static final long serialVersionUID = 7814764735794270541L; - - public ChosenUsersTableViewer(Composite parent, int style) { - super(parent, style); - } - - @Override - protected List listFilteredElements(String filter) { - return userListPage.getSelectedUsers(); - } - } - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserEditor.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserEditor.java deleted file mode 100644 index 66f442082..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserEditor.java +++ /dev/null @@ -1,535 +0,0 @@ -package org.argeo.cms.e4.users; - -import static org.argeo.cms.auth.UserAdminUtils.getProperty; -import static org.argeo.util.naming.LdapAttrs.cn; -import static org.argeo.util.naming.LdapAttrs.givenName; -import static org.argeo.util.naming.LdapAttrs.mail; -import static org.argeo.util.naming.LdapAttrs.sn; -import static org.argeo.util.naming.LdapAttrs.uid; - -import java.util.ArrayList; -import java.util.Iterator; -import java.util.List; - -import javax.inject.Inject; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.cms.auth.CurrentUser; -import org.argeo.cms.auth.UserAdminUtils; -import org.argeo.cms.e4.users.providers.CommonNameLP; -import org.argeo.cms.e4.users.providers.DomainNameLP; -import org.argeo.cms.e4.users.providers.RoleIconLP; -import org.argeo.cms.e4.users.providers.UserFilter; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.eclipse.forms.AbstractFormPart; -//import org.argeo.cms.ui.eclipse.forms.FormToolkit; -import org.argeo.cms.ui.eclipse.forms.IManagedForm; -import org.argeo.eclipse.ui.ColumnDefinition; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.argeo.eclipse.ui.parts.LdifUsersTable; -import org.argeo.util.naming.LdapAttrs; -import org.eclipse.e4.ui.workbench.modeling.EPartService; -import org.eclipse.jface.action.Action; -import org.eclipse.jface.action.ToolBarManager; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.jface.dialogs.TrayDialog; -import org.eclipse.jface.resource.ImageDescriptor; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.TableViewer; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.jface.viewers.ViewerDropAdapter; -import org.eclipse.swt.SWT; -import org.eclipse.swt.dnd.DND; -import org.eclipse.swt.dnd.DropTargetEvent; -import org.eclipse.swt.dnd.TextTransfer; -import org.eclipse.swt.dnd.Transfer; -import org.eclipse.swt.dnd.TransferData; -import org.eclipse.swt.events.ModifyEvent; -import org.eclipse.swt.events.ModifyListener; -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.Display; -import org.eclipse.swt.widgets.Link; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; -import org.eclipse.swt.widgets.ToolBar; -import org.osgi.service.useradmin.Group; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.User; -import org.osgi.service.useradmin.UserAdmin; -import org.osgi.service.useradmin.UserAdminEvent; - -/** Display/edit the properties of a given user */ -public class UserEditor extends AbstractRoleEditor { - // final static String ID = "UserEditor.mainPage"; - - @Inject - private EPartService partService; - - // private final UserEditor editor; - // private UserAdminWrapper userAdminWrapper; - - // Local configuration - // private final int PRE_TITLE_INDENT = 10; - - // public UserMainPage(FormEditor editor, UserAdminWrapper userAdminWrapper) { - // super(editor, ID, "Main"); - // this.editor = (UserEditor) editor; - // this.userAdminWrapper = userAdminWrapper; - // } - - // protected void createFormContent(final IManagedForm mf) { - // ScrolledForm form = mf.getForm(); - // Composite body = form.getBody(); - // GridLayout mainLayout = new GridLayout(); - // // mainLayout.marginRight = 10; - // body.setLayout(mainLayout); - // User user = editor.getDisplayedUser(); - // appendOverviewPart(body, user); - // // Remove to ability to force the password for his own user. The user - // // must then use the change pwd feature - // appendMemberOfPart(body, user); - // } - - @Override - protected void createUi(Composite body) { - // Composite body = new Composite(parent, SWT.BORDER); - GridLayout mainLayout = new GridLayout(); - // mainLayout.marginRight = 10; - body.setLayout(mainLayout); - // body.getParent().setLayout(new GridLayout()); - // body.setLayoutData(CmsUiUtils.fillAll()); - User user = getDisplayedUser(); - appendOverviewPart(body, user); - // Remove to ability to force the password for his own user. The user - // must then use the change pwd feature - appendMemberOfPart(body, user); - } - - /** Creates the general section */ - private void appendOverviewPart(final Composite parent, final User user) { - // FormToolkit tk = getManagedForm().getToolkit(); - - // Section section = tk.createSection(parent, SWT.NO_FOCUS); - // GridData gd = EclipseUiUtils.fillWidth(); - // // gd.verticalAlignment = PRE_TITLE_INDENT; - // section.setLayoutData(gd); - Composite body = new Composite(parent, SWT.NONE); - body.setLayoutData(EclipseUiUtils.fillWidth()); - // section.setClient(body); - // body.setLayout(new GridLayout(6, false)); - body.setLayout(new GridLayout(2, false)); - - Text commonName = createReadOnlyLT(body, "Name", getProperty(user, cn)); - Text distinguishedName = createReadOnlyLT(body, "Login", getProperty(user, uid)); - Text firstName = createLT(body, "First name", getProperty(user, givenName)); - Text lastName = createLT(body, "Last name", getProperty(user, sn)); - Text email = createLT(body, "Email", getProperty(user, mail)); - - Link resetPwdLk = new Link(body, SWT.NONE); - if (!UserAdminUtils.isCurrentUser(user)) { - resetPwdLk.setText("Reset password"); - } - resetPwdLk.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 2, 1)); - - // create form part (controller) - AbstractFormPart part = new AbstractFormPart() { - private MainInfoListener listener; - - @Override - public void initialize(IManagedForm form) { - super.initialize(form); - listener = new MainInfoListener(parent.getDisplay(), this); - userAdminWrapper.addListener(listener); - } - - @Override - public void dispose() { - userAdminWrapper.removeListener(listener); - super.dispose(); - } - - @SuppressWarnings("unchecked") - public void commit(boolean onSave) { - // TODO Sanity checks (mail validity...) - user.getProperties().put(LdapAttrs.givenName.name(), firstName.getText()); - user.getProperties().put(LdapAttrs.sn.name(), lastName.getText()); - user.getProperties().put(LdapAttrs.cn.name(), commonName.getText()); - user.getProperties().put(LdapAttrs.mail.name(), email.getText()); - super.commit(onSave); - } - - @Override - public void refresh() { - distinguishedName.setText(UserAdminUtils.getProperty(user, LdapAttrs.uid.name())); - commonName.setText(UserAdminUtils.getProperty(user, LdapAttrs.cn.name())); - firstName.setText(UserAdminUtils.getProperty(user, LdapAttrs.givenName.name())); - lastName.setText(UserAdminUtils.getProperty(user, LdapAttrs.sn.name())); - email.setText(UserAdminUtils.getProperty(user, LdapAttrs.mail.name())); - refreshFormTitle(user); - super.refresh(); - } - }; - - // Improve this: automatically generate CN when first or last name - // changes - ModifyListener cnML = new ModifyListener() { - private static final long serialVersionUID = 4298649222869835486L; - - @Override - public void modifyText(ModifyEvent event) { - String first = firstName.getText(); - String last = lastName.getText(); - String cn = first.trim() + " " + last.trim() + " "; - cn = cn.trim(); - commonName.setText(cn); - // getManagedForm().getForm().setText(cn); - updateEditorTitle(cn); - } - }; - firstName.addModifyListener(cnML); - lastName.addModifyListener(cnML); - - ModifyListener defaultListener = new FormPartML(part); - firstName.addModifyListener(defaultListener); - lastName.addModifyListener(defaultListener); - email.addModifyListener(defaultListener); - - if (!UserAdminUtils.isCurrentUser(user)) - resetPwdLk.addSelectionListener(new SelectionAdapter() { - private static final long serialVersionUID = 5881800534589073787L; - - @Override - public void widgetSelected(SelectionEvent e) { - new ChangePasswordDialog(user, "Reset password").open(); - } - }); - - getManagedForm().addPart(part); - } - - private class ChangePasswordDialog extends TrayDialog { - private static final long serialVersionUID = 2843538207460082349L; - - private User user; - private Text password1; - private Text password2; - private String title; - // private FormToolkit tk; - - public ChangePasswordDialog(User user, String title) { - super(Display.getDefault().getActiveShell()); - // this.tk = tk; - this.user = user; - this.title = title; - } - - protected Control createDialogArea(Composite parent) { - Composite dialogarea = (Composite) super.createDialogArea(parent); - dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - Composite body = new Composite(dialogarea, SWT.NO_FOCUS); - body.setLayoutData(EclipseUiUtils.fillAll()); - GridLayout layout = new GridLayout(2, false); - body.setLayout(layout); - - password1 = createLP(body, "New password", ""); - password2 = createLP(body, "Repeat password", ""); - parent.pack(); - return body; - } - - @SuppressWarnings("unchecked") - @Override - protected void okPressed() { - String msg = null; - - if (password1.getText().equals("")) - msg = "Password cannot be empty"; - else if (password1.getText().equals(password2.getText())) { - char[] newPassword = password1.getText().toCharArray(); - // userAdminWrapper.beginTransactionIfNeeded(); - userAdminWrapper.beginTransactionIfNeeded(); - user.getCredentials().put(null, newPassword); - userAdminWrapper.commitOrNotifyTransactionStateChange(); - super.okPressed(); - } else { - msg = "Passwords are not equals"; - } - - if (EclipseUiUtils.notEmpty(msg)) - MessageDialog.openError(getParentShell(), "Cannot reset pasword", msg); - } - - protected void configureShell(Shell shell) { - super.configureShell(shell); - shell.setText(title); - } - } - - private LdifUsersTable appendMemberOfPart(final Composite parent, User user) { - // Section section = addSection(tk, parent, "Roles"); - // Composite body = (Composite) section.getClient(); - // Composite body= parent; - Composite body = new Composite(parent, SWT.BORDER); - body.setLayout(new GridLayout()); - body.setLayoutData(CmsSwtUtils.fillAll()); - - // boolean isAdmin = CurrentUser.isInRole(NodeConstants.ROLE_ADMIN); - - // Displayed columns - List columnDefs = new ArrayList(); - columnDefs.add(new ColumnDefinition(new RoleIconLP(), "", 0, 24)); - columnDefs.add(new ColumnDefinition(new CommonNameLP(), "Name", 150)); - columnDefs.add(new ColumnDefinition(new DomainNameLP(), "Domain", 100)); - // Only show technical DN to administrators - // if (isAdmin) - // columnDefs.add(new ColumnDefinition(new UserNameLP(), "Distinguished Name", - // 300)); - - // Create and configure the table - final LdifUsersTable userViewerCmp = new MyUserTableViewer(body, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL, user); - - userViewerCmp.setColumnDefinitions(columnDefs); - // if (isAdmin) - // userViewerCmp.populateWithStaticFilters(false, false); - // else - userViewerCmp.populate(true, false); - GridData gd = EclipseUiUtils.fillAll(); - gd.heightHint = 500; - userViewerCmp.setLayoutData(gd); - - // Controllers - TableViewer userViewer = userViewerCmp.getTableViewer(); - userViewer.addDoubleClickListener(new UserTableDefaultDClickListener(partService)); - int operations = DND.DROP_COPY | DND.DROP_MOVE; - Transfer[] tt = new Transfer[] { TextTransfer.getInstance() }; - GroupDropListener dropL = new GroupDropListener(userAdminWrapper, userViewer, user); - userViewer.addDropSupport(operations, tt, dropL); - - AbstractFormPart part = new AbstractFormPart() { - - private GroupChangeListener listener; - - @Override - public void initialize(IManagedForm form) { - super.initialize(form); - listener = new GroupChangeListener(parent.getDisplay(), this); - userAdminWrapper.addListener(listener); - } - - public void commit(boolean onSave) { - super.commit(onSave); - } - - @Override - public void dispose() { - userAdminWrapper.removeListener(listener); - super.dispose(); - } - - @Override - public void refresh() { - userViewerCmp.refresh(); - super.refresh(); - } - }; - getManagedForm().addPart(part); - // addRemoveAbitily(body, userViewer, user); - // userViewerCmp.refresh(); - String tooltip = "Remove " + UserAdminUtils.getUserLocalId(user.getName()) + " from the below selected groups"; - Action action = new RemoveMembershipAction(userViewer, user, tooltip, SecurityAdminImages.ICON_REMOVE_DESC); - ToolBarManager toolBarManager = new ToolBarManager(SWT.FLAT); - ToolBar toolBar = toolBarManager.createControl(body); - toolBar.setLayoutData(CmsSwtUtils.fillWidth()); - toolBarManager.add(action); - toolBarManager.update(true); - return userViewerCmp; - } - - private class MyUserTableViewer extends LdifUsersTable { - private static final long serialVersionUID = 2653790051461237329L; - - private Button showSystemRoleBtn; - - private final User user; - private final UserFilter userFilter; - - public MyUserTableViewer(Composite parent, int style, User user) { - super(parent, style, true); - this.user = user; - userFilter = new UserFilter(); - } - - protected void populateStaticFilters(Composite staticFilterCmp) { - staticFilterCmp.setLayout(new GridLayout()); - showSystemRoleBtn = new Button(staticFilterCmp, SWT.CHECK); - showSystemRoleBtn.setText("Show system roles"); - boolean showSysRole = CurrentUser.isInRole(CmsConstants.ROLE_ADMIN); - showSystemRoleBtn.setSelection(showSysRole); - userFilter.setShowSystemRole(showSysRole); - showSystemRoleBtn.addSelectionListener(new SelectionAdapter() { - private static final long serialVersionUID = -7033424592697691676L; - - @Override - public void widgetSelected(SelectionEvent e) { - userFilter.setShowSystemRole(showSystemRoleBtn.getSelection()); - refresh(); - } - }); - } - - @Override - protected List listFilteredElements(String filter) { - List users = (List) getFlatGroups(null); - List filteredUsers = new ArrayList(); - if (users.contains(user)) - users.remove(user); - userFilter.setSearchText(filter); - for (User user : users) - if (userFilter.select(null, null, user)) - filteredUsers.add(user); - return filteredUsers; - } - } - - // private void addRemoveAbility(Composite parent, TableViewer userViewer, User - // user) { - // // Section section = sectionPart.getSection(); - // ToolBarManager toolBarManager = new ToolBarManager(SWT.FLAT); - // ToolBar toolbar = toolBarManager.createControl(parent); - // final Cursor handCursor = new Cursor(Display.getCurrent(), SWT.CURSOR_HAND); - // toolbar.setCursor(handCursor); - // toolbar.addDisposeListener(new DisposeListener() { - // private static final long serialVersionUID = 3882131405820522925L; - // - // public void widgetDisposed(DisposeEvent e) { - // if ((handCursor != null) && (handCursor.isDisposed() == false)) { - // handCursor.dispose(); - // } - // } - // }); - // - // String tooltip = "Remove " + UserAdminUtils.getUserLocalId(user.getName()) + - // " from the below selected groups"; - // Action action = new RemoveMembershipAction(userViewer, user, tooltip, - // SecurityAdminImages.ICON_REMOVE_DESC); - // toolBarManager.add(action); - // toolBarManager.update(true); - // // section.setTextClient(toolbar); - // } - - private class RemoveMembershipAction extends Action { - private static final long serialVersionUID = -1337713097184522588L; - - private final TableViewer userViewer; - private final User user; - - RemoveMembershipAction(TableViewer userViewer, User user, String name, ImageDescriptor img) { - super(name, img); - this.userViewer = userViewer; - this.user = user; - } - - @Override - public void run() { - ISelection selection = userViewer.getSelection(); - if (selection.isEmpty()) - return; - - @SuppressWarnings("unchecked") - Iterator it = ((IStructuredSelection) selection).iterator(); - List groups = new ArrayList(); - while (it.hasNext()) { - Group currGroup = it.next(); - groups.add(currGroup); - } - - userAdminWrapper.beginTransactionIfNeeded(); - for (Group group : groups) { - group.removeMember(user); - } - userAdminWrapper.commitOrNotifyTransactionStateChange(); - for (Group group : groups) { - userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, group)); - } - } - } - - /** - * Defines the table as being a potential target to add group memberships - * (roles) to this user - */ - private class GroupDropListener extends ViewerDropAdapter { - private static final long serialVersionUID = 2893468717831451621L; - - private final UserAdminWrapper myUserAdminWrapper; - private final User myUser; - - public GroupDropListener(UserAdminWrapper userAdminWrapper, Viewer userViewer, User user) { - super(userViewer); - this.myUserAdminWrapper = userAdminWrapper; - this.myUser = user; - } - - @Override - public boolean validateDrop(Object target, int operation, TransferData transferType) { - // Target is always OK in a list only view - // TODO check if not a string - boolean validDrop = true; - return validDrop; - } - - @Override - public void drop(DropTargetEvent event) { - String name = (String) event.data; - UserAdmin myUserAdmin = myUserAdminWrapper.getUserAdmin(); - Role role = myUserAdmin.getRole(name); - // TODO this check should be done before. - if (role.getType() == Role.GROUP) { - // TODO check if the user is already member of this group - - myUserAdminWrapper.beginTransactionIfNeeded(); - Group group = (Group) role; - group.addMember(myUser); - userAdminWrapper.commitOrNotifyTransactionStateChange(); - myUserAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CHANGED, group)); - } - super.drop(event); - } - - @Override - public boolean performDrop(Object data) { - // userTableViewerCmp.refresh(); - return true; - } - } - - // LOCAL HELPERS - private void refreshFormTitle(User group) { - // getManagedForm().getForm().setText(UserAdminUtils.getProperty(group, - // LdapAttrs.cn.name())); - } - - /** Appends a section with a title */ - // private Section addSection(FormToolkit tk, Composite parent, String title) { - // Section section = tk.createSection(parent, Section.TITLE_BAR); - // GridData gd = EclipseUiUtils.fillWidth(); - // gd.verticalAlignment = PRE_TITLE_INDENT; - // section.setLayoutData(gd); - // section.setText(title); - // // section.getMenu().setVisible(true); - // - // Composite body = tk.createComposite(section, SWT.WRAP); - // body.setLayoutData(EclipseUiUtils.fillAll()); - // section.setClient(body); - // - // return section; - // } - -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserTableDefaultDClickListener.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserTableDefaultDClickListener.java deleted file mode 100644 index c6d024ebc..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UserTableDefaultDClickListener.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.argeo.cms.e4.users; - -import org.argeo.cms.e4.CmsE4Utils; -import org.argeo.util.naming.LdapAttrs; -import org.eclipse.e4.ui.workbench.modeling.EPartService; -import org.eclipse.jface.viewers.DoubleClickEvent; -import org.eclipse.jface.viewers.IDoubleClickListener; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.osgi.service.useradmin.Group; -import org.osgi.service.useradmin.User; - -/** - * Default double click listener for the various user tables, will open the - * clicked item in the editor - */ -public class UserTableDefaultDClickListener implements IDoubleClickListener { - private final EPartService partService; - - public UserTableDefaultDClickListener(EPartService partService) { - this.partService = partService; - } - - public void doubleClick(DoubleClickEvent evt) { - if (evt.getSelection().isEmpty()) - return; - Object obj = ((IStructuredSelection) evt.getSelection()).getFirstElement(); - User user = (User) obj; - - String editorId = getEditorId(user); - CmsE4Utils.openEditor(partService, editorId, LdapAttrs.uid.name(), user.getName()); - } - - protected String getEditorId(User user) { - if (user instanceof Group) - return "org.argeo.cms.e4.partdescriptor.groupEditor"; - else - return "org.argeo.cms.e4.partdescriptor.userEditor"; - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UsersView.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UsersView.java deleted file mode 100644 index 877a925c6..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/UsersView.java +++ /dev/null @@ -1,182 +0,0 @@ -package org.argeo.cms.e4.users; - -import java.util.ArrayList; -import java.util.List; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import javax.inject.Inject; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.cms.CmsException; -import org.argeo.cms.auth.CurrentUser; -import org.argeo.cms.e4.users.providers.CommonNameLP; -import org.argeo.cms.e4.users.providers.DomainNameLP; -import org.argeo.cms.e4.users.providers.MailLP; -import org.argeo.cms.e4.users.providers.UserDragListener; -import org.argeo.cms.e4.users.providers.UserNameLP; -import org.argeo.eclipse.ui.ColumnDefinition; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.argeo.eclipse.ui.parts.LdifUsersTable; -import org.argeo.util.naming.LdapAttrs; -import org.argeo.util.naming.LdapObjs; -import org.eclipse.e4.ui.di.Focus; -import org.eclipse.e4.ui.workbench.modeling.EPartService; -import org.eclipse.e4.ui.workbench.modeling.ESelectionService; -import org.eclipse.jface.viewers.ISelectionChangedListener; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.SelectionChangedEvent; -import org.eclipse.jface.viewers.TableViewer; -import org.eclipse.swt.SWT; -import org.eclipse.swt.dnd.DND; -import org.eclipse.swt.dnd.TextTransfer; -import org.eclipse.swt.dnd.Transfer; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Display; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.User; -import org.osgi.service.useradmin.UserAdminEvent; -import org.osgi.service.useradmin.UserAdminListener; - -/** List all users with filter - based on Ldif userAdmin */ -public class UsersView { - // private final static Log log = LogFactory.getLog(UsersView.class); - - // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + ".usersView"; - - @Inject - private UserAdminWrapper userAdminWrapper; - @Inject - private EPartService partService; - - // UI Objects - private LdifUsersTable userTableViewerCmp; - private TableViewer userViewer; - private List columnDefs = new ArrayList(); - - private UserAdminListener listener; - - @PostConstruct - public void createPartControl(Composite parent, ESelectionService selectionService) { - - parent.setLayout(EclipseUiUtils.noSpaceGridLayout()); - // Define the displayed columns - columnDefs.add(new ColumnDefinition(new CommonNameLP(), "Common Name", 150)); - columnDefs.add(new ColumnDefinition(new MailLP(), "E-mail", 150)); - columnDefs.add(new ColumnDefinition(new DomainNameLP(), "Domain", 200)); - // Only show technical DN to admin - if (CurrentUser.isInRole(CmsConstants.ROLE_ADMIN)) - columnDefs.add(new ColumnDefinition(new UserNameLP(), "Distinguished Name", 300)); - - // Create and configure the table - userTableViewerCmp = new MyUserTableViewer(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL); - userTableViewerCmp.setLayoutData(EclipseUiUtils.fillAll()); - userTableViewerCmp.setColumnDefinitions(columnDefs); - userTableViewerCmp.populate(true, false); - - // Links - userViewer = userTableViewerCmp.getTableViewer(); - userViewer.addDoubleClickListener(new UserTableDefaultDClickListener(partService)); - userViewer.addSelectionChangedListener(new ISelectionChangedListener() { - - @Override - public void selectionChanged(SelectionChangedEvent event) { - IStructuredSelection selection = (IStructuredSelection) event.getSelection(); - selectionService.setSelection(selection.toList()); - } - }); - // getViewSite().setSelectionProvider(userViewer); - - // Really? - userTableViewerCmp.refresh(); - - // Drag and drop - int operations = DND.DROP_COPY | DND.DROP_MOVE; - Transfer[] tt = new Transfer[] { TextTransfer.getInstance() }; - userViewer.addDragSupport(operations, tt, new UserDragListener(userViewer)); - - // Register a useradmin listener - listener = new MyUiUAListener(parent.getDisplay()); - userAdminWrapper.addListener(listener); - } - - private class MyUiUAListener extends UiUserAdminListener { - public MyUiUAListener(Display display) { - super(display); - } - - @Override - public void roleChangedToUiThread(UserAdminEvent event) { - if (userViewer != null && !userViewer.getTable().isDisposed()) - refresh(); - } - } - - private class MyUserTableViewer extends LdifUsersTable { - private static final long serialVersionUID = 8467999509931900367L; - - private final String[] knownProps = { LdapAttrs.DN, LdapAttrs.uid.name(), LdapAttrs.cn.name(), - LdapAttrs.givenName.name(), LdapAttrs.sn.name(), LdapAttrs.mail.name() }; - - public MyUserTableViewer(Composite parent, int style) { - super(parent, style); - } - - @Override - protected List listFilteredElements(String filter) { - Role[] roles; - - try { - StringBuilder builder = new StringBuilder(); - - StringBuilder tmpBuilder = new StringBuilder(); - if (EclipseUiUtils.notEmpty(filter)) - for (String prop : knownProps) { - tmpBuilder.append("("); - tmpBuilder.append(prop); - tmpBuilder.append("=*"); - tmpBuilder.append(filter); - tmpBuilder.append("*)"); - } - if (tmpBuilder.length() > 1) { - builder.append("(&(").append(LdapAttrs.objectClass.name()).append("=") - .append(LdapObjs.inetOrgPerson.name()).append(")(|"); - builder.append(tmpBuilder.toString()); - builder.append("))"); - } else - builder.append("(").append(LdapAttrs.objectClass.name()).append("=") - .append(LdapObjs.inetOrgPerson.name()).append(")"); - roles = userAdminWrapper.getUserAdmin().getRoles(builder.toString()); - } catch (InvalidSyntaxException e) { - throw new CmsException("Unable to get roles with filter: " + filter, e); - } - List users = new ArrayList(); - for (Role role : roles) - // if (role.getType() == Role.USER && role.getType() != - // Role.GROUP) - users.add((User) role); - return users; - } - } - - public void refresh() { - userTableViewerCmp.refresh(); - } - - // Override generic view methods - @PreDestroy - public void dispose() { - userAdminWrapper.removeListener(listener); - } - - @Focus - public void setFocus() { - userTableViewerCmp.setFocus(); - } - - /* DEPENDENCY INJECTION */ - public void setUserAdminWrapper(UserAdminWrapper userAdminWrapper) { - this.userAdminWrapper = userAdminWrapper; - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteGroups.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteGroups.java deleted file mode 100644 index 742bc3f5f..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteGroups.java +++ /dev/null @@ -1,95 +0,0 @@ -package org.argeo.cms.e4.users.handlers; - -import java.util.List; - -import javax.inject.Inject; -import javax.inject.Named; - -import org.argeo.cms.auth.UserAdminUtils; -import org.argeo.cms.e4.users.GroupsView; -import org.argeo.cms.e4.users.UserAdminWrapper; -import org.eclipse.e4.core.di.annotations.CanExecute; -import org.eclipse.e4.core.di.annotations.Execute; -import org.eclipse.e4.ui.model.application.ui.basic.MPart; -import org.eclipse.e4.ui.services.IServiceConstants; -import org.eclipse.e4.ui.workbench.modeling.ESelectionService; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.swt.widgets.Display; -import org.osgi.service.useradmin.Group; -import org.osgi.service.useradmin.UserAdmin; -import org.osgi.service.useradmin.UserAdminEvent; - -/** Delete the selected groups */ -public class DeleteGroups { - // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + - // ".deleteGroups"; - - /* DEPENDENCY INJECTION */ - @Inject - private UserAdminWrapper userAdminWrapper; - - @Inject - ESelectionService selectionService; - - @SuppressWarnings("unchecked") - @Execute - public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part, ESelectionService selectionService) { - // ISelection selection = null;// HandlerUtil.getCurrentSelection(event); - // if (selection.isEmpty()) - // return null; - // - // List groups = new ArrayList(); - // Iterator it = ((IStructuredSelection) selection).iterator(); - - List selection = (List) selectionService.getSelection(); - if (selection == null) - return; - - StringBuilder builder = new StringBuilder(); - for (Group group : selection) { - Group currGroup = group; - String groupName = UserAdminUtils.getUserLocalId(currGroup.getName()); - // TODO add checks - builder.append(groupName).append("; "); - // groups.add(currGroup); - } - - if (!MessageDialog.openQuestion(Display.getCurrent().getActiveShell(), "Delete Groups", "Are you sure that you " - + "want to delete these groups?\n" + builder.substring(0, builder.length() - 2))) - return; - - userAdminWrapper.beginTransactionIfNeeded(); - UserAdmin userAdmin = userAdminWrapper.getUserAdmin(); - // IWorkbenchPage iwp = - // HandlerUtil.getActiveWorkbenchWindow(event).getActivePage(); - for (Group group : selection) { - String groupName = group.getName(); - // TODO find a way to close the editor cleanly if opened. Cannot be - // done through the UserAdminListeners, it causes a - // java.util.ConcurrentModificationException because disposing the - // editor unregisters and disposes the listener - // IEditorPart part = iwp.findEditor(new UserEditorInput(groupName)); - // if (part != null) - // iwp.closeEditor(part, false); - userAdmin.removeRole(groupName); - } - userAdminWrapper.commitOrNotifyTransactionStateChange(); - - // Update the view - for (Group group : selection) { - userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_REMOVED, group)); - } - - // return null; - } - - @CanExecute - public boolean canExecute(@Named(IServiceConstants.ACTIVE_PART) MPart part, ESelectionService selectionService) { - return part.getObject() instanceof GroupsView && selectionService.getSelection() != null; - } - - /* DEPENDENCY INJECTION */ - // public void setUserAdminWrapper(UserAdminWrapper userAdminWrapper) { - // this.userAdminWrapper = userAdminWrapper; - // } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteUsers.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteUsers.java deleted file mode 100644 index d1afd2210..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/DeleteUsers.java +++ /dev/null @@ -1,88 +0,0 @@ -package org.argeo.cms.e4.users.handlers; - -import java.util.List; - -import javax.inject.Inject; -import javax.inject.Named; - -import org.argeo.cms.auth.UserAdminUtils; -import org.argeo.cms.e4.users.UserAdminWrapper; -import org.argeo.cms.e4.users.UsersView; -import org.eclipse.e4.core.di.annotations.CanExecute; -import org.eclipse.e4.core.di.annotations.Execute; -import org.eclipse.e4.ui.model.application.ui.basic.MPart; -import org.eclipse.e4.ui.services.IServiceConstants; -import org.eclipse.e4.ui.workbench.modeling.ESelectionService; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.swt.widgets.Display; -import org.osgi.service.useradmin.User; -import org.osgi.service.useradmin.UserAdmin; -import org.osgi.service.useradmin.UserAdminEvent; - -/** Delete the selected users */ -public class DeleteUsers { - // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + ".deleteUsers"; - - /* DEPENDENCY INJECTION */ - @Inject - private UserAdminWrapper userAdminWrapper; - - @SuppressWarnings("unchecked") - @Execute - public void execute(@Named(IServiceConstants.ACTIVE_PART) MPart part, ESelectionService selectionService) { - // ISelection selection = null;// HandlerUtil.getCurrentSelection(event); - // if (selection.isEmpty()) - // return null; - List selection = (List) selectionService.getSelection(); - if (selection == null) - return; - -// Iterator it = ((IStructuredSelection) selection).iterator(); -// List users = new ArrayList(); - StringBuilder builder = new StringBuilder(); - - for(User user:selection) { - User currUser = user; -// User currUser = it.next(); - String userName = UserAdminUtils.getUserLocalId(currUser.getName()); - if (UserAdminUtils.isCurrentUser(currUser)) { - MessageDialog.openError(Display.getCurrent().getActiveShell(), "Deletion forbidden", - "You cannot delete your own user this way."); - return; - } - builder.append(userName).append("; "); -// users.add(currUser); - } - - if (!MessageDialog.openQuestion(Display.getCurrent().getActiveShell(), "Delete Users", - "Are you sure that you want to delete these users?\n" + builder.substring(0, builder.length() - 2))) - return; - - userAdminWrapper.beginTransactionIfNeeded(); - UserAdmin userAdmin = userAdminWrapper.getUserAdmin(); - // IWorkbenchPage iwp = - // HandlerUtil.getActiveWorkbenchWindow(event).getActivePage(); - - for (User user : selection) { - String userName = user.getName(); - // TODO find a way to close the editor cleanly if opened. Cannot be - // done through the UserAdminListeners, it causes a - // java.util.ConcurrentModificationException because disposing the - // editor unregisters and disposes the listener - // IEditorPart part = iwp.findEditor(new UserEditorInput(userName)); - // if (part != null) - // iwp.closeEditor(part, false); - userAdmin.removeRole(userName); - } - userAdminWrapper.commitOrNotifyTransactionStateChange(); - - for (User user : selection) { - userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_REMOVED, user)); - } - } - - @CanExecute - public boolean canExecute(@Named(IServiceConstants.ACTIVE_PART) MPart part, ESelectionService selectionService) { - return part.getObject() instanceof UsersView && selectionService.getSelection() != null; - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewGroup.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewGroup.java deleted file mode 100644 index d2ffa791b..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewGroup.java +++ /dev/null @@ -1,212 +0,0 @@ -package org.argeo.cms.e4.users.handlers; - -import java.util.Dictionary; -import java.util.Map; - -import javax.inject.Inject; - -import org.argeo.cms.CmsException; -import org.argeo.cms.e4.users.UserAdminWrapper; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.argeo.eclipse.ui.dialogs.ErrorFeedback; -import org.argeo.osgi.useradmin.UserAdminConf; -import org.argeo.util.naming.LdapAttrs; -import org.eclipse.e4.core.di.annotations.Execute; -import org.eclipse.jface.wizard.Wizard; -import org.eclipse.jface.wizard.WizardDialog; -import org.eclipse.jface.wizard.WizardPage; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.FocusEvent; -import org.eclipse.swt.events.FocusListener; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Combo; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; -import org.osgi.service.useradmin.Group; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.UserAdminEvent; - -/** Create a new group */ -public class NewGroup { - // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + ".newGroup"; - - /* DEPENDENCY INJECTION */ - @Inject - private UserAdminWrapper userAdminWrapper; - - @Execute - public Object execute() { - NewGroupWizard newGroupWizard = new NewGroupWizard(); - newGroupWizard.setWindowTitle("Group creation"); - WizardDialog dialog = new WizardDialog(Display.getCurrent().getActiveShell(), newGroupWizard); - dialog.open(); - return null; - } - - private class NewGroupWizard extends Wizard { - - // Pages - private MainGroupInfoWizardPage mainGroupInfo; - - // UI fields - private Text dNameTxt, commonNameTxt, descriptionTxt; - private Combo baseDnCmb; - - public NewGroupWizard() { - } - - @Override - public void addPages() { - mainGroupInfo = new MainGroupInfoWizardPage(); - addPage(mainGroupInfo); - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - @Override - public boolean performFinish() { - if (!canFinish()) - return false; - String commonName = commonNameTxt.getText(); - try { - userAdminWrapper.beginTransactionIfNeeded(); - String dn = getDn(commonName); - Group group = (Group) userAdminWrapper.getUserAdmin().createRole(dn, Role.GROUP); - Dictionary props = group.getProperties(); - String descStr = descriptionTxt.getText(); - if (EclipseUiUtils.notEmpty(descStr)) - props.put(LdapAttrs.description.name(), descStr); - userAdminWrapper.commitOrNotifyTransactionStateChange(); - userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CREATED, group)); - return true; - } catch (Exception e) { - ErrorFeedback.show("Cannot create new group " + commonName, e); - return false; - } - } - - private class MainGroupInfoWizardPage extends WizardPage implements FocusListener { - private static final long serialVersionUID = -3150193365151601807L; - - public MainGroupInfoWizardPage() { - super("Main"); - setTitle("General information"); - setMessage("Please choose a domain, provide a common name " + "and a free description"); - } - - @Override - public void createControl(Composite parent) { - Composite bodyCmp = new Composite(parent, SWT.NONE); - setControl(bodyCmp); - bodyCmp.setLayout(new GridLayout(2, false)); - - dNameTxt = EclipseUiUtils.createGridLT(bodyCmp, "Distinguished name"); - dNameTxt.setEnabled(false); - - baseDnCmb = createGridLC(bodyCmp, "Base DN"); - // Initialise before adding the listener to avoid NPE - initialiseDnCmb(baseDnCmb); - baseDnCmb.addFocusListener(this); - - commonNameTxt = EclipseUiUtils.createGridLT(bodyCmp, "Common name"); - commonNameTxt.addFocusListener(this); - - Label descLbl = new Label(bodyCmp, SWT.LEAD); - descLbl.setText("Description"); - descLbl.setLayoutData(new GridData(SWT.RIGHT, SWT.TOP, false, false)); - descriptionTxt = new Text(bodyCmp, SWT.LEAD | SWT.MULTI | SWT.WRAP | SWT.BORDER); - descriptionTxt.setLayoutData(EclipseUiUtils.fillAll()); - descriptionTxt.addFocusListener(this); - - // Initialize buttons - setPageComplete(false); - getContainer().updateButtons(); - } - - @Override - public void focusLost(FocusEvent event) { - String name = commonNameTxt.getText(); - if (EclipseUiUtils.isEmpty(name)) - dNameTxt.setText(""); - else - dNameTxt.setText(getDn(name)); - - String message = checkComplete(); - if (message != null) { - setMessage(message, WizardPage.ERROR); - setPageComplete(false); - } else { - setMessage("Complete", WizardPage.INFORMATION); - setPageComplete(true); - } - getContainer().updateButtons(); - } - - @Override - public void focusGained(FocusEvent event) { - } - - /** @return the error message or null if complete */ - protected String checkComplete() { - String name = commonNameTxt.getText(); - - if (name.trim().equals("")) - return "Common name must not be empty"; - Role role = userAdminWrapper.getUserAdmin().getRole(getDn(name)); - if (role != null) - return "Group " + name + " already exists"; - return null; - } - - @Override - public void setVisible(boolean visible) { - super.setVisible(visible); - if (visible) - if (baseDnCmb.getSelectionIndex() == -1) - baseDnCmb.setFocus(); - else - commonNameTxt.setFocus(); - } - } - - private Map getDns() { - return userAdminWrapper.getKnownBaseDns(true); - } - - private String getDn(String cn) { - Map dns = getDns(); - String bdn = baseDnCmb.getText(); - if (EclipseUiUtils.notEmpty(bdn)) { - Dictionary props = UserAdminConf.uriAsProperties(dns.get(bdn)); - String dn = LdapAttrs.cn.name() + "=" + cn + "," + UserAdminConf.groupBase.getValue(props) + "," + bdn; - return dn; - } - return null; - } - - private void initialiseDnCmb(Combo combo) { - Map dns = userAdminWrapper.getKnownBaseDns(true); - if (dns.isEmpty()) - throw new CmsException("No writable base dn found. Cannot create group"); - combo.setItems(dns.keySet().toArray(new String[0])); - if (dns.size() == 1) - combo.select(0); - } - } - - private Combo createGridLC(Composite parent, String label) { - Label lbl = new Label(parent, SWT.LEAD); - lbl.setText(label); - lbl.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false)); - Combo combo = new Combo(parent, SWT.LEAD | SWT.BORDER | SWT.READ_ONLY); - combo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); - return combo; - } - - /* DEPENDENCY INJECTION */ - public void setUserAdminWrapper(UserAdminWrapper userAdminWrapper) { - this.userAdminWrapper = userAdminWrapper; - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewUser.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewUser.java deleted file mode 100644 index 07d82c749..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/NewUser.java +++ /dev/null @@ -1,287 +0,0 @@ -package org.argeo.cms.e4.users.handlers; - -import java.util.Dictionary; -import java.util.List; -import java.util.Map; - -import javax.inject.Inject; -import javax.naming.InvalidNameException; -import javax.naming.ldap.LdapName; -import javax.naming.ldap.Rdn; - -import org.argeo.cms.CmsException; -import org.argeo.cms.auth.UserAdminUtils; -import org.argeo.cms.e4.users.UiAdminUtils; -import org.argeo.cms.e4.users.UserAdminWrapper; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.argeo.eclipse.ui.dialogs.ErrorFeedback; -import org.argeo.osgi.useradmin.UserAdminConf; -import org.argeo.util.naming.LdapAttrs; -import org.eclipse.e4.core.di.annotations.Execute; -import org.eclipse.jface.wizard.Wizard; -import org.eclipse.jface.wizard.WizardDialog; -import org.eclipse.jface.wizard.WizardPage; -import org.eclipse.swt.SWT; -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.Combo; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.User; -import org.osgi.service.useradmin.UserAdminEvent; - -/** Open a wizard that enables creation of a new user. */ -public class NewUser { - // private final static Log log = LogFactory.getLog(NewUser.class); - // public final static String ID = WorkbenchUiPlugin.PLUGIN_ID + ".newUser"; - - /* DEPENDENCY INJECTION */ - @Inject - private UserAdminWrapper userAdminWrapper; - - @Execute - public Object execute() { - NewUserWizard newUserWizard = new NewUserWizard(); - newUserWizard.setWindowTitle("User creation"); - WizardDialog dialog = new WizardDialog(Display.getCurrent().getActiveShell(), newUserWizard); - dialog.open(); - return null; - } - - private class NewUserWizard extends Wizard { - - // pages - private MainUserInfoWizardPage mainUserInfo; - - // End user fields - private Text dNameTxt, usernameTxt, firstNameTxt, lastNameTxt, primaryMailTxt, pwd1Txt, pwd2Txt; - private Combo baseDnCmb; - - public NewUserWizard() { - - } - - @Override - public void addPages() { - mainUserInfo = new MainUserInfoWizardPage(); - addPage(mainUserInfo); - String message = "Default wizard that also eases user creation tests:\n " - + "Mail and last name are automatically " - + "generated form the uid. Password are defauted to 'demo'."; - mainUserInfo.setMessage(message, WizardPage.WARNING); - } - - @SuppressWarnings({ "rawtypes", "unchecked" }) - @Override - public boolean performFinish() { - if (!canFinish()) - return false; - String username = mainUserInfo.getUsername(); - userAdminWrapper.beginTransactionIfNeeded(); - try { - User user = (User) userAdminWrapper.getUserAdmin().createRole(getDn(username), Role.USER); - - Dictionary props = user.getProperties(); - - String lastNameStr = lastNameTxt.getText(); - if (EclipseUiUtils.notEmpty(lastNameStr)) - props.put(LdapAttrs.sn.name(), lastNameStr); - - String firstNameStr = firstNameTxt.getText(); - if (EclipseUiUtils.notEmpty(firstNameStr)) - props.put(LdapAttrs.givenName.name(), firstNameStr); - - String cn = UserAdminUtils.buildDefaultCn(firstNameStr, lastNameStr); - if (EclipseUiUtils.notEmpty(cn)) - props.put(LdapAttrs.cn.name(), cn); - - String mailStr = primaryMailTxt.getText(); - if (EclipseUiUtils.notEmpty(mailStr)) - props.put(LdapAttrs.mail.name(), mailStr); - - char[] password = mainUserInfo.getPassword(); - user.getCredentials().put(null, password); - userAdminWrapper.commitOrNotifyTransactionStateChange(); - userAdminWrapper.notifyListeners(new UserAdminEvent(null, UserAdminEvent.ROLE_CREATED, user)); - return true; - } catch (Exception e) { - ErrorFeedback.show("Cannot create new user " + username, e); - return false; - } - } - - private class MainUserInfoWizardPage extends WizardPage implements ModifyListener { - private static final long serialVersionUID = -3150193365151601807L; - - public MainUserInfoWizardPage() { - super("Main"); - setTitle("Required Information"); - } - - @Override - public void createControl(Composite parent) { - Composite composite = new Composite(parent, SWT.NONE); - composite.setLayout(new GridLayout(2, false)); - dNameTxt = EclipseUiUtils.createGridLT(composite, "Distinguished name", this); - dNameTxt.setEnabled(false); - - baseDnCmb = createGridLC(composite, "Base DN"); - initialiseDnCmb(baseDnCmb); - baseDnCmb.addModifyListener(this); - baseDnCmb.addModifyListener(new ModifyListener() { - private static final long serialVersionUID = -1435351236582736843L; - - @Override - public void modifyText(ModifyEvent event) { - String name = usernameTxt.getText(); - dNameTxt.setText(getDn(name)); - } - }); - - usernameTxt = EclipseUiUtils.createGridLT(composite, "Local ID", this); - usernameTxt.addModifyListener(new ModifyListener() { - private static final long serialVersionUID = -1435351236582736843L; - - @Override - public void modifyText(ModifyEvent event) { - String name = usernameTxt.getText(); - if (name.trim().equals("")) { - dNameTxt.setText(""); - lastNameTxt.setText(""); - primaryMailTxt.setText(""); - pwd1Txt.setText(""); - pwd2Txt.setText(""); - } else { - dNameTxt.setText(getDn(name)); - lastNameTxt.setText(name.toUpperCase()); - primaryMailTxt.setText(getMail(name)); - pwd1Txt.setText("demo"); - pwd2Txt.setText("demo"); - } - } - }); - - primaryMailTxt = EclipseUiUtils.createGridLT(composite, "Email", this); - firstNameTxt = EclipseUiUtils.createGridLT(composite, "First name", this); - lastNameTxt = EclipseUiUtils.createGridLT(composite, "Last name", this); - pwd1Txt = EclipseUiUtils.createGridLP(composite, "Password", this); - pwd2Txt = EclipseUiUtils.createGridLP(composite, "Repeat password", this); - setControl(composite); - - // Initialize buttons - setPageComplete(false); - getContainer().updateButtons(); - } - - @Override - public void modifyText(ModifyEvent event) { - String message = checkComplete(); - if (message != null) { - setMessage(message, WizardPage.ERROR); - setPageComplete(false); - } else { - setMessage("Complete", WizardPage.INFORMATION); - setPageComplete(true); - } - getContainer().updateButtons(); - } - - /** @return error message or null if complete */ - protected String checkComplete() { - String name = usernameTxt.getText(); - - if (name.trim().equals("")) - return "User name must not be empty"; - Role role = userAdminWrapper.getUserAdmin().getRole(getDn(name)); - if (role != null) - return "User " + name + " already exists"; - if (!primaryMailTxt.getText().matches(UiAdminUtils.EMAIL_PATTERN)) - return "Not a valid email address"; - if (lastNameTxt.getText().trim().equals("")) - return "Specify a last name"; - if (pwd1Txt.getText().trim().equals("")) - return "Specify a password"; - if (pwd2Txt.getText().trim().equals("")) - return "Repeat the password"; - if (!pwd2Txt.getText().equals(pwd1Txt.getText())) - return "Passwords are different"; - return null; - } - - @Override - public void setVisible(boolean visible) { - super.setVisible(visible); - if (visible) - if (baseDnCmb.getSelectionIndex() == -1) - baseDnCmb.setFocus(); - else - usernameTxt.setFocus(); - } - - public String getUsername() { - return usernameTxt.getText(); - } - - public char[] getPassword() { - return pwd1Txt.getTextChars(); - } - - } - - private Map getDns() { - return userAdminWrapper.getKnownBaseDns(true); - } - - private String getDn(String uid) { - Map dns = getDns(); - String bdn = baseDnCmb.getText(); - if (EclipseUiUtils.notEmpty(bdn)) { - Dictionary props = UserAdminConf.uriAsProperties(dns.get(bdn)); - String dn = LdapAttrs.uid.name() + "=" + uid + "," + UserAdminConf.userBase.getValue(props) + "," + bdn; - return dn; - } - return null; - } - - private void initialiseDnCmb(Combo combo) { - Map dns = userAdminWrapper.getKnownBaseDns(true); - if (dns.isEmpty()) - throw new CmsException("No writable base dn found. Cannot create user"); - combo.setItems(dns.keySet().toArray(new String[0])); - if (dns.size() == 1) - combo.select(0); - } - - private String getMail(String username) { - if (baseDnCmb.getSelectionIndex() == -1) - return null; - String baseDn = baseDnCmb.getText(); - try { - LdapName name = new LdapName(baseDn); - List rdns = name.getRdns(); - return username + "@" + (String) rdns.get(1).getValue() + '.' + (String) rdns.get(0).getValue(); - } catch (InvalidNameException e) { - throw new CmsException("Unable to generate mail for " + username + " with base dn " + baseDn, e); - } - } - } - - private Combo createGridLC(Composite parent, String label) { - Label lbl = new Label(parent, SWT.LEAD); - lbl.setText(label); - lbl.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false)); - Combo combo = new Combo(parent, SWT.LEAD | SWT.BORDER | SWT.READ_ONLY); - combo.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); - return combo; - } - - /* DEPENDENCY INJECTION */ - public void setUserAdminWrapper(UserAdminWrapper userAdminWrapper) { - this.userAdminWrapper = userAdminWrapper; - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/package-info.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/package-info.java deleted file mode 100644 index cf3db1d16..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/handlers/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Users management handlers. */ -package org.argeo.cms.e4.users.handlers; \ No newline at end of file diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/package-info.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/package-info.java deleted file mode 100644 index c6f14b0cf..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Users management perspective. */ -package org.argeo.cms.e4.users; \ No newline at end of file diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/CommonNameLP.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/CommonNameLP.java deleted file mode 100644 index 2d8db67d7..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/CommonNameLP.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.argeo.cms.e4.users.providers; - -import org.argeo.cms.auth.UserAdminUtils; -import org.argeo.util.naming.LdapAttrs; -import org.osgi.service.useradmin.User; - -/** Simply declare a label provider that returns the common name of a user */ -public class CommonNameLP extends UserAdminAbstractLP { - private static final long serialVersionUID = 5256703081044911941L; - - @Override - public String getText(User user) { - return UserAdminUtils.getProperty(user, LdapAttrs.cn.name()); - } - - @Override - public String getToolTipText(Object element) { - return UserAdminUtils.getProperty((User) element, LdapAttrs.DN); - } - -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/DomainNameLP.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/DomainNameLP.java deleted file mode 100644 index e23729da8..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/DomainNameLP.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.argeo.cms.e4.users.providers; - -import org.argeo.cms.auth.UserAdminUtils; -import org.osgi.service.useradmin.User; - -/** The human friendly domain name for the corresponding user. */ -public class DomainNameLP extends UserAdminAbstractLP { - private static final long serialVersionUID = 5256703081044911941L; - - @Override - public String getText(User user) { - return UserAdminUtils.getDomainName(user); - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/MailLP.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/MailLP.java deleted file mode 100644 index 52d3b858f..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/MailLP.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.argeo.cms.e4.users.providers; - -import org.argeo.cms.auth.UserAdminUtils; -import org.argeo.util.naming.LdapAttrs; -import org.osgi.service.useradmin.User; - -/** Simply declare a label provider that returns the Primary Mail of a user */ -public class MailLP extends UserAdminAbstractLP { - private static final long serialVersionUID = 8329764452141982707L; - - @Override - public String getText(User user) { - return UserAdminUtils.getProperty(user, LdapAttrs.mail.name()); - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/RoleIconLP.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/RoleIconLP.java deleted file mode 100644 index 8c94093e4..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/RoleIconLP.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.argeo.cms.e4.users.providers; - -import org.argeo.api.cms.CmsContext; -import org.argeo.api.cms.CmsConstants; -import org.argeo.cms.auth.UserAdminUtils; -import org.argeo.cms.e4.users.SecurityAdminImages; -import org.argeo.util.naming.LdapAttrs; -import org.eclipse.swt.graphics.Image; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.User; - -/** Provide a bundle specific image depending on the current user type */ -public class RoleIconLP extends UserAdminAbstractLP { - private static final long serialVersionUID = 6550449442061090388L; - - @Override - public String getText(User user) { - return ""; - } - - @Override - public Image getImage(Object element) { - User user = (User) element; - String dn = user.getName(); - if (dn.endsWith(CmsConstants.ROLES_BASEDN)) - return SecurityAdminImages.ICON_ROLE; - else if (user.getType() == Role.GROUP) { - String businessCategory = UserAdminUtils.getProperty(user, LdapAttrs.businessCategory); - if (businessCategory != null && businessCategory.equals(CmsContext.WORKGROUP)) - return SecurityAdminImages.ICON_WORKGROUP; - return SecurityAdminImages.ICON_GROUP; - } else - return SecurityAdminImages.ICON_USER; - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserAdminAbstractLP.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserAdminAbstractLP.java deleted file mode 100644 index e33b1531d..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserAdminAbstractLP.java +++ /dev/null @@ -1,66 +0,0 @@ -package org.argeo.cms.e4.users.providers; - -import javax.naming.InvalidNameException; -import javax.naming.ldap.LdapName; - -import org.argeo.cms.CmsException; -import org.argeo.cms.auth.UserAdminUtils; -import org.eclipse.jface.resource.JFaceResources; -import org.eclipse.jface.viewers.ColumnLabelProvider; -import org.eclipse.swt.SWT; -import org.eclipse.swt.graphics.Font; -import org.eclipse.swt.widgets.Display; -import org.osgi.service.useradmin.User; - -/** - * Utility class that add font modifications to a column label provider - * depending on the given user properties - */ -public abstract class UserAdminAbstractLP extends ColumnLabelProvider { - private static final long serialVersionUID = 137336765024922368L; - - // private Font italic; - private Font bold; - - @Override - public Font getFont(Object element) { - // Self as bold - try { - LdapName selfUserName = UserAdminUtils.getCurrentUserLdapName(); - String userName = ((User) element).getName(); - LdapName userLdapName = new LdapName(userName); - if (userLdapName.equals(selfUserName)) { - if (bold == null) - bold = JFaceResources.getFontRegistry() - .defaultFontDescriptor().setStyle(SWT.BOLD) - .createFont(Display.getCurrent()); - return bold; - } - } catch (InvalidNameException e) { - throw new CmsException("cannot parse dn for " + element, e); - } - - // Disabled as Italic - // Node userProfile = (Node) elem; - // if (!userProfile.getProperty(ARGEO_ENABLED).getBoolean()) - // return italic; - - return null; - // return super.getFont(element); - } - - @Override - public String getText(Object element) { - User user = (User) element; - return getText(user); - } - - public void setDisplay(Display display) { - // italic = JFaceResources.getFontRegistry().defaultFontDescriptor() - // .setStyle(SWT.ITALIC).createFont(display); - bold = JFaceResources.getFontRegistry().defaultFontDescriptor() - .setStyle(SWT.BOLD).createFont(Display.getCurrent()); - } - - public abstract String getText(User user); -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserDragListener.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserDragListener.java deleted file mode 100644 index 56a26244b..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserDragListener.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.argeo.cms.e4.users.providers; - -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.swt.dnd.DragSourceEvent; -import org.eclipse.swt.dnd.DragSourceListener; -import org.osgi.service.useradmin.User; - -/** Default drag listener to modify group and users via the UI */ -public class UserDragListener implements DragSourceListener { - private static final long serialVersionUID = -2074337775033781454L; - private final Viewer viewer; - - public UserDragListener(Viewer viewer) { - this.viewer = viewer; - } - - public void dragStart(DragSourceEvent event) { - // TODO implement finer checks - IStructuredSelection selection = (IStructuredSelection) viewer - .getSelection(); - if (selection.isEmpty() || selection.size() > 1) - event.doit = false; - else - event.doit = true; - } - - public void dragSetData(DragSourceEvent event) { - // TODO Support multiple selection - Object obj = ((IStructuredSelection) viewer.getSelection()) - .getFirstElement(); - if (obj != null) { - User user = (User) obj; - event.data = user.getName(); - } - } - - public void dragFinished(DragSourceEvent event) { - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserFilter.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserFilter.java deleted file mode 100644 index 154b04725..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserFilter.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.argeo.cms.e4.users.providers; - -import static org.argeo.eclipse.ui.EclipseUiUtils.notEmpty; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.cms.auth.UserAdminUtils; -import org.argeo.util.naming.LdapAttrs; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.jface.viewers.ViewerFilter; -import org.osgi.service.useradmin.User; - -/** - * Filter user list using JFace mechanism on the client (yet on the server) side - * rather than having the UserAdmin to process the search - */ -public class UserFilter extends ViewerFilter { - private static final long serialVersionUID = 5082509381672880568L; - - private String searchString; - private boolean showSystemRole = true; - - private final String[] knownProps = { LdapAttrs.DN, LdapAttrs.cn.name(), LdapAttrs.givenName.name(), - LdapAttrs.sn.name(), LdapAttrs.uid.name(), LdapAttrs.description.name(), LdapAttrs.mail.name() }; - - public void setSearchText(String s) { - // ensure that the value can be used for matching - if (notEmpty(s)) - searchString = ".*" + s.toLowerCase() + ".*"; - else - searchString = ".*"; - } - - public void setShowSystemRole(boolean showSystemRole) { - this.showSystemRole = showSystemRole; - } - - @Override - public boolean select(Viewer viewer, Object parentElement, Object element) { - User user = (User) element; - if (!showSystemRole && user.getName().matches(".*(" + CmsConstants.ROLES_BASEDN + ")")) - // UserAdminUtils.getProperty(user, LdifName.dn.name()) - // .toLowerCase().endsWith(AuthConstants.ROLES_BASEDN)) - return false; - - if (searchString == null || searchString.length() == 0) - return true; - - if (user.getName().matches(searchString)) - return true; - - for (String key : knownProps) { - String currVal = UserAdminUtils.getProperty(user, key); - if (notEmpty(currVal) && currVal.toLowerCase().matches(searchString)) - return true; - } - return false; - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserNameLP.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserNameLP.java deleted file mode 100644 index 3cd00eb2b..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/UserNameLP.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.argeo.cms.e4.users.providers; - -import org.osgi.service.useradmin.User; - -/** Simply declare a label provider that returns the username of a user */ -public class UserNameLP extends UserAdminAbstractLP { - private static final long serialVersionUID = 6550449442061090388L; - - @Override - public String getText(User user) { - return user.getName(); - } -} diff --git a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/package-info.java b/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/package-info.java deleted file mode 100644 index 33bef8dee..000000000 --- a/org.argeo.cms.e4/src/org/argeo/cms/e4/users/providers/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Users management content providers. */ -package org.argeo.cms.e4.users.providers; \ No newline at end of file diff --git a/org.argeo.cms.jcr/.classpath b/org.argeo.cms.jcr/.classpath deleted file mode 100644 index 4a00becd8..000000000 --- a/org.argeo.cms.jcr/.classpath +++ /dev/null @@ -1,11 +0,0 @@ - - - - - - - - - - - diff --git a/org.argeo.cms.jcr/.project b/org.argeo.cms.jcr/.project deleted file mode 100644 index 3e470f829..000000000 --- a/org.argeo.cms.jcr/.project +++ /dev/null @@ -1,33 +0,0 @@ - - - org.argeo.cms.jcr - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.pde.ManifestBuilder - - - - - org.eclipse.pde.SchemaBuilder - - - - - org.eclipse.pde.ds.core.builder - - - - - - org.eclipse.pde.PluginNature - org.eclipse.jdt.core.javanature - - diff --git a/org.argeo.cms.jcr/.settings/org.eclipse.jdt.core.prefs b/org.argeo.cms.jcr/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index 7e2e11935..000000000 --- a/org.argeo.cms.jcr/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -1,101 +0,0 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=disabled -org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore -org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jdt.annotation.NonNull -org.eclipse.jdt.core.compiler.annotation.nonnull.secondary= -org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jdt.annotation.NonNullByDefault -org.eclipse.jdt.core.compiler.annotation.nonnullbydefault.secondary= -org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jdt.annotation.Nullable -org.eclipse.jdt.core.compiler.annotation.nullable.secondary= -org.eclipse.jdt.core.compiler.annotation.nullanalysis=disabled -org.eclipse.jdt.core.compiler.problem.APILeak=warning -org.eclipse.jdt.core.compiler.problem.annotationSuperInterface=warning -org.eclipse.jdt.core.compiler.problem.autoboxing=ignore -org.eclipse.jdt.core.compiler.problem.comparingIdentical=warning -org.eclipse.jdt.core.compiler.problem.deadCode=warning -org.eclipse.jdt.core.compiler.problem.deprecation=warning -org.eclipse.jdt.core.compiler.problem.deprecationInDeprecatedCode=disabled -org.eclipse.jdt.core.compiler.problem.deprecationWhenOverridingDeprecatedMethod=disabled -org.eclipse.jdt.core.compiler.problem.discouragedReference=warning -org.eclipse.jdt.core.compiler.problem.emptyStatement=ignore -org.eclipse.jdt.core.compiler.problem.explicitlyClosedAutoCloseable=ignore -org.eclipse.jdt.core.compiler.problem.fallthroughCase=ignore -org.eclipse.jdt.core.compiler.problem.fatalOptionalError=disabled -org.eclipse.jdt.core.compiler.problem.fieldHiding=ignore -org.eclipse.jdt.core.compiler.problem.finalParameterBound=warning -org.eclipse.jdt.core.compiler.problem.finallyBlockNotCompletingNormally=warning -org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.problem.hiddenCatchBlock=warning -org.eclipse.jdt.core.compiler.problem.includeNullInfoFromAsserts=disabled -org.eclipse.jdt.core.compiler.problem.incompatibleNonInheritedInterfaceMethod=warning -org.eclipse.jdt.core.compiler.problem.incompleteEnumSwitch=warning -org.eclipse.jdt.core.compiler.problem.indirectStaticAccess=ignore -org.eclipse.jdt.core.compiler.problem.localVariableHiding=ignore -org.eclipse.jdt.core.compiler.problem.methodWithConstructorName=warning -org.eclipse.jdt.core.compiler.problem.missingDefaultCase=ignore -org.eclipse.jdt.core.compiler.problem.missingDeprecatedAnnotation=ignore -org.eclipse.jdt.core.compiler.problem.missingEnumCaseDespiteDefault=disabled -org.eclipse.jdt.core.compiler.problem.missingHashCodeMethod=ignore -org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotation=ignore -org.eclipse.jdt.core.compiler.problem.missingOverrideAnnotationForInterfaceMethodImplementation=enabled -org.eclipse.jdt.core.compiler.problem.missingSerialVersion=warning -org.eclipse.jdt.core.compiler.problem.missingSynchronizedOnInheritedMethod=ignore -org.eclipse.jdt.core.compiler.problem.noEffectAssignment=warning -org.eclipse.jdt.core.compiler.problem.noImplicitStringConversion=warning -org.eclipse.jdt.core.compiler.problem.nonExternalizedStringLiteral=ignore -org.eclipse.jdt.core.compiler.problem.nonnullParameterAnnotationDropped=warning -org.eclipse.jdt.core.compiler.problem.nonnullTypeVariableFromLegacyInvocation=warning -org.eclipse.jdt.core.compiler.problem.nullAnnotationInferenceConflict=error -org.eclipse.jdt.core.compiler.problem.nullReference=warning -org.eclipse.jdt.core.compiler.problem.nullSpecViolation=error -org.eclipse.jdt.core.compiler.problem.nullUncheckedConversion=warning -org.eclipse.jdt.core.compiler.problem.overridingPackageDefaultMethod=warning -org.eclipse.jdt.core.compiler.problem.parameterAssignment=ignore -org.eclipse.jdt.core.compiler.problem.pessimisticNullAnalysisForFreeTypeVariables=warning -org.eclipse.jdt.core.compiler.problem.possibleAccidentalBooleanAssignment=ignore -org.eclipse.jdt.core.compiler.problem.potentialNullReference=ignore -org.eclipse.jdt.core.compiler.problem.potentiallyUnclosedCloseable=ignore -org.eclipse.jdt.core.compiler.problem.rawTypeReference=warning -org.eclipse.jdt.core.compiler.problem.redundantNullAnnotation=warning -org.eclipse.jdt.core.compiler.problem.redundantNullCheck=ignore -org.eclipse.jdt.core.compiler.problem.redundantSpecificationOfTypeArguments=ignore -org.eclipse.jdt.core.compiler.problem.redundantSuperinterface=ignore -org.eclipse.jdt.core.compiler.problem.reportMethodCanBePotentiallyStatic=ignore -org.eclipse.jdt.core.compiler.problem.reportMethodCanBeStatic=ignore -org.eclipse.jdt.core.compiler.problem.specialParameterHidingField=disabled -org.eclipse.jdt.core.compiler.problem.staticAccessReceiver=warning -org.eclipse.jdt.core.compiler.problem.suppressOptionalErrors=disabled -org.eclipse.jdt.core.compiler.problem.suppressWarnings=enabled -org.eclipse.jdt.core.compiler.problem.syntacticNullAnalysisForFields=disabled -org.eclipse.jdt.core.compiler.problem.syntheticAccessEmulation=ignore -org.eclipse.jdt.core.compiler.problem.terminalDeprecation=warning -org.eclipse.jdt.core.compiler.problem.typeParameterHiding=warning -org.eclipse.jdt.core.compiler.problem.unavoidableGenericTypeProblems=enabled -org.eclipse.jdt.core.compiler.problem.uncheckedTypeOperation=warning -org.eclipse.jdt.core.compiler.problem.unclosedCloseable=warning -org.eclipse.jdt.core.compiler.problem.undocumentedEmptyBlock=ignore -org.eclipse.jdt.core.compiler.problem.unhandledWarningToken=warning -org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentType=warning -org.eclipse.jdt.core.compiler.problem.unlikelyCollectionMethodArgumentTypeStrict=disabled -org.eclipse.jdt.core.compiler.problem.unlikelyEqualsArgumentType=info -org.eclipse.jdt.core.compiler.problem.unnecessaryElse=ignore -org.eclipse.jdt.core.compiler.problem.unnecessaryTypeCheck=ignore -org.eclipse.jdt.core.compiler.problem.unqualifiedFieldAccess=ignore -org.eclipse.jdt.core.compiler.problem.unstableAutoModuleName=warning -org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownException=ignore -org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionExemptExceptionAndThrowable=enabled -org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionIncludeDocCommentReference=enabled -org.eclipse.jdt.core.compiler.problem.unusedDeclaredThrownExceptionWhenOverriding=disabled -org.eclipse.jdt.core.compiler.problem.unusedExceptionParameter=ignore -org.eclipse.jdt.core.compiler.problem.unusedImport=warning -org.eclipse.jdt.core.compiler.problem.unusedLabel=warning -org.eclipse.jdt.core.compiler.problem.unusedLocal=warning -org.eclipse.jdt.core.compiler.problem.unusedObjectAllocation=ignore -org.eclipse.jdt.core.compiler.problem.unusedParameter=ignore -org.eclipse.jdt.core.compiler.problem.unusedParameterIncludeDocCommentReference=enabled -org.eclipse.jdt.core.compiler.problem.unusedParameterWhenImplementingAbstract=disabled -org.eclipse.jdt.core.compiler.problem.unusedParameterWhenOverridingConcrete=disabled -org.eclipse.jdt.core.compiler.problem.unusedPrivateMember=warning -org.eclipse.jdt.core.compiler.problem.unusedTypeParameter=ignore -org.eclipse.jdt.core.compiler.problem.unusedWarningToken=warning -org.eclipse.jdt.core.compiler.problem.varargsArgumentNeedCast=warning diff --git a/org.argeo.cms.jcr/OSGI-INF/dataServletContext.xml b/org.argeo.cms.jcr/OSGI-INF/dataServletContext.xml deleted file mode 100644 index f5fc8deaa..000000000 --- a/org.argeo.cms.jcr/OSGI-INF/dataServletContext.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/org.argeo.cms.jcr/OSGI-INF/filesServlet.xml b/org.argeo.cms.jcr/OSGI-INF/filesServlet.xml deleted file mode 100644 index a283ef075..000000000 --- a/org.argeo.cms.jcr/OSGI-INF/filesServlet.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/org.argeo.cms.jcr/OSGI-INF/filesServletContext.xml b/org.argeo.cms.jcr/OSGI-INF/filesServletContext.xml deleted file mode 100644 index 5fb56e3ed..000000000 --- a/org.argeo.cms.jcr/OSGI-INF/filesServletContext.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/org.argeo.cms.jcr/OSGI-INF/jcrDeployment.xml b/org.argeo.cms.jcr/OSGI-INF/jcrDeployment.xml deleted file mode 100644 index a94b15168..000000000 --- a/org.argeo.cms.jcr/OSGI-INF/jcrDeployment.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/org.argeo.cms.jcr/OSGI-INF/jcrFsProvider.xml b/org.argeo.cms.jcr/OSGI-INF/jcrFsProvider.xml deleted file mode 100644 index e26453b06..000000000 --- a/org.argeo.cms.jcr/OSGI-INF/jcrFsProvider.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/org.argeo.cms.jcr/OSGI-INF/jcrRepositoryFactory.xml b/org.argeo.cms.jcr/OSGI-INF/jcrRepositoryFactory.xml deleted file mode 100644 index b43b51920..000000000 --- a/org.argeo.cms.jcr/OSGI-INF/jcrRepositoryFactory.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/org.argeo.cms.jcr/OSGI-INF/jcrServletContext.xml b/org.argeo.cms.jcr/OSGI-INF/jcrServletContext.xml deleted file mode 100644 index a0885bbc5..000000000 --- a/org.argeo.cms.jcr/OSGI-INF/jcrServletContext.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/org.argeo.cms.jcr/OSGI-INF/repositoryContextsFactory.xml b/org.argeo.cms.jcr/OSGI-INF/repositoryContextsFactory.xml deleted file mode 100644 index db2bfaa26..000000000 --- a/org.argeo.cms.jcr/OSGI-INF/repositoryContextsFactory.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/org.argeo.cms.jcr/bnd.bnd b/org.argeo.cms.jcr/bnd.bnd deleted file mode 100644 index 71071f697..000000000 --- a/org.argeo.cms.jcr/bnd.bnd +++ /dev/null @@ -1,37 +0,0 @@ -Bundle-Activator: org.argeo.cms.jcr.internal.osgi.CmsJcrActivator - -Provide-Capability:\ -cms.datamodel; name=jcrx; cnd=/org/argeo/jcr/jcrx.cnd; abstract=true,\ -cms.datamodel; name=argeo; cnd=/org/argeo/cms/jcr/argeo.cnd; abstract=true,\ -cms.datamodel;name=ldap; cnd=/org/argeo/cms/jcr/ldap.cnd; abstract=true,\ -osgi.service;objectClass="javax.jcr.Repository" - -Import-Package:\ -org.argeo.cms.servlet,\ -javax.jcr.security,\ -org.h2;resolution:=optional,\ -org.postgresql;version="[42,43)";resolution:=optional,\ -org.apache.jackrabbit.webdav.server,\ -org.apache.jackrabbit.webdav.jcr,\ -org.apache.commons.httpclient.cookie;resolution:=optional,\ -org.osgi.framework.namespace;version=0.0.0,\ -org.osgi.*;version=0.0.0,\ -org.osgi.service.http.whiteboard,\ -org.apache.jackrabbit.api,\ -org.apache.jackrabbit.commons,\ -org.apache.jackrabbit.spi,\ -org.apache.jackrabbit.spi2dav,\ -org.apache.jackrabbit.spi2davex,\ -org.apache.jackrabbit.webdav,\ -junit.*;resolution:=optional,\ -* - -Service-Component:\ -OSGI-INF/repositoryContextsFactory.xml,\ -OSGI-INF/jcrRepositoryFactory.xml,\ -OSGI-INF/jcrFsProvider.xml,\ -OSGI-INF/jcrDeployment.xml,\ -OSGI-INF/jcrServletContext.xml,\ -OSGI-INF/dataServletContext.xml,\ -OSGI-INF/filesServletContext.xml,\ -OSGI-INF/filesServlet.xml diff --git a/org.argeo.cms.jcr/build.properties b/org.argeo.cms.jcr/build.properties deleted file mode 100644 index 3ddcf97c6..000000000 --- a/org.argeo.cms.jcr/build.properties +++ /dev/null @@ -1,28 +0,0 @@ -output.. = bin/ -bin.includes = META-INF/,\ - .,\ - OSGI-INF/jcrDeployment.xml,\ - OSGI-INF/repositoryContextsFactory.xml,\ - OSGI-INF/jcrRepositoryFactory.xml,\ - OSGI-INF/jcrFsProvider.xml -source.. = src/ -additional.bundles = org.apache.jackrabbit.core,\ - javax.jcr,\ - org.apache.jackrabbit.api,\ - org.apache.jackrabbit.data,\ - org.apache.jackrabbit.jcr.commons,\ - org.apache.jackrabbit.spi,\ - org.apache.jackrabbit.spi.commons,\ - org.slf4j.api,\ - org.apache.commons.collections,\ - EDU.oswego.cs.dl.util.concurrent,\ - org.apache.lucene,\ - org.apache.tika.core,\ - org.apache.commons.dbcp,\ - org.apache.commons.pool,\ - com.google.guava,\ - org.apache.jackrabbit.jcr2spi,\ - org.apache.jackrabbit.spi2dav,\ - org.apache.httpcomponents.httpclient,\ - org.apache.httpcomponents.httpcore,\ - org.apache.tika.parsers diff --git a/org.argeo.cms.jcr/pom.xml b/org.argeo.cms.jcr/pom.xml deleted file mode 100644 index 58de05bb9..000000000 --- a/org.argeo.cms.jcr/pom.xml +++ /dev/null @@ -1,24 +0,0 @@ - - - 4.0.0 - - org.argeo.commons - argeo-commons - 2.3-SNAPSHOT - .. - - org.argeo.cms.jcr - CMS JCR - - - org.argeo.commons - org.argeo.cms - 2.3-SNAPSHOT - - - org.argeo.commons - org.argeo.cms.servlet - 2.3-SNAPSHOT - - - \ No newline at end of file diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/fs/CmsFsUtils.java b/org.argeo.cms.jcr/src/org/argeo/cms/fs/CmsFsUtils.java deleted file mode 100644 index 40d38eec2..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/fs/CmsFsUtils.java +++ /dev/null @@ -1,88 +0,0 @@ -package org.argeo.cms.fs; - -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.file.FileSystem; -import java.nio.file.Path; -import java.nio.file.spi.FileSystemProvider; - -import javax.jcr.NoSuchWorkspaceException; -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.query.Query; -import javax.jcr.query.QueryManager; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.jcr.Jcr; - -/** Utilities around documents. */ -public class CmsFsUtils { - // TODO make it more robust and configurable - private static String baseWorkspaceName = CmsConstants.SYS_WORKSPACE; - - public static Node getNode(Repository repository, Path path) { - String workspaceName = path.getNameCount() == 0 ? baseWorkspaceName : path.getName(0).toString(); - String jcrPath = '/' + path.subpath(1, path.getNameCount()).toString(); - try { - Session newSession; - try { - newSession = repository.login(workspaceName); - } catch (NoSuchWorkspaceException e) { - // base workspace - newSession = repository.login(baseWorkspaceName); - jcrPath = path.toString(); - } - return newSession.getNode(jcrPath); - } catch (RepositoryException e) { - throw new IllegalStateException("Cannot get node from path " + path, e); - } - } - - public static NodeIterator getLastUpdatedDocuments(Session session) { - try { - String qStr = "//element(*, nt:file)"; - qStr += " order by @jcr:lastModified descending"; - QueryManager queryManager = session.getWorkspace().getQueryManager(); - @SuppressWarnings("deprecation") - Query xpathQuery = queryManager.createQuery(qStr, Query.XPATH); - xpathQuery.setLimit(8); - NodeIterator nit = xpathQuery.execute().getNodes(); - return nit; - } catch (RepositoryException e) { - throw new IllegalStateException("Unable to retrieve last updated documents", e); - } - } - - public static Path getPath(FileSystemProvider nodeFileSystemProvider, URI uri) { - try { - FileSystem fileSystem = nodeFileSystemProvider.getFileSystem(uri); - if (fileSystem == null) - fileSystem = nodeFileSystemProvider.newFileSystem(uri, null); - String path = uri.getPath(); - return fileSystem.getPath(path); - } catch (IOException e) { - throw new IllegalStateException("Unable to initialise file system for " + uri, e); - } - } - - public static Path getPath(FileSystemProvider nodeFileSystemProvider, Node node) { - String workspaceName = Jcr.getWorkspaceName(node); - String fullPath = baseWorkspaceName.equals(workspaceName) ? Jcr.getPath(node) - : '/' + workspaceName + Jcr.getPath(node); - URI uri; - try { - uri = new URI(CmsConstants.SCHEME_NODE, null, fullPath, null); - } catch (URISyntaxException e) { - throw new IllegalArgumentException("Cannot interpret " + fullPath + " as an URI", e); - } - return getPath(nodeFileSystemProvider, uri); - } - - /** Singleton. */ - private CmsFsUtils() { - } -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/CustomRepositoryConfigurationParser.java b/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/CustomRepositoryConfigurationParser.java deleted file mode 100644 index c2898577e..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/CustomRepositoryConfigurationParser.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.argeo.cms.internal.jcr; - -import java.util.Properties; - -import org.apache.jackrabbit.core.config.BeanConfig; -import org.apache.jackrabbit.core.config.ConfigurationException; -import org.apache.jackrabbit.core.config.RepositoryConfigurationParser; -import org.apache.jackrabbit.core.config.WorkspaceSecurityConfig; -import org.apache.jackrabbit.core.util.db.ConnectionFactory; -import org.w3c.dom.Element; - -/** - * A {@link RepositoryConfigurationParser} providing more flexibility with - * classloaders. - */ -@SuppressWarnings("restriction") -class CustomRepositoryConfigurationParser extends RepositoryConfigurationParser { - private ClassLoader classLoader = null; - - public CustomRepositoryConfigurationParser(Properties variables) { - super(variables); - } - - public CustomRepositoryConfigurationParser(Properties variables, ConnectionFactory connectionFactory) { - super(variables, connectionFactory); - } - - @Override - protected RepositoryConfigurationParser createSubParser(Properties variables) { - Properties props = new Properties(getVariables()); - props.putAll(variables); - CustomRepositoryConfigurationParser subParser = new CustomRepositoryConfigurationParser(props, - connectionFactory); - subParser.setClassLoader(classLoader); - return subParser; - } - - @Override - public WorkspaceSecurityConfig parseWorkspaceSecurityConfig(Element parent) throws ConfigurationException { - WorkspaceSecurityConfig workspaceSecurityConfig = super.parseWorkspaceSecurityConfig(parent); - workspaceSecurityConfig.getAccessControlProviderConfig().setClassLoader(classLoader); - return workspaceSecurityConfig; - } - - @Override - protected BeanConfig parseBeanConfig(Element parent, String name) throws ConfigurationException { - BeanConfig beanConfig = super.parseBeanConfig(parent, name); - if (beanConfig.getClassName().startsWith("org.argeo")) { - beanConfig.setClassLoader(classLoader); - } - return beanConfig; - } - - public void setClassLoader(ClassLoader classLoader) { - this.classLoader = classLoader; - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/JackrabbitType.java b/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/JackrabbitType.java deleted file mode 100644 index 40c83f6df..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/JackrabbitType.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.argeo.cms.internal.jcr; - -/** Pre-defined Jackrabbit repository configurations. */ -enum JackrabbitType { - /** Local file system */ - localfs, - /** Embedded Java H2 database */ - h2, - /** Embedded Java H2 database in PostgreSQL compatibility mode */ - h2_postgresql, - /** PostgreSQL */ - postgresql, - /** PostgreSQL with datastore */ - postgresql_ds, - /** PostgreSQL with cluster */ - postgresql_cluster, - /** PostgreSQL with cluster and datastore */ - postgresql_cluster_ds, - /** Memory */ - memory; -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/JcrInitUtils.java b/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/JcrInitUtils.java deleted file mode 100644 index 0536fb645..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/JcrInitUtils.java +++ /dev/null @@ -1,123 +0,0 @@ -package org.argeo.cms.internal.jcr; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Dictionary; -import java.util.HashMap; -import java.util.Hashtable; -import java.util.Map; - -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.RepositoryFactory; - -import org.argeo.api.cms.CmsDeployment; -import org.argeo.api.cms.CmsLog; -import org.argeo.api.cms.CmsConstants; -import org.argeo.jackrabbit.client.ClientDavexRepositoryFactory; -import org.argeo.jcr.JcrException; -import org.argeo.util.naming.LdapAttrs; -import org.osgi.framework.BundleContext; -import org.osgi.framework.Constants; -import org.osgi.framework.FrameworkUtil; - -/** JCR specific init utilities. */ -public class JcrInitUtils { - private final static CmsLog log = CmsLog.getLog(JcrInitUtils.class); - private final static BundleContext bundleContext = FrameworkUtil.getBundle(JcrInitUtils.class).getBundleContext(); - - public static void addToDeployment(CmsDeployment nodeDeployment) { - // node repository -// Dictionary provided = null; - Dictionary provided = nodeDeployment.getProps(CmsConstants.NODE_REPOS_FACTORY_PID, - CmsConstants.NODE); - Dictionary nodeConfig = JcrInitUtils.getNodeRepositoryConfig(provided); - // node repository is mandatory - nodeDeployment.addFactoryDeployConfig(CmsConstants.NODE_REPOS_FACTORY_PID, nodeConfig); - - // additional repositories -// dataModels: for (DataModels.DataModel dataModel : dataModels.getNonAbstractDataModels()) { -// if (NodeConstants.NODE_REPOSITORY.equals(dataModel.getName())) -// continue dataModels; -// Dictionary config = JcrInitUtils.getRepositoryConfig(dataModel.getName(), -// getProps(NodeConstants.NODE_REPOS_FACTORY_PID, dataModel.getName())); -// if (config.size() != 0) -// putFactoryDeployConfig(NodeConstants.NODE_REPOS_FACTORY_PID, config); -// } - - } - - /** Override the provided config with the framework properties */ - public static Dictionary getNodeRepositoryConfig(Dictionary provided) { - Dictionary props = provided != null ? provided : new Hashtable(); - for (RepoConf repoConf : RepoConf.values()) { - Object value = getFrameworkProp(CmsConstants.NODE_REPO_PROP_PREFIX + repoConf.name()); - if (value != null) { - props.put(repoConf.name(), value); - if (log.isDebugEnabled()) - log.debug("Set node repo configuration " + repoConf.name() + " to " + value); - } - } - props.put(CmsConstants.CN, CmsConstants.NODE_REPOSITORY); - return props; - } - - public static Dictionary getRepositoryConfig(String dataModelName, - Dictionary provided) { - if (dataModelName.equals(CmsConstants.NODE_REPOSITORY) || dataModelName.equals(CmsConstants.EGO_REPOSITORY)) - throw new IllegalArgumentException("Data model '" + dataModelName + "' is reserved."); - Dictionary props = provided != null ? provided : new Hashtable(); - for (RepoConf repoConf : RepoConf.values()) { - Object value = getFrameworkProp( - CmsConstants.NODE_REPOS_PROP_PREFIX + dataModelName + '.' + repoConf.name()); - if (value != null) { - props.put(repoConf.name(), value); - if (log.isDebugEnabled()) - log.debug("Set " + dataModelName + " repo configuration " + repoConf.name() + " to " + value); - } - } - if (props.size() != 0) - props.put(CmsConstants.CN, dataModelName); - return props; - } - - private static void registerRemoteInit(String uri) { - try { - Repository repository = createRemoteRepository(new URI(uri)); - Hashtable properties = new Hashtable<>(); - properties.put(CmsConstants.CN, CmsConstants.NODE_INIT); - properties.put(LdapAttrs.labeledURI.name(), uri); - properties.put(Constants.SERVICE_RANKING, -1000); - bundleContext.registerService(Repository.class, repository, properties); - } catch (RepositoryException e) { - throw new JcrException(e); - } catch (URISyntaxException e) { - throw new IllegalArgumentException(e); - } - } - - private static Repository createRemoteRepository(URI uri) throws RepositoryException { - RepositoryFactory repositoryFactory = new ClientDavexRepositoryFactory(); - Map params = new HashMap(); - params.put(ClientDavexRepositoryFactory.JACKRABBIT_DAVEX_URI, uri.toString()); - // TODO make it configurable - params.put(ClientDavexRepositoryFactory.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, CmsConstants.SYS_WORKSPACE); - return repositoryFactory.getRepository(params); - } - - private static String getFrameworkProp(String key, String def) { - String value; - if (bundleContext != null) - value = bundleContext.getProperty(key); - else - value = System.getProperty(key); - if (value == null) - return def; - return value; - } - - private static String getFrameworkProp(String key) { - return getFrameworkProp(key, null); - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/LocalFsDataStore.java b/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/LocalFsDataStore.java deleted file mode 100644 index dba005cb4..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/LocalFsDataStore.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.argeo.cms.internal.jcr; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Files; -import java.nio.file.Paths; - -import org.apache.jackrabbit.core.data.DataIdentifier; -import org.apache.jackrabbit.core.data.DataRecord; -import org.apache.jackrabbit.core.data.DataStoreException; -import org.apache.jackrabbit.core.data.FileDataStore; - -/** - * experimental Duplicate added entries in another directory (typically a - * remote mount). - */ -@SuppressWarnings("restriction") -public class LocalFsDataStore extends FileDataStore { - String redundantPath; - FileDataStore redundantStore; - - @Override - public void init(String homeDir) { - // init primary first - super.init(homeDir); - - if (redundantPath != null) { - // redundant directory must be created first - // TODO implement some polling? - if (Files.exists(Paths.get(redundantPath))) { - redundantStore = new FileDataStore(); - redundantStore.setPath(redundantPath); - redundantStore.init(homeDir); - } - } - } - - @Override - public DataRecord addRecord(InputStream input) throws DataStoreException { - DataRecord dataRecord = super.addRecord(input); - syncRedundantRecord(dataRecord); - return dataRecord; - } - - @Override - public DataRecord getRecord(DataIdentifier identifier) throws DataStoreException { - DataRecord dataRecord = super.getRecord(identifier); - syncRedundantRecord(dataRecord); - return dataRecord; - } - - protected void syncRedundantRecord(DataRecord dataRecord) throws DataStoreException { - if (redundantStore == null) - return; - if (redundantStore.getRecordIfStored(dataRecord.getIdentifier()) == null) { - try (InputStream redundant = dataRecord.getStream()) { - redundantStore.addRecord(redundant); - } catch (IOException e) { - throw new DataStoreException("Cannot add redundant record.", e); - } - } - } - - public void setRedundantPath(String redundantPath) { - this.redundantPath = redundantPath; - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/RepoConf.java b/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/RepoConf.java deleted file mode 100644 index a45656cf5..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/RepoConf.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.argeo.cms.internal.jcr; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.osgi.metatype.EnumAD; -import org.argeo.osgi.metatype.EnumOCD; - -/** JCR repository configuration */ -public enum RepoConf implements EnumAD { - /** Repository type */ - type("h2"), - /** Default workspace */ - defaultWorkspace(CmsConstants.SYS_WORKSPACE), - /** Database URL */ - dburl(null), - /** Database user */ - dbuser(null), - /** Database password */ - dbpassword(null), - - /** The identifier (can be an URL locating the repo) */ - labeledUri(null), - // - // JACKRABBIT SPECIFIC - // - /** Maximum database pool size */ - maxPoolSize(10), - /** Maximum cache size in MB */ - maxCacheMB(null), - /** Bundle cache size in MB */ - bundleCacheMB(8), - /** Extractor pool size */ - extractorPoolSize(0), - /** Search cache size */ - searchCacheSize(1000), - /** Max volatile index size */ - maxVolatileIndexSize(1048576), - /** Cluster id (if appropriate configuration) */ - clusterId("default"), - /** Indexes base path */ - indexesBase(null); - - /** The default value. */ - private Object def; - private String oid; - - RepoConf(String oid, Object def) { - this.oid = oid; - this.def = def; - } - - RepoConf(Object def) { - this.def = def; - } - - public Object getDefault() { - return def; - } - - @Override - public String getID() { - if (oid != null) - return oid; - return EnumAD.super.getID(); - } - - public static class OCD extends EnumOCD { - public OCD(String locale) { - super(RepoConf.class, locale); - } - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/RepositoryBuilder.java b/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/RepositoryBuilder.java deleted file mode 100644 index 3db97167c..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/RepositoryBuilder.java +++ /dev/null @@ -1,225 +0,0 @@ -package org.argeo.cms.internal.jcr; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Dictionary; -import java.util.Enumeration; -import java.util.Properties; -import java.util.UUID; - -import javax.jcr.RepositoryException; - -import org.apache.jackrabbit.core.RepositoryContext; -import org.apache.jackrabbit.core.RepositoryImpl; -import org.apache.jackrabbit.core.cache.CacheManager; -import org.apache.jackrabbit.core.config.RepositoryConfig; -import org.apache.jackrabbit.core.config.RepositoryConfigurationParser; -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.jcr.internal.CmsPaths; -import org.xml.sax.InputSource; - -/** Can interpret properties in order to create an actual JCR repository. */ -public class RepositoryBuilder { - private final static CmsLog log = CmsLog.getLog(RepositoryBuilder.class); - - public RepositoryContext createRepositoryContext(Dictionary properties) - throws RepositoryException, IOException { - RepositoryConfig repositoryConfig = createRepositoryConfig(properties); - RepositoryContext repositoryContext = createJackrabbitRepository(repositoryConfig); - RepositoryImpl repository = repositoryContext.getRepository(); - - // cache - Object maxCacheMbStr = prop(properties, RepoConf.maxCacheMB); - if (maxCacheMbStr != null) { - Integer maxCacheMB = Integer.parseInt(maxCacheMbStr.toString()); - CacheManager cacheManager = repository.getCacheManager(); - cacheManager.setMaxMemory(maxCacheMB * 1024l * 1024l); - cacheManager.setMaxMemoryPerCache((maxCacheMB / 4) * 1024l * 1024l); - } - - return repositoryContext; - } - - RepositoryConfig createRepositoryConfig(Dictionary properties) throws RepositoryException, IOException { - JackrabbitType type = JackrabbitType.valueOf(prop(properties, RepoConf.type).toString()); - ClassLoader cl = getClass().getClassLoader(); - final String base = "/org/argeo/cms/internal/jcr"; - try (InputStream in = cl.getResourceAsStream(base + "/repository-" + type.name() + ".xml")) { - if (in == null) - throw new IllegalArgumentException("Repository configuration not found"); - InputSource config = new InputSource(in); - Properties jackrabbitVars = getConfigurationProperties(type, properties); - // RepositoryConfig repositoryConfig = RepositoryConfig.create(config, - // jackrabbitVars); - - // custom configuration parser - CustomRepositoryConfigurationParser parser = new CustomRepositoryConfigurationParser(jackrabbitVars); - parser.setClassLoader(cl); - RepositoryConfig repositoryConfig = parser.parseRepositoryConfig(config); - repositoryConfig.init(); - - // set the proper classloaders - repositoryConfig.getSecurityConfig().getSecurityManagerConfig().setClassLoader(cl); - repositoryConfig.getSecurityConfig().getAccessManagerConfig().setClassLoader(cl); -// for (WorkspaceConfig workspaceConfig : repositoryConfig.getWorkspaceConfigs()) { -// workspaceConfig.getSecurityConfig().getAccessControlProviderConfig().setClassLoader(cl); -// } - return repositoryConfig; - } - } - - private Properties getConfigurationProperties(JackrabbitType type, Dictionary properties) { - Properties props = new Properties(); - for (Enumeration keys = properties.keys(); keys.hasMoreElements();) { - String key = keys.nextElement(); - props.put(key, properties.get(key)); - } - - // cluster id - // cf. https://wiki.apache.org/jackrabbit/Clustering - // TODO deal with multiple repos - String clusterId = System.getProperty("org.apache.jackrabbit.core.cluster.node_id"); - String clusterIdProp = props.getProperty(RepoConf.clusterId.name()); - if (clusterId != null) { - if (clusterIdProp != null) - throw new IllegalArgumentException("Cluster id defined as System properties and in deploy config"); - props.put(RepoConf.clusterId.name(), clusterId); - } else { - clusterId = clusterIdProp; - } - - // home - String homeUri = props.getProperty(RepoConf.labeledUri.name()); - Path homePath; - if (homeUri == null) { - String cn = props.getProperty(CmsConstants.CN); - assert cn != null; - if (clusterId != null) { - homePath = CmsPaths.getRepoDirPath(cn + '/' + clusterId); - } else { - homePath = CmsPaths.getRepoDirPath(cn); - } - } else { - try { - URI uri = new URI(homeUri); - String host = uri.getHost(); - if (host == null || host.trim().equals("")) { - homePath = Paths.get(uri).toAbsolutePath(); - } else { - // TODO remote at this stage? - throw new IllegalArgumentException("Cannot manage repository path for host " + host); - } - } catch (URISyntaxException e) { - throw new IllegalArgumentException("Invalid repository home URI", e); - } - } - // TODO use Jackrabbit API (?) - Path rootUuidPath = homePath.resolve("repository/meta/rootUUID"); - try { - if (!Files.exists(rootUuidPath)) { - Files.createDirectories(rootUuidPath.getParent()); - Files.write(rootUuidPath, UUID.randomUUID().toString().getBytes()); - } - // File homeDir = homePath.toFile(); - // homeDir.mkdirs(); - } catch (IOException e) { - throw new RuntimeException("Cannot set up repository home " + homePath, e); - } - // home cannot be overridden - props.put(RepositoryConfigurationParser.REPOSITORY_HOME_VARIABLE, homePath.toString()); - - setProp(props, RepoConf.indexesBase, CmsPaths.getRepoIndexesBase().toString()); - // common - setProp(props, RepoConf.defaultWorkspace); - setProp(props, RepoConf.maxPoolSize); - // Jackrabbit defaults - setProp(props, RepoConf.bundleCacheMB); - // See http://wiki.apache.org/jackrabbit/Search - setProp(props, RepoConf.extractorPoolSize); - setProp(props, RepoConf.searchCacheSize); - setProp(props, RepoConf.maxVolatileIndexSize); - - // specific - String dburl; - switch (type) { - case h2: - dburl = "jdbc:h2:" + homePath.toAbsolutePath() + "/h2/repository"; - setProp(props, RepoConf.dburl, dburl); - setProp(props, RepoConf.dbuser, "sa"); - setProp(props, RepoConf.dbpassword, ""); - break; - case h2_postgresql: - dburl = "jdbc:h2:" + homePath.toAbsolutePath() + "/h2/repository;MODE=PostgreSQL;DATABASE_TO_LOWER=TRUE"; - setProp(props, RepoConf.dburl, dburl); - setProp(props, RepoConf.dbuser, "sa"); - setProp(props, RepoConf.dbpassword, ""); - break; - case postgresql: - case postgresql_ds: - case postgresql_cluster: - case postgresql_cluster_ds: - dburl = "jdbc:postgresql://localhost/demo"; - setProp(props, RepoConf.dburl, dburl); - setProp(props, RepoConf.dbuser, "argeo"); - setProp(props, RepoConf.dbpassword, "argeo"); - break; - case memory: - break; - case localfs: - break; - default: - throw new IllegalArgumentException("Unsupported node type " + type); - } - return props; - } - - private void setProp(Properties props, RepoConf key, String def) { - Object value = props.get(key.name()); - if (value == null) - value = def; - if (value == null) - value = key.getDefault(); - if (value != null) - props.put(key.name(), value.toString()); - } - - private void setProp(Properties props, RepoConf key) { - setProp(props, key, null); - } - - private String prop(Dictionary properties, RepoConf key) { - Object value = properties.get(key.name()); - if (value == null) - return key.getDefault() != null ? key.getDefault().toString() : null; - else - return value.toString(); - } - - private RepositoryContext createJackrabbitRepository(RepositoryConfig repositoryConfig) throws RepositoryException { - ClassLoader currentContextCl = Thread.currentThread().getContextClassLoader(); - Thread.currentThread().setContextClassLoader(RepositoryBuilder.class.getClassLoader()); - try { - long begin = System.currentTimeMillis(); - // - // Actual repository creation - // - RepositoryContext repositoryContext = RepositoryContext.create(repositoryConfig); - - double duration = ((double) (System.currentTimeMillis() - begin)) / 1000; - if (log.isDebugEnabled()) - log.debug( - "Created Jackrabbit repository in " + duration + " s, home: " + repositoryConfig.getHomeDir()); - - return repositoryContext; - } finally { - Thread.currentThread().setContextClassLoader(currentContextCl); - } - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-h2.xml b/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-h2.xml deleted file mode 100644 index ace0fa5ee..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-h2.xml +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-h2_postgresql.xml b/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-h2_postgresql.xml deleted file mode 100644 index 430367656..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-h2_postgresql.xml +++ /dev/null @@ -1,86 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-localfs.xml b/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-localfs.xml deleted file mode 100644 index b88907919..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-localfs.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-memory.xml b/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-memory.xml deleted file mode 100644 index 3630a149d..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-memory.xml +++ /dev/null @@ -1,59 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql.xml b/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql.xml deleted file mode 100644 index de2f245ad..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster.xml b/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster.xml deleted file mode 100644 index 488ad6b72..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster.xml +++ /dev/null @@ -1,87 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster_ds.xml b/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster_ds.xml deleted file mode 100644 index b430674c9..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_cluster_ds.xml +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_ds.xml b/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_ds.xml deleted file mode 100644 index 5229d1660..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/internal/jcr/repository-postgresql_ds.xml +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/CmsJcrUtils.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/CmsJcrUtils.java deleted file mode 100644 index b5d9adfca..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/CmsJcrUtils.java +++ /dev/null @@ -1,276 +0,0 @@ -package org.argeo.cms.jcr; - -import java.security.PrivilegedAction; -import java.util.HashMap; -import java.util.Map; - -import javax.jcr.NoSuchWorkspaceException; -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.RepositoryFactory; -import javax.jcr.Session; -import javax.naming.InvalidNameException; -import javax.naming.ldap.LdapName; -import javax.security.auth.AuthPermission; -import javax.security.auth.Subject; -import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; - -import org.argeo.api.cms.CmsAuth; -import org.argeo.api.cms.CmsConstants; - -/** Utilities related to Argeo model in JCR */ -public class CmsJcrUtils { - /** - * Wraps the call to the repository factory based on parameter - * {@link CmsConstants#CN} in order to simplify it and protect against future - * API changes. - */ - public static Repository getRepositoryByAlias(RepositoryFactory repositoryFactory, String alias) { - try { - Map parameters = new HashMap(); - parameters.put(CmsConstants.CN, alias); - return repositoryFactory.getRepository(parameters); - } catch (RepositoryException e) { - throw new RuntimeException("Unexpected exception when trying to retrieve repository with alias " + alias, - e); - } - } - - /** - * Wraps the call to the repository factory based on parameter - * {@link CmsConstants#LABELED_URI} in order to simplify it and protect against - * future API changes. - */ - public static Repository getRepositoryByUri(RepositoryFactory repositoryFactory, String uri) { - return getRepositoryByUri(repositoryFactory, uri, null); - } - - /** - * Wraps the call to the repository factory based on parameter - * {@link CmsConstants#LABELED_URI} in order to simplify it and protect against - * future API changes. - */ - public static Repository getRepositoryByUri(RepositoryFactory repositoryFactory, String uri, String alias) { - try { - Map parameters = new HashMap(); - parameters.put(CmsConstants.LABELED_URI, uri); - if (alias != null) - parameters.put(CmsConstants.CN, alias); - return repositoryFactory.getRepository(parameters); - } catch (RepositoryException e) { - throw new RuntimeException("Unexpected exception when trying to retrieve repository with uri " + uri, e); - } - } - - /** - * Returns the home node of the user or null if none was found. - * - * @param session the session to use in order to perform the search, this can - * be a session with a different user ID than the one searched, - * typically when a system or admin session is used. - * @param username the username of the user - */ - public static Node getUserHome(Session session, String username) { -// try { -// QueryObjectModelFactory qomf = session.getWorkspace().getQueryManager().getQOMFactory(); -// Selector sel = qomf.selector(NodeTypes.NODE_USER_HOME, "sel"); -// DynamicOperand dop = qomf.propertyValue(sel.getSelectorName(), NodeNames.LDAP_UID); -// StaticOperand sop = qomf.literal(session.getValueFactory().createValue(username)); -// Constraint constraint = qomf.comparison(dop, QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, sop); -// Query query = qomf.createQuery(sel, constraint, null, null); -// return querySingleNode(query); -// } catch (RepositoryException e) { -// throw new RuntimeException("Cannot find home for user " + username, e); -// } - - try { - checkUserWorkspace(session, username); - String homePath = getHomePath(username); - if (session.itemExists(homePath)) - return session.getNode(homePath); - // legacy - homePath = "/home/" + username; - if (session.itemExists(homePath)) - return session.getNode(homePath); - return null; - } catch (RepositoryException e) { - throw new RuntimeException("Cannot find home for user " + username, e); - } - } - - private static String getHomePath(String username) { - LdapName dn; - try { - dn = new LdapName(username); - } catch (InvalidNameException e) { - throw new IllegalArgumentException("Invalid name " + username, e); - } - String userId = dn.getRdn(dn.size() - 1).getValue().toString(); - return '/' + userId; - } - - private static void checkUserWorkspace(Session session, String username) { - String workspaceName = session.getWorkspace().getName(); - if (!CmsConstants.HOME_WORKSPACE.equals(workspaceName)) - throw new IllegalArgumentException(workspaceName + " is not the home workspace for user " + username); - } - - /** - * Returns the home node of the user or null if none was found. - * - * @param session the session to use in order to perform the search, this can - * be a session with a different user ID than the one searched, - * typically when a system or admin session is used. - * @param groupname the name of the group - */ - public static Node getGroupHome(Session session, String groupname) { -// try { -// QueryObjectModelFactory qomf = session.getWorkspace().getQueryManager().getQOMFactory(); -// Selector sel = qomf.selector(NodeTypes.NODE_GROUP_HOME, "sel"); -// DynamicOperand dop = qomf.propertyValue(sel.getSelectorName(), NodeNames.LDAP_CN); -// StaticOperand sop = qomf.literal(session.getValueFactory().createValue(cn)); -// Constraint constraint = qomf.comparison(dop, QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, sop); -// Query query = qomf.createQuery(sel, constraint, null, null); -// return querySingleNode(query); -// } catch (RepositoryException e) { -// throw new RuntimeException("Cannot find home for group " + cn, e); -// } - - try { - checkGroupWorkspace(session, groupname); - String homePath = getGroupPath(groupname); - if (session.itemExists(homePath)) - return session.getNode(homePath); - // legacy - homePath = "/groups/" + groupname; - if (session.itemExists(homePath)) - return session.getNode(homePath); - return null; - } catch (RepositoryException e) { - throw new RuntimeException("Cannot find home for group " + groupname, e); - } - - } - - private static String getGroupPath(String groupname) { - String cn; - try { - LdapName dn = new LdapName(groupname); - cn = dn.getRdn(dn.size() - 1).getValue().toString(); - } catch (InvalidNameException e) { - cn = groupname; - } - return '/' + cn; - } - - private static void checkGroupWorkspace(Session session, String groupname) { - String workspaceName = session.getWorkspace().getName(); - if (!CmsConstants.SRV_WORKSPACE.equals(workspaceName)) - throw new IllegalArgumentException(workspaceName + " is not the group workspace for group " + groupname); - } - - /** - * Queries one single node. - * - * @return one single node or null if none was found - * @throws ArgeoJcrException if more than one node was found - */ -// private static Node querySingleNode(Query query) { -// NodeIterator nodeIterator; -// try { -// QueryResult queryResult = query.execute(); -// nodeIterator = queryResult.getNodes(); -// } catch (RepositoryException e) { -// throw new RuntimeException("Cannot execute query " + query, e); -// } -// Node node; -// if (nodeIterator.hasNext()) -// node = nodeIterator.nextNode(); -// else -// return null; -// -// if (nodeIterator.hasNext()) -// throw new RuntimeException("Query returned more than one node."); -// return node; -// } - - /** Returns the home node of the session user or null if none was found. */ - public static Node getUserHome(Session session) { - String userID = session.getUserID(); - return getUserHome(session, userID); - } - - /** Whether this node is the home of the user of the underlying session. */ - public static boolean isUserHome(Node node) { - try { - String userID = node.getSession().getUserID(); - return node.hasProperty(Property.JCR_ID) && node.getProperty(Property.JCR_ID).getString().equals(userID); - } catch (RepositoryException e) { - throw new IllegalStateException(e); - } - } - - /** - * Translate the path to this node into a path containing the name of the - * repository and the name of the workspace. - */ - public static String getDataPath(String cn, Node node) { - assert node != null; - StringBuilder buf = new StringBuilder(CmsConstants.PATH_DATA); - try { - return buf.append('/').append(cn).append('/').append(node.getSession().getWorkspace().getName()) - .append(node.getPath()).toString(); - } catch (RepositoryException e) { - throw new IllegalStateException("Cannot get data path for " + node + " in repository " + cn, e); - } - } - - /** - * Translate the path to this node into a path containing the name of the - * repository and the name of the workspace. - */ - public static String getDataPath(Node node) { - return getDataPath(CmsConstants.NODE, node); - } - - /** - * Open a JCR session with full read/write rights on the data, as - * {@link CmsConstants#ROLE_USER_ADMIN}, using the - * {@link CmsAuth#LOGIN_CONTEXT_DATA_ADMIN} login context. For security - * hardened deployement, use {@link AuthPermission} on this login context. - */ - public static Session openDataAdminSession(Repository repository, String workspaceName) { - ClassLoader currentCl = Thread.currentThread().getContextClassLoader(); - LoginContext loginContext; - try { - loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_DATA_ADMIN); - loginContext.login(); - } catch (LoginException e1) { - throw new RuntimeException("Could not login as data admin", e1); - } finally { - Thread.currentThread().setContextClassLoader(currentCl); - } - return Subject.doAs(loginContext.getSubject(), new PrivilegedAction() { - - @Override - public Session run() { - try { - return repository.login(workspaceName); - } catch (NoSuchWorkspaceException e) { - throw new IllegalArgumentException("No workspace " + workspaceName + " available", e); - } catch (RepositoryException e) { - throw new RuntimeException("Cannot open data admin session", e); - } - } - - }); - } - - /** Singleton. */ - private CmsJcrUtils() { - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java deleted file mode 100644 index 04c5d2d8c..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContent.java +++ /dev/null @@ -1,201 +0,0 @@ -package org.argeo.cms.jcr.acr; - -import java.util.Calendar; -import java.util.Iterator; -import java.util.Optional; - -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.Property; -import javax.jcr.PropertyIterator; -import javax.jcr.PropertyType; -import javax.jcr.RepositoryException; -import javax.jcr.Value; -import javax.jcr.nodetype.NodeType; -import javax.xml.namespace.QName; - -import org.argeo.api.acr.Content; -import org.argeo.api.acr.spi.AbstractContent; -import org.argeo.api.acr.spi.ProvidedSession; -import org.argeo.jcr.Jcr; -import org.argeo.jcr.JcrException; - -public class JcrContent extends AbstractContent { - private Node jcrNode; - - private JcrContentProvider provider; - private ProvidedSession session; - - protected JcrContent(ProvidedSession session, JcrContentProvider provider, Node node) { - this.session = session; - this.provider = provider; - this.jcrNode = node; - } - - @Override - public QName getName() { - return session.parsePrefixedName(Jcr.getName(jcrNode)); - } - - @Override - public Optional get(QName key, Class clss) { - if (isDefaultAttrTypeRequested(clss)) { - return Optional.of((A) get(jcrNode, key.toString())); - } - return Optional.of((A) Jcr.get(jcrNode, key.toString())); - } - - @Override - public Iterator iterator() { - try { - return new JcrContentIterator(jcrNode.getNodes()); - } catch (RepositoryException e) { - throw new JcrException("Cannot list children of " + jcrNode, e); - } - } - - @Override - protected Iterable keys() { - return new Iterable() { - - @Override - public Iterator iterator() { - try { - PropertyIterator propertyIterator = jcrNode.getProperties(); - return new JcrKeyIterator(provider, propertyIterator); - } catch (RepositoryException e) { - throw new JcrException("Cannot retrive properties from " + jcrNode, e); - } - } - }; - } - - public Node getJcrNode() { - return jcrNode; - } - - /** Cast to a standard Java object. */ - static Object get(Node node, String property) { - try { - Value value = node.getProperty(property).getValue(); - switch (value.getType()) { - case PropertyType.STRING: - return value.getString(); - case PropertyType.DOUBLE: - return (Double) value.getDouble(); - case PropertyType.LONG: - return (Long) value.getLong(); - case PropertyType.BOOLEAN: - return (Boolean) value.getBoolean(); - case PropertyType.DATE: - Calendar calendar = value.getDate(); - return calendar.toInstant(); - case PropertyType.BINARY: - throw new IllegalArgumentException("Binary is not supported as an attribute"); - default: - return value.getString(); - } - } catch (RepositoryException e) { - throw new JcrException("Cannot cast value from " + property + " of node " + node, e); - } - } - - class JcrContentIterator implements Iterator { - private final NodeIterator nodeIterator; - // we keep track in order to be able to delete it - private JcrContent current = null; - - protected JcrContentIterator(NodeIterator nodeIterator) { - this.nodeIterator = nodeIterator; - } - - @Override - public boolean hasNext() { - return nodeIterator.hasNext(); - } - - @Override - public Content next() { - current = new JcrContent(session, provider, nodeIterator.nextNode()); - return current; - } - - @Override - public void remove() { - if (current != null) { - Jcr.remove(current.getJcrNode()); - } - } - - } - - @Override - public Content getParent() { - return new JcrContent(session, provider, Jcr.getParent(getJcrNode())); - } - - @Override - public Content add(QName name, QName... classes) { - if (classes.length > 0) { - QName primaryType = classes[0]; - Node child = Jcr.addNode(getJcrNode(), name.toString(), primaryType.toString()); - for (int i = 1; i < classes.length; i++) { - try { - child.addMixin(classes[i].toString()); - } catch (RepositoryException e) { - throw new JcrException("Cannot add child to " + getJcrNode(), e); - } - } - - } else { - Jcr.addNode(getJcrNode(), name.toString(), NodeType.NT_UNSTRUCTURED); - } - return null; - } - - @Override - public void remove() { - Jcr.remove(getJcrNode()); - } - - @Override - protected void removeAttr(QName key) { - Property property = Jcr.getProperty(getJcrNode(), key.toString()); - if (property != null) { - try { - property.remove(); - } catch (RepositoryException e) { - throw new JcrException("Cannot remove property " + key + " from " + getJcrNode(), e); - } - } - - } - - class JcrKeyIterator implements Iterator { - private final JcrContentProvider contentSession; - private final PropertyIterator propertyIterator; - - protected JcrKeyIterator(JcrContentProvider contentSession, PropertyIterator propertyIterator) { - this.contentSession = contentSession; - this.propertyIterator = propertyIterator; - } - - @Override - public boolean hasNext() { - return propertyIterator.hasNext(); - } - - @Override - public QName next() { - Property property = null; - try { - property = propertyIterator.nextProperty(); - // TODO map standard property names - return session.parsePrefixedName(property.getName()); - } catch (RepositoryException e) { - throw new JcrException("Cannot retrieve property " + property, null); - } - } - - } -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentProvider.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentProvider.java deleted file mode 100644 index ef8e375d0..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/acr/JcrContentProvider.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.argeo.cms.jcr.acr; - -import java.util.Arrays; -import java.util.Iterator; - -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.xml.namespace.NamespaceContext; - -import org.argeo.api.acr.Content; -import org.argeo.api.acr.spi.ContentProvider; -import org.argeo.api.acr.spi.ProvidedSession; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.jcr.JcrException; -import org.argeo.jcr.JcrUtils; - -public class JcrContentProvider implements ContentProvider, NamespaceContext { - private Repository jcrRepository; - private Session adminSession; - - public void init() { - adminSession = CmsJcrUtils.openDataAdminSession(jcrRepository, null); - } - - public void destroy() { - JcrUtils.logoutQuietly(adminSession); - } - - public void setJcrRepository(Repository jcrRepository) { - this.jcrRepository = jcrRepository; - } - - @Override - public Content get(ProvidedSession session, String mountPath, String relativePath) { - // TODO Auto-generated method stub - return null; - } - - /* - * NAMESPACE CONTEXT - */ - @Override - public String getNamespaceURI(String prefix) { - try { - return adminSession.getNamespaceURI(prefix); - } catch (RepositoryException e) { - throw new JcrException(e); - } - } - - @Override - public String getPrefix(String namespaceURI) { - try { - return adminSession.getNamespacePrefix(namespaceURI); - } catch (RepositoryException e) { - throw new JcrException(e); - } - } - - @Override - public Iterator getPrefixes(String namespaceURI) { - try { - return Arrays.asList(adminSession.getNamespacePrefix(namespaceURI)).iterator(); - } catch (RepositoryException e) { - throw new JcrException(e); - } - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/argeo.cnd b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/argeo.cnd deleted file mode 100644 index c9e6ee7e2..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/argeo.cnd +++ /dev/null @@ -1,34 +0,0 @@ - - -// GENERIC TYPES -[argeo:remoteRepository] > nt:unstructured -- argeo:uri (STRING) -- argeo:userID (STRING) -+ argeo:password (argeo:encrypted) - -// TABULAR CONTENT -[argeo:table] > nt:file -+ * (argeo:column) * - -[argeo:column] > mix:title -- jcr:requiredType (STRING) = 'STRING' - -[argeo:csv] > nt:resource - -// CRYPTO -[argeo:encrypted] -mixin -// initialization vector used by some algorithms -- argeo:iv (BINARY) - -[argeo:pbeKeySpec] -mixin -- argeo:secretKeyFactory (STRING) -- argeo:salt (BINARY) -- argeo:iterationCount (LONG) -- argeo:keyLength (LONG) -- argeo:secretKeyEncryption (STRING) - -[argeo:pbeSpec] > argeo:pbeKeySpec -mixin -- argeo:cipher (STRING) diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/dn.cnd b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/dn.cnd deleted file mode 100644 index 80849be95..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/dn.cnd +++ /dev/null @@ -1,10 +0,0 @@ -// DN (see https://tools.ietf.org/html/rfc4514) - - - - - - - - - diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrDeployment.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrDeployment.java deleted file mode 100644 index 340d13782..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrDeployment.java +++ /dev/null @@ -1,481 +0,0 @@ -package org.argeo.cms.jcr.internal; - -import static org.argeo.cms.osgi.DataModelNamespace.CMS_DATA_MODEL_NAMESPACE; -import static org.osgi.service.http.whiteboard.HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX; - -import java.io.File; -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.Reader; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashSet; -import java.util.Hashtable; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.security.auth.callback.CallbackHandler; -import javax.servlet.Servlet; - -import org.apache.jackrabbit.commons.cnd.CndImporter; -import org.apache.jackrabbit.core.RepositoryContext; -import org.apache.jackrabbit.core.RepositoryImpl; -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsDeployment; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.ArgeoNames; -import org.argeo.cms.internal.jcr.JcrInitUtils; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.cms.jcr.internal.servlet.CmsRemotingServlet; -import org.argeo.cms.jcr.internal.servlet.CmsWebDavServlet; -import org.argeo.cms.jcr.internal.servlet.JcrHttpUtils; -import org.argeo.cms.osgi.DataModelNamespace; -import org.argeo.cms.security.CryptoKeyring; -import org.argeo.cms.security.Keyring; -import org.argeo.jcr.Jcr; -import org.argeo.jcr.JcrException; -import org.argeo.jcr.JcrUtils; -import org.argeo.util.LangUtils; -import org.argeo.util.naming.LdapAttrs; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.Constants; -import org.osgi.framework.FrameworkUtil; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.framework.ServiceReference; -import org.osgi.framework.wiring.BundleCapability; -import org.osgi.framework.wiring.BundleWire; -import org.osgi.framework.wiring.BundleWiring; -import org.osgi.service.cm.ManagedService; -import org.osgi.service.http.whiteboard.HttpWhiteboardConstants; -import org.osgi.util.tracker.ServiceTracker; - -/** Implementation of a CMS deployment. */ -public class CmsJcrDeployment { - private final CmsLog log = CmsLog.getLog(getClass()); - private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); - - private DataModels dataModels; - private String webDavConfig = JcrHttpUtils.WEBDAV_CONFIG; - - private boolean argeoDataModelExtensionsAvailable = false; - - // Readiness - private boolean nodeAvailable = false; - - CmsDeployment cmsDeployment; - - public CmsJcrDeployment() { -// initTrackers(); - } - - public void start() { - dataModels = new DataModels(bc); - - ServiceTracker repoContextSt = new RepositoryContextStc(); - repoContextSt.open(); - //KernelUtils.asyncOpen(repoContextSt); - -// nodeDeployment = CmsJcrActivator.getService(NodeDeployment.class); - - JcrInitUtils.addToDeployment(cmsDeployment); - - } - - public void stop() { -// if (nodeHttp != null) -// nodeHttp.destroy(); - - try { - for (ServiceReference sr : bc - .getServiceReferences(JackrabbitLocalRepository.class, null)) { - bc.getService(sr).destroy(); - } - } catch (InvalidSyntaxException e1) { - log.error("Cannot clean repositories", e1); - } - - } - - public void setCmsDeployment(CmsDeployment cmsDeployment) { - this.cmsDeployment = cmsDeployment; - } - - /** - * Checks whether the deployment is available according to expectations, and - * mark it as available. - */ -// private synchronized void checkReadiness() { -// if (isAvailable()) -// return; -// if (nodeAvailable && userAdminAvailable && (httpExpected ? httpAvailable : true)) { -// String data = KernelUtils.getFrameworkProp(KernelUtils.OSGI_INSTANCE_AREA); -// String state = KernelUtils.getFrameworkProp(KernelUtils.OSGI_CONFIGURATION_AREA); -// availableSince = System.currentTimeMillis(); -// long jvmUptime = ManagementFactory.getRuntimeMXBean().getUptime(); -// String jvmUptimeStr = " in " + (jvmUptime / 1000) + "." + (jvmUptime % 1000) + "s"; -// log.info("## ARGEO NODE AVAILABLE" + (log.isDebugEnabled() ? jvmUptimeStr : "") + " ##"); -// if (log.isDebugEnabled()) { -// log.debug("## state: " + state); -// if (data != null) -// log.debug("## data: " + data); -// } -// long begin = bc.getService(bc.getServiceReference(NodeState.class)).getAvailableSince(); -// long initDuration = System.currentTimeMillis() - begin; -// if (log.isTraceEnabled()) -// log.trace("Kernel initialization took " + initDuration + "ms"); -// tributeToFreeSoftware(initDuration); -// } -// } - - private void prepareNodeRepository(Repository deployedNodeRepository, List publishAsLocalRepo) { -// if (availableSince != null) { -// throw new IllegalStateException("Deployment is already available"); -// } - - // home - prepareDataModel(CmsConstants.NODE_REPOSITORY, deployedNodeRepository, publishAsLocalRepo); - - // init from backup -// if (deployConfig.isFirstInit()) { -// Path restorePath = Paths.get(System.getProperty("user.dir"), "restore"); -// if (Files.exists(restorePath)) { -// if (log.isDebugEnabled()) -// log.debug("Found backup " + restorePath + ", restoring it..."); -// LogicalRestore logicalRestore = new LogicalRestore(bc, deployedNodeRepository, restorePath); -// KernelUtils.doAsDataAdmin(logicalRestore); -// log.info("Restored backup from " + restorePath); -// } -// } - - // init from repository - Collection> initRepositorySr; - try { - initRepositorySr = bc.getServiceReferences(Repository.class, - "(" + CmsConstants.CN + "=" + CmsConstants.NODE_INIT + ")"); - } catch (InvalidSyntaxException e1) { - throw new IllegalArgumentException(e1); - } - Iterator> it = initRepositorySr.iterator(); - while (it.hasNext()) { - ServiceReference sr = it.next(); - Object labeledUri = sr.getProperties().get(LdapAttrs.labeledURI.name()); - Repository initRepository = bc.getService(sr); - if (log.isDebugEnabled()) - log.debug("Found init repository " + labeledUri + ", copying it..."); - initFromRepository(deployedNodeRepository, initRepository); - log.info("Node repository initialised from " + labeledUri); - } - } - - /** Init from a (typically remote) repository. */ - private void initFromRepository(Repository deployedNodeRepository, Repository initRepository) { - Session initSession = null; - try { - initSession = initRepository.login(); - workspaces: for (String workspaceName : initSession.getWorkspace().getAccessibleWorkspaceNames()) { - if ("security".equals(workspaceName)) - continue workspaces; - if (log.isDebugEnabled()) - log.debug("Copying workspace " + workspaceName + " from init repository..."); - long begin = System.currentTimeMillis(); - Session targetSession = null; - Session sourceSession = null; - try { - try { - targetSession = CmsJcrUtils.openDataAdminSession(deployedNodeRepository, workspaceName); - } catch (IllegalArgumentException e) {// no such workspace - Session adminSession = CmsJcrUtils.openDataAdminSession(deployedNodeRepository, null); - try { - adminSession.getWorkspace().createWorkspace(workspaceName); - } finally { - Jcr.logout(adminSession); - } - targetSession = CmsJcrUtils.openDataAdminSession(deployedNodeRepository, workspaceName); - } - sourceSession = initRepository.login(workspaceName); -// JcrUtils.copyWorkspaceXml(sourceSession, targetSession); - // TODO deal with referenceable nodes - JcrUtils.copy(sourceSession.getRootNode(), targetSession.getRootNode()); - targetSession.save(); - long duration = System.currentTimeMillis() - begin; - if (log.isDebugEnabled()) - log.debug("Copied workspace " + workspaceName + " from init repository in " + (duration / 1000) - + " s"); - } catch (Exception e) { - log.error("Cannot copy workspace " + workspaceName + " from init repository.", e); - } finally { - Jcr.logout(sourceSession); - Jcr.logout(targetSession); - } - } - } catch (RepositoryException e) { - throw new JcrException(e); - } finally { - Jcr.logout(initSession); - } - } - - private void prepareHomeRepository(RepositoryImpl deployedRepository) { - Session adminSession = KernelUtils.openAdminSession(deployedRepository); - try { - argeoDataModelExtensionsAvailable = Arrays - .asList(adminSession.getWorkspace().getNamespaceRegistry().getURIs()) - .contains(ArgeoNames.ARGEO_NAMESPACE); - } catch (RepositoryException e) { - log.warn("Cannot check whether Argeo namespace is registered assuming it isn't.", e); - argeoDataModelExtensionsAvailable = false; - } finally { - JcrUtils.logoutQuietly(adminSession); - } - - // Publish home with the highest service ranking - Hashtable regProps = new Hashtable<>(); - regProps.put(CmsConstants.CN, CmsConstants.EGO_REPOSITORY); - regProps.put(Constants.SERVICE_RANKING, Integer.MAX_VALUE); - Repository egoRepository = new EgoRepository(deployedRepository, false); - bc.registerService(Repository.class, egoRepository, regProps); - registerRepositoryServlets(CmsConstants.EGO_REPOSITORY, egoRepository); - - // Keyring only if Argeo extensions are available - if (argeoDataModelExtensionsAvailable) { - new ServiceTracker(bc, CallbackHandler.class, null) { - - @Override - public CallbackHandler addingService(ServiceReference reference) { - NodeKeyRing nodeKeyring = new NodeKeyRing(egoRepository); - CallbackHandler callbackHandler = bc.getService(reference); - nodeKeyring.setDefaultCallbackHandler(callbackHandler); - bc.registerService(LangUtils.names(Keyring.class, CryptoKeyring.class, ManagedService.class), - nodeKeyring, LangUtils.dict(Constants.SERVICE_PID, CmsConstants.NODE_KEYRING_PID)); - return callbackHandler; - } - - }.open(); - } - } - - /** Session is logged out. */ - private void prepareDataModel(String cn, Repository repository, List publishAsLocalRepo) { - Session adminSession = KernelUtils.openAdminSession(repository); - try { - Set processed = new HashSet(); - bundles: for (Bundle bundle : bc.getBundles()) { - BundleWiring wiring = bundle.adapt(BundleWiring.class); - if (wiring == null) - continue bundles; - if (CmsConstants.NODE_REPOSITORY.equals(cn))// process all data models - processWiring(cn, adminSession, wiring, processed, false, publishAsLocalRepo); - else { - List capabilities = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE); - for (BundleCapability capability : capabilities) { - String dataModelName = (String) capability.getAttributes().get(DataModelNamespace.NAME); - if (dataModelName.equals(cn))// process only own data model - processWiring(cn, adminSession, wiring, processed, false, publishAsLocalRepo); - } - } - } - } finally { - JcrUtils.logoutQuietly(adminSession); - } - } - - private void processWiring(String cn, Session adminSession, BundleWiring wiring, Set processed, - boolean importListedAbstractModels, List publishAsLocalRepo) { - // recursively process requirements first - List requiredWires = wiring.getRequiredWires(CMS_DATA_MODEL_NAMESPACE); - for (BundleWire wire : requiredWires) { - processWiring(cn, adminSession, wire.getProviderWiring(), processed, true, publishAsLocalRepo); - } - - List capabilities = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE); - capabilities: for (BundleCapability capability : capabilities) { - if (!importListedAbstractModels - && KernelUtils.asBoolean((String) capability.getAttributes().get(DataModelNamespace.ABSTRACT))) { - continue capabilities; - } - boolean publish = registerDataModelCapability(cn, adminSession, capability, processed); - if (publish) - publishAsLocalRepo.add((String) capability.getAttributes().get(DataModelNamespace.NAME)); - } - } - - private boolean registerDataModelCapability(String cn, Session adminSession, BundleCapability capability, - Set processed) { - Map attrs = capability.getAttributes(); - String name = (String) attrs.get(DataModelNamespace.NAME); - if (processed.contains(name)) { - if (log.isTraceEnabled()) - log.trace("Data model " + name + " has already been processed"); - return false; - } - - // CND - String path = (String) attrs.get(DataModelNamespace.CND); - if (path != null) { - File dataModel = bc.getBundle().getDataFile("dataModels/" + path); - if (!dataModel.exists()) { - URL url = capability.getRevision().getBundle().getResource(path); - if (url == null) - throw new IllegalArgumentException("No data model '" + name + "' found under path " + path); - try (Reader reader = new InputStreamReader(url.openStream())) { - CndImporter.registerNodeTypes(reader, adminSession, true); - processed.add(name); - dataModel.getParentFile().mkdirs(); - dataModel.createNewFile(); - if (log.isDebugEnabled()) - log.debug("Registered CND " + url); - } catch (Exception e) { - log.error("Cannot import CND " + url, e); - } - } - } - - if (KernelUtils.asBoolean((String) attrs.get(DataModelNamespace.ABSTRACT))) - return false; - // Non abstract - boolean isStandalone = isStandalone(name); - boolean publishLocalRepo; - if (isStandalone && name.equals(cn))// includes the node itself - publishLocalRepo = true; - else if (!isStandalone && cn.equals(CmsConstants.NODE_REPOSITORY)) - publishLocalRepo = true; - else - publishLocalRepo = false; - - return publishLocalRepo; - } - - boolean isStandalone(String dataModelName) { - return cmsDeployment.getProps(CmsConstants.NODE_REPOS_FACTORY_PID, dataModelName) != null; - } - - private void publishLocalRepo(String dataModelName, Repository repository) { - Hashtable properties = new Hashtable<>(); - properties.put(CmsConstants.CN, dataModelName); - LocalRepository localRepository; - String[] classes; - if (repository instanceof RepositoryImpl) { - localRepository = new JackrabbitLocalRepository((RepositoryImpl) repository, dataModelName); - classes = new String[] { Repository.class.getName(), LocalRepository.class.getName(), - JackrabbitLocalRepository.class.getName() }; - } else { - localRepository = new LocalRepository(repository, dataModelName); - classes = new String[] { Repository.class.getName(), LocalRepository.class.getName() }; - } - bc.registerService(classes, localRepository, properties); - - // TODO make it configurable - registerRepositoryServlets(dataModelName, localRepository); - if (log.isTraceEnabled()) - log.trace("Published data model " + dataModelName); - } - -// @Override -// public synchronized Long getAvailableSince() { -// return availableSince; -// } -// -// public synchronized boolean isAvailable() { -// return availableSince != null; -// } - - protected void registerRepositoryServlets(String alias, Repository repository) { - // FIXME re-enable it with a proper class loader -// registerRemotingServlet(alias, repository); -// registerWebdavServlet(alias, repository); - } - - protected void registerWebdavServlet(String alias, Repository repository) { - CmsWebDavServlet webdavServlet = new CmsWebDavServlet(alias, repository); - Hashtable ip = new Hashtable<>(); - ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsWebDavServlet.INIT_PARAM_RESOURCE_CONFIG, webDavConfig); - ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsWebDavServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, - "/" + alias); - - ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, "/" + alias + "/*"); - ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT, - "(" + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_PATH + "=" + CmsConstants.PATH_DATA + ")"); - bc.registerService(Servlet.class, webdavServlet, ip); - } - - protected void registerRemotingServlet(String alias, Repository repository) { - CmsRemotingServlet remotingServlet = new CmsRemotingServlet(alias, repository); - Hashtable ip = new Hashtable<>(); - ip.put(CmsConstants.CN, alias); - // Properties ip = new Properties(); - ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_RESOURCE_PATH_PREFIX, - "/" + alias); - ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_AUTHENTICATE_HEADER, - "Negotiate"); - - // Looks like a bug in Jackrabbit remoting init - Path tmpDir; - try { - tmpDir = Files.createTempDirectory("remoting_" + alias); - } catch (IOException e) { - throw new RuntimeException("Cannot create temp directory for remoting servlet", e); - } - ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_HOME, tmpDir.toString()); - ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_TMP_DIRECTORY, - "remoting_" + alias); - ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_PROTECTED_HANDLERS_CONFIG, - JcrHttpUtils.DEFAULT_PROTECTED_HANDLERS); - ip.put(HTTP_WHITEBOARD_SERVLET_INIT_PARAM_PREFIX + CmsRemotingServlet.INIT_PARAM_CREATE_ABSOLUTE_URI, "false"); - - ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_SERVLET_PATTERN, "/" + alias + "/*"); - ip.put(HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_SELECT, - "(" + HttpWhiteboardConstants.HTTP_WHITEBOARD_CONTEXT_PATH + "=" + CmsConstants.PATH_JCR + ")"); - bc.registerService(Servlet.class, remotingServlet, ip); - } - - private class RepositoryContextStc extends ServiceTracker { - - public RepositoryContextStc() { - super(bc, RepositoryContext.class, null); - } - - @Override - public RepositoryContext addingService(ServiceReference reference) { - RepositoryContext repoContext = bc.getService(reference); - String cn = (String) reference.getProperty(CmsConstants.CN); - if (cn != null) { - List publishAsLocalRepo = new ArrayList<>(); - if (cn.equals(CmsConstants.NODE_REPOSITORY)) { -// JackrabbitDataModelMigration.clearRepositoryCaches(repoContext.getRepositoryConfig()); - prepareNodeRepository(repoContext.getRepository(), publishAsLocalRepo); - // TODO separate home repository - prepareHomeRepository(repoContext.getRepository()); - registerRepositoryServlets(cn, repoContext.getRepository()); - nodeAvailable = true; -// checkReadiness(); - } else { - prepareDataModel(cn, repoContext.getRepository(), publishAsLocalRepo); - } - // Publish all at once, so that bundles with multiple CNDs are consistent - for (String dataModelName : publishAsLocalRepo) - publishLocalRepo(dataModelName, repoContext.getRepository()); - } - return repoContext; - } - - @Override - public void modifiedService(ServiceReference reference, RepositoryContext service) { - } - - @Override - public void removedService(ServiceReference reference, RepositoryContext service) { - } - - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrFsProvider.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrFsProvider.java deleted file mode 100644 index 0099b3bed..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsJcrFsProvider.java +++ /dev/null @@ -1,133 +0,0 @@ -package org.argeo.cms.jcr.internal; - -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.file.FileSystem; -import java.nio.file.FileSystemAlreadyExistsException; -import java.nio.file.Path; -import java.nio.file.spi.FileSystemProvider; -import java.util.HashMap; -import java.util.Map; - -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.RepositoryFactory; -import javax.jcr.Session; -import javax.jcr.nodetype.NodeType; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.cms.auth.CurrentUser; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.jackrabbit.fs.AbstractJackrabbitFsProvider; -import org.argeo.jcr.fs.JcrFileSystem; -import org.argeo.jcr.fs.JcrFileSystemProvider; -import org.argeo.jcr.fs.JcrFsException; - -/** Implementation of an {@link FileSystemProvider} based on Jackrabbit. */ -public class CmsJcrFsProvider extends AbstractJackrabbitFsProvider { - private Map fileSystems = new HashMap<>(); - - private RepositoryFactory repositoryFactory; - private Repository repository; - - @Override - public String getScheme() { - return CmsConstants.SCHEME_NODE; - } - - @Override - public FileSystem newFileSystem(URI uri, Map env) throws IOException { -// BundleContext bc = FrameworkUtil.getBundle(CmsJcrFsProvider.class).getBundleContext(); - String username = CurrentUser.getUsername(); - if (username == null) { - // TODO deal with anonymous - return null; - } - if (fileSystems.containsKey(username)) - throw new FileSystemAlreadyExistsException("CMS file system already exists for user " + username); - - try { - String host = uri.getHost(); - if (host != null && !host.trim().equals("")) { - URI repoUri = new URI("http", uri.getUserInfo(), uri.getHost(), uri.getPort(), "/jcr/node", null, null); -// RepositoryFactory repositoryFactory = bc.getService(bc.getServiceReference(RepositoryFactory.class)); - Repository repository = CmsJcrUtils.getRepositoryByUri(repositoryFactory, repoUri.toString()); - CmsFileSystem fileSystem = new CmsFileSystem(this, repository); - fileSystems.put(username, fileSystem); - return fileSystem; - } else { -// Repository repository = bc.getService( -// bc.getServiceReferences(Repository.class, "(cn=" + CmsConstants.EGO_REPOSITORY + ")") -// .iterator().next()); - - // Session session = repository.login(); - CmsFileSystem fileSystem = new CmsFileSystem(this, repository); - fileSystems.put(username, fileSystem); - return fileSystem; - } - } catch (URISyntaxException e) { - throw new IllegalArgumentException("Cannot open file system " + uri + " for user " + username, e); - } - } - - @Override - public FileSystem getFileSystem(URI uri) { - return currentUserFileSystem(); - } - - @Override - public Path getPath(URI uri) { - JcrFileSystem fileSystem = currentUserFileSystem(); - String path = uri.getPath(); - if (fileSystem == null) - try { - fileSystem = (JcrFileSystem) newFileSystem(uri, new HashMap()); - } catch (IOException e) { - throw new JcrFsException("Could not autocreate file system", e); - } - return fileSystem.getPath(path); - } - - protected JcrFileSystem currentUserFileSystem() { - String username = CurrentUser.getUsername(); - return fileSystems.get(username); - } - - public Node getUserHome(Repository repository) { - try { - Session session = repository.login(CmsConstants.HOME_WORKSPACE); - return CmsJcrUtils.getUserHome(session); - } catch (RepositoryException e) { - throw new IllegalStateException("Cannot get user home", e); - } - } - - public void setRepositoryFactory(RepositoryFactory repositoryFactory) { - this.repositoryFactory = repositoryFactory; - } - - public void setRepository(Repository repository) { - this.repository = repository; - } - - static class CmsFileSystem extends JcrFileSystem { - public CmsFileSystem(JcrFileSystemProvider provider, Repository repository) throws IOException { - super(provider, repository); - } - - public boolean skipNode(Node node) throws RepositoryException { -// if (node.isNodeType(NodeType.NT_HIERARCHY_NODE) || node.isNodeType(NodeTypes.NODE_USER_HOME) -// || node.isNodeType(NodeTypes.NODE_GROUP_HOME)) - if (node.isNodeType(NodeType.NT_HIERARCHY_NODE)) - return false; - // FIXME Better identifies home - if (node.hasProperty(Property.JCR_ID)) - return false; - return true; - } - - } -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsPaths.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsPaths.java deleted file mode 100644 index e7f5a55af..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsPaths.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.argeo.cms.jcr.internal; - -import java.nio.file.Path; - -/** Centralises access to the default node deployment directories. */ -public class CmsPaths { - public static Path getRepoDirPath(String cn) { - return KernelUtils.getOsgiInstancePath(KernelConstants.DIR_REPOS + '/' + cn); - } - - public static Path getRepoIndexesBase() { - return KernelUtils.getOsgiInstancePath(KernelConstants.DIR_INDEXES); - } - - /** Singleton. */ - private CmsPaths() { - } -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsWorkspaceIndexer.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsWorkspaceIndexer.java deleted file mode 100644 index 69b98dc3a..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/CmsWorkspaceIndexer.java +++ /dev/null @@ -1,342 +0,0 @@ -package org.argeo.cms.jcr.internal; - -import java.util.GregorianCalendar; -import java.util.concurrent.LinkedBlockingDeque; -import java.util.concurrent.atomic.AtomicBoolean; - -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.PropertyType; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.Value; -import javax.jcr.nodetype.NodeType; -import javax.jcr.observation.Event; -import javax.jcr.observation.EventIterator; -import javax.jcr.observation.EventListener; -import javax.jcr.version.VersionManager; - -import org.apache.jackrabbit.api.JackrabbitValue; -import org.apache.jackrabbit.core.RepositoryImpl; -import org.argeo.api.cms.CmsLog; -import org.argeo.jcr.JcrUtils; - -/** Ensure consistency of files, folder and last modified nodes. */ -class CmsWorkspaceIndexer implements EventListener { - private final static CmsLog log = CmsLog.getLog(CmsWorkspaceIndexer.class); - -// private final static String MIX_ETAG = "mix:etag"; - private final static String JCR_ETAG = "jcr:etag"; -// private final static String JCR_LAST_MODIFIED = "jcr:lastModified"; -// private final static String JCR_LAST_MODIFIED_BY = "jcr:lastModifiedBy"; -// private final static String JCR_MIXIN_TYPES = "jcr:mixinTypes"; - private final static String JCR_DATA = "jcr:data"; - private final static String JCR_CONTENT = "jcr:data"; - - private String cn; - private String workspaceName; - private RepositoryImpl repositoryImpl; - private Session session; - private VersionManager versionManager; - - private LinkedBlockingDeque toProcess = new LinkedBlockingDeque<>(); - private IndexingThread indexingThread; - private AtomicBoolean stopping = new AtomicBoolean(false); - - public CmsWorkspaceIndexer(RepositoryImpl repositoryImpl, String cn, String workspaceName) - throws RepositoryException { - this.cn = cn; - this.workspaceName = workspaceName; - this.repositoryImpl = repositoryImpl; - } - - public void init() { - session = KernelUtils.openAdminSession(repositoryImpl, workspaceName); - try { - String[] nodeTypes = { NodeType.NT_FILE, NodeType.MIX_LAST_MODIFIED }; - session.getWorkspace().getObservationManager().addEventListener(this, - Event.NODE_ADDED | Event.PROPERTY_CHANGED, "/", true, null, nodeTypes, true); - versionManager = session.getWorkspace().getVersionManager(); - - indexingThread = new IndexingThread(); - indexingThread.start(); - } catch (RepositoryException e1) { - throw new IllegalStateException(e1); - } - } - - public void destroy() { - stopping.set(true); - indexingThread.interrupt(); - // TODO make it configurable - try { - indexingThread.join(10 * 60 * 1000); - } catch (InterruptedException e1) { - log.warn("Indexing thread interrupted. Will log out session."); - } - - try { - session.getWorkspace().getObservationManager().removeEventListener(this); - } catch (RepositoryException e) { - if (log.isTraceEnabled()) - log.warn("Cannot unregistered JCR event listener", e); - } finally { - JcrUtils.logoutQuietly(session); - } - } - - private synchronized void processEvents(EventIterator events) { - long begin = System.currentTimeMillis(); - long count = 0; - while (events.hasNext()) { - Event event = events.nextEvent(); - try { - toProcess.put(event); - } catch (InterruptedException e) { - e.printStackTrace(); - } -// processEvent(event); - count++; - } - long duration = System.currentTimeMillis() - begin; - if (log.isTraceEnabled()) - log.trace("Processed " + count + " events in " + duration + " ms"); - notifyAll(); - } - - protected synchronized void processEvent(Event event) { - try { - String eventPath = event.getPath(); - if (event.getType() == Event.NODE_ADDED) { - if (!versionManager.isCheckedOut(eventPath)) - return;// ignore checked-in nodes - if (log.isTraceEnabled()) - log.trace("NODE_ADDED " + eventPath); -// session.refresh(true); - session.refresh(false); - Node node = session.getNode(eventPath); - Node parentNode = node.getParent(); - if (parentNode.isNodeType(NodeType.NT_FILE)) { - if (node.isNodeType(NodeType.NT_UNSTRUCTURED)) { - if (!node.isNodeType(NodeType.MIX_LAST_MODIFIED)) - node.addMixin(NodeType.MIX_LAST_MODIFIED); - Property property = node.getProperty(Property.JCR_DATA); - String etag = toEtag(property.getValue()); - session.save(); - node.setProperty(JCR_ETAG, etag); - if (log.isTraceEnabled()) - log.trace("ETag and last modified added to new " + node); - } else if (node.isNodeType(NodeType.NT_RESOURCE)) { -// if (!node.isNodeType(MIX_ETAG)) -// node.addMixin(MIX_ETAG); -// session.save(); -// Property property = node.getProperty(Property.JCR_DATA); -// String etag = toEtag(property.getValue()); -// node.setProperty(JCR_ETAG, etag); -// session.save(); - } -// setLastModifiedRecursive(parentNode, event); -// session.save(); -// if (log.isTraceEnabled()) -// log.trace("ETag and last modified added to new " + node); - } - -// if (node.isNodeType(NodeType.NT_FOLDER)) { -// setLastModifiedRecursive(node, event); -// session.save(); -// if (log.isTraceEnabled()) -// log.trace("Last modified added to new " + node); -// } - } else if (event.getType() == Event.PROPERTY_CHANGED) { - String propertyName = extractItemName(eventPath); - // skip if last modified properties are explicitly set - if (!propertyName.equals(JCR_DATA)) - return; -// if (propertyName.equals(JCR_LAST_MODIFIED)) -// return; -// if (propertyName.equals(JCR_LAST_MODIFIED_BY)) -// return; -// if (propertyName.equals(JCR_MIXIN_TYPES)) -// return; -// if (propertyName.equals(JCR_ETAG)) -// return; - - if (log.isTraceEnabled()) - log.trace("PROPERTY_CHANGED " + eventPath); - - if (!session.propertyExists(eventPath)) - return; - session.refresh(false); - Property property = session.getProperty(eventPath); - Node node = property.getParent(); - if (property.getType() == PropertyType.BINARY && propertyName.equals(JCR_DATA) - && node.isNodeType(NodeType.NT_UNSTRUCTURED)) { - String etag = toEtag(property.getValue()); - node.setProperty(JCR_ETAG, etag); - Node parentNode = node.getParent(); - if (parentNode.isNodeType(NodeType.MIX_LAST_MODIFIED)) { - setLastModified(parentNode, event); - } - if (log.isTraceEnabled()) - log.trace("ETag and last modified updated for " + node); - } -// setLastModified(node, event); -// session.save(); -// if (log.isTraceEnabled()) -// log.trace("ETag and last modified updated for " + node); - } else if (event.getType() == Event.NODE_REMOVED) { - String removeNodePath = eventPath; - String nodeName = extractItemName(eventPath); - if (JCR_CONTENT.equals(nodeName)) // parent is a file, deleted anyhow - return; - if (log.isTraceEnabled()) - log.trace("NODE_REMOVED " + eventPath); -// String parentPath = JcrUtils.parentPath(removeNodePath); -// session.refresh(true); -// setLastModified(parentPath, event); -// session.save(); - if (log.isTraceEnabled()) - log.trace("Last modified updated for parents of removed " + removeNodePath); - } - } catch (Exception e) { - if (log.isTraceEnabled()) - log.warn("Cannot process event " + event, e); - } finally { -// try { -// session.refresh(true); -// if (session.hasPendingChanges()) -// session.save(); -//// session.refresh(false); -// } catch (RepositoryException e) { -// if (log.isTraceEnabled()) -// log.warn("Cannot refresh JCR session", e); -// } - } - - } - - private String extractItemName(String path) { - if (path == null || path.length() <= 1) - return null; - int lastIndex = path.lastIndexOf('/'); - if (lastIndex >= 0) { - return path.substring(lastIndex + 1); - } else { - return path; - } - } - - @Override - public void onEvent(EventIterator events) { - processEvents(events); -// Runnable toRun = new Runnable() { -// -// @Override -// public void run() { -// processEvents(events); -// } -// }; -// Future future = Activator.getInternalExecutorService().submit(toRun); -// try { -// // make the call synchronous -// future.get(60, TimeUnit.SECONDS); -// } catch (TimeoutException | ExecutionException | InterruptedException e) { -// // silent -// } - } - - static String toEtag(Value v) { - if (v instanceof JackrabbitValue) { - JackrabbitValue value = (JackrabbitValue) v; - return '\"' + value.getContentIdentity() + '\"'; - } else { - return null; - } - - } - - protected synchronized void setLastModified(Node node, Event event) throws RepositoryException { - GregorianCalendar calendar = new GregorianCalendar(); - calendar.setTimeInMillis(event.getDate()); - node.setProperty(Property.JCR_LAST_MODIFIED, calendar); - node.setProperty(Property.JCR_LAST_MODIFIED_BY, event.getUserID()); - if (log.isTraceEnabled()) - log.trace("Last modified set on " + node); - } - - /** Recursively set the last updated time on parents. */ - protected synchronized void setLastModifiedRecursive(Node node, Event event) throws RepositoryException { - if (versionManager.isCheckedOut(node.getPath())) { - if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) { - setLastModified(node, event); - } - if (node.isNodeType(NodeType.NT_FOLDER) && !node.isNodeType(NodeType.MIX_LAST_MODIFIED)) { - node.addMixin(NodeType.MIX_LAST_MODIFIED); - if (log.isTraceEnabled()) - log.trace("Last modified mix-in added to " + node); - } - - } - - // end condition - if (node.getDepth() == 0) { -// try { -// node.getSession().save(); -// } catch (RepositoryException e) { -// log.warn("Cannot index workspace", e); -// } - return; - } else { - Node parent = node.getParent(); - setLastModifiedRecursive(parent, event); - } - } - - /** - * Recursively set the last updated time on parents. Useful to use paths when - * dealing with deletions. - */ - protected synchronized void setLastModifiedRecursive(String path, Event event) throws RepositoryException { - // root node will always exist, so end condition is delegated to the other - // recursive setLastModified method - if (session.nodeExists(path)) { - setLastModifiedRecursive(session.getNode(path), event); - } else { - setLastModifiedRecursive(JcrUtils.parentPath(path), event); - } - } - - @Override - public String toString() { - return "Indexer for workspace " + workspaceName + " of repository " + cn; - } - - class IndexingThread extends Thread { - - public IndexingThread() { - super(CmsWorkspaceIndexer.this.toString()); - // TODO Auto-generated constructor stub - } - - @Override - public void run() { - life: while (session != null && session.isLive()) { - try { - Event nextEvent = toProcess.take(); - processEvent(nextEvent); - } catch (InterruptedException e) { - // silent - interrupted(); - } - - if (stopping.get() && toProcess.isEmpty()) { - break life; - } - } - if (log.isDebugEnabled()) - log.debug(CmsWorkspaceIndexer.this.toString() + " has shut down."); - } - - } - -} \ No newline at end of file diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/DataModels.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/DataModels.java deleted file mode 100644 index f2196bd41..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/DataModels.java +++ /dev/null @@ -1,190 +0,0 @@ -package org.argeo.cms.jcr.internal; - -import static org.argeo.cms.osgi.DataModelNamespace.CMS_DATA_MODEL_NAMESPACE; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; - -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.osgi.DataModelNamespace; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.BundleEvent; -import org.osgi.framework.BundleListener; -import org.osgi.framework.wiring.BundleCapability; -import org.osgi.framework.wiring.BundleWire; -import org.osgi.framework.wiring.BundleWiring; - -class DataModels implements BundleListener { - private final static CmsLog log = CmsLog.getLog(DataModels.class); - - private Map dataModels = new TreeMap<>(); - - public DataModels(BundleContext bc) { - for (Bundle bundle : bc.getBundles()) - processBundle(bundle, null); - bc.addBundleListener(this); - } - - public List getNonAbstractDataModels() { - List res = new ArrayList<>(); - for (String name : dataModels.keySet()) { - DataModel dataModel = dataModels.get(name); - if (!dataModel.isAbstract()) - res.add(dataModel); - } - // TODO reorder? - return res; - } - - @Override - public void bundleChanged(BundleEvent event) { - if (event.getType() == Bundle.RESOLVED) { - processBundle(event.getBundle(), null); - } else if (event.getType() == Bundle.UNINSTALLED) { - BundleWiring wiring = event.getBundle().adapt(BundleWiring.class); - List providedDataModels = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE); - if (providedDataModels.size() == 0) - return; - for (BundleCapability bundleCapability : providedDataModels) { - dataModels.remove(bundleCapability.getAttributes().get(DataModelNamespace.NAME)); - } - } - - } - - protected void processBundle(Bundle bundle, List scannedBundles) { - if (scannedBundles != null && scannedBundles.contains(bundle)) - throw new IllegalStateException("Cycle in CMS data model requirements for " + bundle); - BundleWiring wiring = bundle.adapt(BundleWiring.class); - if (wiring == null) { - int bundleState = bundle.getState(); - if (bundleState != Bundle.INSTALLED && bundleState != Bundle.UNINSTALLED) {// ignore unresolved bundles - log.warn("Bundle " + bundle.getSymbolicName() + " #" + bundle.getBundleId() + " (" - + bundle.getLocation() + ") cannot be adapted to a wiring"); - } else { - if (log.isTraceEnabled()) - log.warn("Bundle " + bundle.getSymbolicName() + " is not resolved."); - } - return; - } - List providedDataModels = wiring.getCapabilities(CMS_DATA_MODEL_NAMESPACE); - if (providedDataModels.size() == 0) - return; - List requiredDataModels = wiring.getRequiredWires(CMS_DATA_MODEL_NAMESPACE); - // process requirements first - for (BundleWire bundleWire : requiredDataModels) { - List nextScannedBundles = new ArrayList<>(); - if (scannedBundles != null) - nextScannedBundles.addAll(scannedBundles); - nextScannedBundles.add(bundle); - Bundle providerBundle = bundleWire.getProvider().getBundle(); - processBundle(providerBundle, nextScannedBundles); - } - for (BundleCapability bundleCapability : providedDataModels) { - String name = (String) bundleCapability.getAttributes().get(DataModelNamespace.NAME); - assert name != null; - if (!dataModels.containsKey(name)) { - DataModel dataModel = new DataModel(name, bundleCapability, requiredDataModels); - dataModels.put(dataModel.getName(), dataModel); - } - } - } - - /** Return a negative depth if dataModel is required by ref, 0 otherwise. */ - static int required(DataModel ref, DataModel dataModel, int depth) { - for (DataModel dm : ref.getRequired()) { - if (dm.equals(dataModel))// found here - return depth - 1; - int d = required(dm, dataModel, depth - 1); - if (d != 0)// found deeper - return d; - } - return 0;// not found - } - - class DataModel { - private final String name; - private final boolean abstrct; - // private final boolean standalone; - private final String cnd; - private final List required; - - private DataModel(String name, BundleCapability bundleCapability, List requiredDataModels) { - assert CMS_DATA_MODEL_NAMESPACE.equals(bundleCapability.getNamespace()); - this.name = name; - Map attrs = bundleCapability.getAttributes(); - abstrct = KernelUtils.asBoolean((String) attrs.get(DataModelNamespace.ABSTRACT)); - // standalone = KernelUtils.asBoolean((String) - // attrs.get(DataModelNamespace.CAPABILITY_STANDALONE_ATTRIBUTE)); - cnd = (String) attrs.get(DataModelNamespace.CND); - List req = new ArrayList<>(); - for (BundleWire wire : requiredDataModels) { - String requiredDataModelName = (String) wire.getCapability().getAttributes() - .get(DataModelNamespace.NAME); - assert requiredDataModelName != null; - DataModel requiredDataModel = dataModels.get(requiredDataModelName); - if (requiredDataModel == null) - throw new IllegalStateException("No required data model " + requiredDataModelName); - req.add(requiredDataModel); - } - required = Collections.unmodifiableList(req); - } - - public String getName() { - return name; - } - - public boolean isAbstract() { - return abstrct; - } - - // public boolean isStandalone() { - // return !isAbstract(); - // } - - public String getCnd() { - return cnd; - } - - public List getRequired() { - return required; - } - - // @Override - // public int compareTo(DataModel o) { - // if (equals(o)) - // return 0; - // int res = required(this, o, 0); - // if (res != 0) - // return res; - // // the other way round - // res = required(o, this, 0); - // if (res != 0) - // return -res; - // return 0; - // } - - @Override - public int hashCode() { - return name.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof DataModel) - return ((DataModel) obj).name.equals(name); - return false; - } - - @Override - public String toString() { - return "Data model " + name; - } - - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/EgoRepository.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/EgoRepository.java deleted file mode 100644 index 298025096..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/EgoRepository.java +++ /dev/null @@ -1,265 +0,0 @@ -package org.argeo.cms.jcr.internal; - -import java.security.PrivilegedAction; -import java.text.SimpleDateFormat; -import java.util.HashSet; -import java.util.Set; - -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.nodetype.NodeType; -import javax.jcr.security.Privilege; -import javax.naming.InvalidNameException; -import javax.naming.ldap.LdapName; -import javax.security.auth.Subject; -import javax.security.auth.login.LoginContext; - -import org.argeo.api.cms.CmsAuth; -import org.argeo.api.cms.CmsConstants; -import org.argeo.cms.CmsException; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.jcr.JcrException; -import org.argeo.jcr.JcrRepositoryWrapper; -import org.argeo.jcr.JcrUtils; - -/** - * Make sure each user has a home directory available. - */ -class EgoRepository extends JcrRepositoryWrapper implements KernelConstants { - - /** The home base path. */ -// private String homeBasePath = KernelConstants.DEFAULT_HOME_BASE_PATH; -// private String usersBasePath = KernelConstants.DEFAULT_USERS_BASE_PATH; -// private String groupsBasePath = KernelConstants.DEFAULT_GROUPS_BASE_PATH; - - private Set checkedUsers = new HashSet(); - - private SimpleDateFormat usersDatePath = new SimpleDateFormat("YYYY/MM"); - - private String defaultHomeWorkspace = CmsConstants.HOME_WORKSPACE; - private String defaultGroupsWorkspace = CmsConstants.SRV_WORKSPACE; -// private String defaultGuestsWorkspace = NodeConstants.GUESTS_WORKSPACE; - private final boolean remote; - - public EgoRepository(Repository repository, boolean remote) { - super(repository); - this.remote = remote; - putDescriptor(CmsConstants.CN, CmsConstants.EGO_REPOSITORY); - if (!remote) { - LoginContext lc; - try { - lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_DATA_ADMIN); - lc.login(); - } catch (javax.security.auth.login.LoginException e1) { - throw new IllegalStateException("Cannot login as system", e1); - } - Subject.doAs(lc.getSubject(), new PrivilegedAction() { - - @Override - public Void run() { - loginOrCreateWorkspace(defaultHomeWorkspace); - loginOrCreateWorkspace(defaultGroupsWorkspace); - return null; - } - - }); - } - } - - private void loginOrCreateWorkspace(String workspace) { - Session adminSession = null; - try { - adminSession = JcrUtils.loginOrCreateWorkspace(getRepository(workspace), workspace); -// JcrUtils.addPrivilege(adminSession, "/", NodeConstants.ROLE_USER, Privilege.JCR_READ); - -// initJcr(adminSession); - } catch (RepositoryException e) { - throw new JcrException("Cannot init JCR home", e); - } finally { - JcrUtils.logoutQuietly(adminSession); - } - } - -// @Override -// public Session login(Credentials credentials, String workspaceName) -// throws LoginException, NoSuchWorkspaceException, RepositoryException { -// if (workspaceName == null) { -// return super.login(credentials, getUserHomeWorkspace()); -// } else { -// return super.login(credentials, workspaceName); -// } -// } - - protected String getUserHomeWorkspace() { - // TODO base on JAAS Subject metadata - return defaultHomeWorkspace; - } - - protected String getGroupsWorkspace() { - // TODO base on JAAS Subject metadata - return defaultGroupsWorkspace; - } - -// protected String getGuestsWorkspace() { -// // TODO base on JAAS Subject metadata -// return defaultGuestsWorkspace; -// } - - @Override - protected void processNewSession(Session session, String workspaceName) { - String username = session.getUserID(); - if (username == null || username.toString().equals("")) - return; - if (session.getUserID().equals(CmsConstants.ROLE_ANONYMOUS)) - return; - - String userHomeWorkspace = getUserHomeWorkspace(); - if (workspaceName == null || !workspaceName.equals(userHomeWorkspace)) - return; - - if (checkedUsers.contains(username)) - return; - Session adminSession = KernelUtils.openAdminSession(getRepository(workspaceName), workspaceName); - try { - syncJcr(adminSession, username); - checkedUsers.add(username); - } finally { - JcrUtils.logoutQuietly(adminSession); - } - } - - /* - * JCR - */ - /** Session is logged out. */ - private void initJcr(Session adminSession) { - try { -// JcrUtils.mkdirs(adminSession, homeBasePath); -// JcrUtils.mkdirs(adminSession, groupsBasePath); - adminSession.save(); - -// JcrUtils.addPrivilege(adminSession, homeBasePath, NodeConstants.ROLE_USER_ADMIN, Privilege.JCR_READ); -// JcrUtils.addPrivilege(adminSession, groupsBasePath, NodeConstants.ROLE_USER_ADMIN, Privilege.JCR_READ); - adminSession.save(); - } catch (RepositoryException e) { - throw new CmsException("Cannot initialize home repository", e); - } finally { - JcrUtils.logoutQuietly(adminSession); - } - } - - protected synchronized void syncJcr(Session adminSession, String username) { - // only in the default workspace -// if (workspaceName != null) -// return; - // skip system users - if (username.endsWith(CmsConstants.ROLES_BASEDN)) - return; - - try { - Node userHome = CmsJcrUtils.getUserHome(adminSession, username); - if (userHome == null) { -// String homePath = generateUserPath(username); - String userId = extractUserId(username); -// if (adminSession.itemExists(homePath))// duplicate user id -// userHome = adminSession.getNode(homePath).getParent().addNode(JcrUtils.lastPathElement(homePath)); -// else -// userHome = JcrUtils.mkdirs(adminSession, homePath); - userHome = adminSession.getRootNode().addNode(userId); -// userHome.addMixin(NodeTypes.NODE_USER_HOME); - userHome.addMixin(NodeType.MIX_CREATED); - userHome.addMixin(NodeType.MIX_TITLE); - userHome.setProperty(Property.JCR_ID, username); - // TODO use display name - userHome.setProperty(Property.JCR_TITLE, userId); -// userHome.setProperty(NodeNames.LDAP_UID, username); - adminSession.save(); - - JcrUtils.clearAccessControList(adminSession, userHome.getPath(), username); - JcrUtils.addPrivilege(adminSession, userHome.getPath(), username, Privilege.JCR_ALL); -// JackrabbitSecurityUtils.denyPrivilege(adminSession, userHome.getPath(), NodeConstants.ROLE_USER, -// Privilege.JCR_READ); - } - if (adminSession.hasPendingChanges()) - adminSession.save(); - } catch (RepositoryException e) { - JcrUtils.discardQuietly(adminSession); - throw new JcrException("Cannot sync node security model for " + username, e); - } - } - - /** Generate path for a new user home */ - private String generateUserPath(String username) { - LdapName dn; - try { - dn = new LdapName(username); - } catch (InvalidNameException e) { - throw new CmsException("Invalid name " + username, e); - } - String userId = dn.getRdn(dn.size() - 1).getValue().toString(); - return '/' + userId; -// int atIndex = userId.indexOf('@'); -// if (atIndex < 0) { -// return homeBasePath+'/' + userId; -// } else { -// return usersBasePath + '/' + usersDatePath.format(new Date()) + '/' + userId; -// } - } - - private String extractUserId(String username) { - LdapName dn; - try { - dn = new LdapName(username); - } catch (InvalidNameException e) { - throw new CmsException("Invalid name " + username, e); - } - String userId = dn.getRdn(dn.size() - 1).getValue().toString(); - return userId; -// int atIndex = userId.indexOf('@'); -// if (atIndex < 0) { -// return homeBasePath+'/' + userId; -// } else { -// return usersBasePath + '/' + usersDatePath.format(new Date()) + '/' + userId; -// } - } - - public void createWorkgroup(LdapName dn) { - String groupsWorkspace = getGroupsWorkspace(); - Session adminSession = KernelUtils.openAdminSession(getRepository(groupsWorkspace), groupsWorkspace); - String cn = dn.getRdn(dn.size() - 1).getValue().toString(); - Node newWorkgroup = CmsJcrUtils.getGroupHome(adminSession, cn); - if (newWorkgroup != null) { - JcrUtils.logoutQuietly(adminSession); - throw new CmsException("Workgroup " + newWorkgroup + " already exists for " + dn); - } - try { - // TODO enhance transformation of cn to a valid node name - // String relPath = cn.replaceAll("[^a-zA-Z0-9]", "_"); - String relPath = JcrUtils.replaceInvalidChars(cn); - newWorkgroup = adminSession.getRootNode().addNode(relPath, NodeType.NT_UNSTRUCTURED); -// newWorkgroup = JcrUtils.mkdirs(adminSession.getNode(groupsBasePath), relPath, NodeType.NT_UNSTRUCTURED); -// newWorkgroup.addMixin(NodeTypes.NODE_GROUP_HOME); - newWorkgroup.addMixin(NodeType.MIX_CREATED); - newWorkgroup.addMixin(NodeType.MIX_TITLE); - newWorkgroup.setProperty(Property.JCR_ID, dn.toString()); - newWorkgroup.setProperty(Property.JCR_TITLE, cn); -// newWorkgroup.setProperty(NodeNames.LDAP_CN, cn); - adminSession.save(); - JcrUtils.addPrivilege(adminSession, newWorkgroup.getPath(), dn.toString(), Privilege.JCR_ALL); - adminSession.save(); - } catch (RepositoryException e) { - throw new CmsException("Cannot create workgroup", e); - } finally { - JcrUtils.logoutQuietly(adminSession); - } - - } - - public boolean isRemote() { - return remote; - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JackrabbitLocalRepository.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JackrabbitLocalRepository.java deleted file mode 100644 index bad9fdfd5..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JackrabbitLocalRepository.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.argeo.cms.jcr.internal; - -import java.util.Map; -import java.util.TreeMap; - -import javax.jcr.RepositoryException; -import javax.jcr.Session; - -import org.apache.jackrabbit.core.RepositoryImpl; -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsLog; - -class JackrabbitLocalRepository extends LocalRepository { - private final static CmsLog log = CmsLog.getLog(JackrabbitLocalRepository.class); - final String SECURITY_WORKSPACE = "security"; - - private Map workspaceMonitors = new TreeMap<>(); - - public JackrabbitLocalRepository(RepositoryImpl repository, String cn) { - super(repository, cn); -// Session session = KernelUtils.openAdminSession(repository); -// try { -// if (NodeConstants.NODE.equals(cn)) -// for (String workspaceName : session.getWorkspace().getAccessibleWorkspaceNames()) { -// addMonitor(workspaceName); -// } -// } catch (RepositoryException e) { -// throw new IllegalStateException(e); -// } finally { -// JcrUtils.logoutQuietly(session); -// } - } - - protected RepositoryImpl getJackrabbitrepository(String workspaceName) { - return (RepositoryImpl) getRepository(workspaceName); - } - - @Override - protected synchronized void processNewSession(Session session, String workspaceName) { -// String realWorkspaceName = session.getWorkspace().getName(); -// addMonitor(realWorkspaceName); - } - - private void addMonitor(String realWorkspaceName) { - if (realWorkspaceName.equals(SECURITY_WORKSPACE)) - return; - if (!CmsConstants.NODE_REPOSITORY.equals(getCn())) - return; - - if (!workspaceMonitors.containsKey(realWorkspaceName)) { - try { - CmsWorkspaceIndexer workspaceMonitor = new CmsWorkspaceIndexer( - getJackrabbitrepository(realWorkspaceName), getCn(), realWorkspaceName); - workspaceMonitors.put(realWorkspaceName, workspaceMonitor); - workspaceMonitor.init(); - if (log.isDebugEnabled()) - log.debug("Registered " + workspaceMonitor); - } catch (RepositoryException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - } - - public void destroy() { - for (String workspaceName : workspaceMonitors.keySet()) { - workspaceMonitors.get(workspaceName).destroy(); - } - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JcrKeyring.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JcrKeyring.java deleted file mode 100644 index 17625f5d2..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JcrKeyring.java +++ /dev/null @@ -1,397 +0,0 @@ -package org.argeo.cms.jcr.internal; - -import java.io.ByteArrayInputStream; -import java.io.CharArrayReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.Reader; -import java.nio.charset.StandardCharsets; -import java.security.GeneralSecurityException; -import java.security.NoSuchAlgorithmException; -import java.security.Provider; -import java.security.SecureRandom; - -import javax.crypto.Cipher; -import javax.crypto.CipherInputStream; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.SecretKey; -import javax.crypto.spec.IvParameterSpec; -import javax.jcr.Binary; -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.Property; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.query.Query; - -import org.apache.commons.io.IOUtils; -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.ArgeoNames; -import org.argeo.cms.ArgeoTypes; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.cms.security.AbstractKeyring; -import org.argeo.cms.security.PBEKeySpecCallback; -import org.argeo.jcr.JcrException; -import org.argeo.jcr.JcrUtils; - -/** JCR based implementation of a keyring */ -public class JcrKeyring extends AbstractKeyring implements ArgeoNames { - private final static CmsLog log = CmsLog.getLog(JcrKeyring.class); - /** - * Stronger with 256, but causes problem with Oracle JVM, force 128 in this case - */ - public final static Long DEFAULT_SECRETE_KEY_LENGTH = 256l; - public final static String DEFAULT_SECRETE_KEY_FACTORY = "PBKDF2WithHmacSHA1"; - public final static String DEFAULT_SECRETE_KEY_ENCRYPTION = "AES"; - public final static String DEFAULT_CIPHER_NAME = "AES/CBC/PKCS5Padding"; - - private Integer iterationCountFactor = 200; - private Long secretKeyLength = DEFAULT_SECRETE_KEY_LENGTH; - private String secretKeyFactoryName = DEFAULT_SECRETE_KEY_FACTORY; - private String secretKeyEncryption = DEFAULT_SECRETE_KEY_ENCRYPTION; - private String cipherName = DEFAULT_CIPHER_NAME; - - private final Repository repository; - // TODO remove thread local session ; open a session each time - private ThreadLocal sessionThreadLocal = new ThreadLocal() { - - @Override - protected Session initialValue() { - return login(); - } - - }; - - // FIXME is it really still needed? - /** - * When setup is called the session has not yet been saved and we don't want to - * save it since there maybe other data which would be inconsistent. So we keep - * a reference to this node which will then be used (an reset to null) when - * handling the PBE callback. We keep one per thread in case multiple users are - * accessing the same instance of a keyring. - */ - // private ThreadLocal notYetSavedKeyring = new ThreadLocal() { - // - // @Override - // protected Node initialValue() { - // return null; - // } - // }; - - public JcrKeyring(Repository repository) { - this.repository = repository; - } - - private Session session() { - Session session = this.sessionThreadLocal.get(); - if (!session.isLive()) { - session = login(); - sessionThreadLocal.set(session); - } - return session; - } - - private Session login() { - try { - return repository.login(CmsConstants.HOME_WORKSPACE); - } catch (RepositoryException e) { - throw new JcrException("Cannot login key ring session", e); - } - } - - @Override - protected synchronized Boolean isSetup() { - Session session = null; - try { - // if (notYetSavedKeyring.get() != null) - // return true; - session = session(); - session.refresh(true); - Node userHome = CmsJcrUtils.getUserHome(session); - return userHome.hasNode(ARGEO_KEYRING); - } catch (RepositoryException e) { - throw new JcrException("Cannot check whether keyring is setup", e); - } finally { - JcrUtils.logoutQuietly(session); - } - } - - @Override - protected synchronized void setup(char[] password) { - Binary binary = null; - // InputStream in = null; - try { - session().refresh(true); - Node userHome = CmsJcrUtils.getUserHome(session()); - Node keyring; - if (userHome.hasNode(ARGEO_KEYRING)) { - throw new IllegalArgumentException("Keyring already set up"); - } else { - keyring = userHome.addNode(ARGEO_KEYRING); - } - keyring.addMixin(ArgeoTypes.ARGEO_PBE_SPEC); - - // deterministic salt and iteration count based on username - String username = session().getUserID(); - byte[] salt = new byte[8]; - byte[] usernameBytes = username.getBytes(StandardCharsets.UTF_8); - for (int i = 0; i < salt.length; i++) { - if (i < usernameBytes.length) - salt[i] = usernameBytes[i]; - else - salt[i] = 0; - } - try (InputStream in = new ByteArrayInputStream(salt);) { - binary = session().getValueFactory().createBinary(in); - keyring.setProperty(ARGEO_SALT, binary); - } catch (IOException e) { - throw new RuntimeException("Cannot set keyring salt", e); - } - - Integer iterationCount = username.length() * iterationCountFactor; - keyring.setProperty(ARGEO_ITERATION_COUNT, iterationCount); - - // default algo - // TODO check if algo and key length are available, use DES if not - keyring.setProperty(ARGEO_SECRET_KEY_FACTORY, secretKeyFactoryName); - keyring.setProperty(ARGEO_KEY_LENGTH, secretKeyLength); - keyring.setProperty(ARGEO_SECRET_KEY_ENCRYPTION, secretKeyEncryption); - keyring.setProperty(ARGEO_CIPHER, cipherName); - - keyring.getSession().save(); - - // encrypted password hash - // IOUtils.closeQuietly(in); - // JcrUtils.closeQuietly(binary); - // byte[] btPass = hash(password, salt, iterationCount); - // in = new ByteArrayInputStream(btPass); - // binary = session().getValueFactory().createBinary(in); - // keyring.setProperty(ARGEO_PASSWORD, binary); - - // notYetSavedKeyring.set(keyring); - } catch (RepositoryException e) { - throw new JcrException("Cannot setup keyring", e); - } finally { - JcrUtils.closeQuietly(binary); - // IOUtils.closeQuietly(in); - // JcrUtils.discardQuietly(session()); - } - } - - @Override - protected synchronized void handleKeySpecCallback(PBEKeySpecCallback pbeCallback) { - Session session = null; - try { - session = session(); - session.refresh(true); - Node userHome = CmsJcrUtils.getUserHome(session); - Node keyring; - if (userHome.hasNode(ARGEO_KEYRING)) - keyring = userHome.getNode(ARGEO_KEYRING); - // else if (notYetSavedKeyring.get() != null) - // keyring = notYetSavedKeyring.get(); - else - throw new IllegalStateException("Keyring not setup"); - - pbeCallback.set(keyring.getProperty(ARGEO_SECRET_KEY_FACTORY).getString(), - JcrUtils.getBinaryAsBytes(keyring.getProperty(ARGEO_SALT)), - (int) keyring.getProperty(ARGEO_ITERATION_COUNT).getLong(), - (int) keyring.getProperty(ARGEO_KEY_LENGTH).getLong(), - keyring.getProperty(ARGEO_SECRET_KEY_ENCRYPTION).getString()); - - // if (notYetSavedKeyring.get() != null) - // notYetSavedKeyring.remove(); - } catch (RepositoryException e) { - throw new JcrException("Cannot handle key spec callback", e); - } finally { - JcrUtils.logoutQuietly(session); - } - } - - /** The parent node must already exist at this path. */ - @Override - protected synchronized void encrypt(String path, InputStream unencrypted) { - // should be called first for lazy initialization - SecretKey secretKey = getSecretKey(null); - Cipher cipher = createCipher(); - - // Binary binary = null; - // InputStream in = null; - try { - session().refresh(true); - Node node; - if (!session().nodeExists(path)) { - String parentPath = JcrUtils.parentPath(path); - if (!session().nodeExists(parentPath)) - throw new IllegalStateException("No parent node of " + path); - Node parentNode = session().getNode(parentPath); - node = parentNode.addNode(JcrUtils.nodeNameFromPath(path)); - } else { - node = session().getNode(path); - } - encrypt(secretKey, cipher, node, unencrypted); - // node.addMixin(ArgeoTypes.ARGEO_ENCRYPTED); - // SecureRandom random = new SecureRandom(); - // byte[] iv = new byte[16]; - // random.nextBytes(iv); - // cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv)); - // JcrUtils.setBinaryAsBytes(node, ARGEO_IV, iv); - // - // try (InputStream in = new CipherInputStream(unencrypted, cipher);) { - // binary = session().getValueFactory().createBinary(in); - // node.setProperty(Property.JCR_DATA, binary); - // session().save(); - // } - } catch (RepositoryException e) { - throw new JcrException("Cannot encrypt", e); - } finally { - try { - unencrypted.close(); - } catch (IOException e) { - // silent - } - // IOUtils.closeQuietly(unencrypted); - // IOUtils.closeQuietly(in); - // JcrUtils.closeQuietly(binary); - JcrUtils.logoutQuietly(session()); - } - } - - protected synchronized void encrypt(SecretKey secretKey, Cipher cipher, Node node, InputStream unencrypted) { - try { - node.addMixin(ArgeoTypes.ARGEO_ENCRYPTED); - SecureRandom random = new SecureRandom(); - byte[] iv = new byte[16]; - random.nextBytes(iv); - cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(iv)); - JcrUtils.setBinaryAsBytes(node, ARGEO_IV, iv); - - Binary binary = null; - try (InputStream in = new CipherInputStream(unencrypted, cipher);) { - binary = session().getValueFactory().createBinary(in); - node.setProperty(Property.JCR_DATA, binary); - session().save(); - } finally { - JcrUtils.closeQuietly(binary); - } - } catch (RepositoryException e) { - throw new JcrException("Cannot encrypt", e); - } catch (GeneralSecurityException | IOException e) { - throw new RuntimeException("Cannot encrypt", e); - } - } - - @Override - protected synchronized InputStream decrypt(String path) { - Binary binary = null; - try { - session().refresh(true); - if (!session().nodeExists(path)) { - char[] password = ask(); - Reader reader = new CharArrayReader(password); - return new ByteArrayInputStream(IOUtils.toByteArray(reader, StandardCharsets.UTF_8)); - } else { - // should be called first for lazy initialisation - SecretKey secretKey = getSecretKey(null); - Cipher cipher = createCipher(); - Node node = session().getNode(path); - return decrypt(secretKey, cipher, node); - } - } catch (RepositoryException e) { - throw new JcrException("Cannot decrypt", e); - } catch (GeneralSecurityException | IOException e) { - throw new RuntimeException("Cannot decrypt", e); - } finally { - JcrUtils.closeQuietly(binary); - JcrUtils.logoutQuietly(session()); - } - } - - protected synchronized InputStream decrypt(SecretKey secretKey, Cipher cipher, Node node) - throws RepositoryException, GeneralSecurityException { - if (node.hasProperty(ARGEO_IV)) { - byte[] iv = JcrUtils.getBinaryAsBytes(node.getProperty(ARGEO_IV)); - cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv)); - } else { - cipher.init(Cipher.DECRYPT_MODE, secretKey); - } - - Binary binary = node.getProperty(Property.JCR_DATA).getBinary(); - InputStream encrypted = binary.getStream(); - return new CipherInputStream(encrypted, cipher); - } - - protected Cipher createCipher() { - try { - Node userHome = CmsJcrUtils.getUserHome(session()); - if (!userHome.hasNode(ARGEO_KEYRING)) - throw new IllegalArgumentException("Keyring not setup"); - Node keyring = userHome.getNode(ARGEO_KEYRING); - String cipherName = keyring.getProperty(ARGEO_CIPHER).getString(); - Provider securityProvider = getSecurityProvider(); - Cipher cipher; - if (securityProvider == null)// TODO use BC? - cipher = Cipher.getInstance(cipherName); - else - cipher = Cipher.getInstance(cipherName, securityProvider); - return cipher; - } catch (NoSuchAlgorithmException | NoSuchPaddingException e) { - throw new IllegalArgumentException("Cannot get cipher", e); - } catch (RepositoryException e) { - throw new JcrException("Cannot get cipher", e); - } finally { - - } - } - - public synchronized void changePassword(char[] oldPassword, char[] newPassword) { - // TODO make it XA compatible - SecretKey oldSecretKey = getSecretKey(oldPassword); - SecretKey newSecretKey = getSecretKey(newPassword); - Session session = session(); - try { - NodeIterator encryptedNodes = session.getWorkspace().getQueryManager() - .createQuery("select * from [argeo:encrypted]", Query.JCR_SQL2).execute().getNodes(); - while (encryptedNodes.hasNext()) { - Node node = encryptedNodes.nextNode(); - InputStream in = decrypt(oldSecretKey, createCipher(), node); - encrypt(newSecretKey, createCipher(), node, in); - if (log.isDebugEnabled()) - log.debug("Converted keyring encrypted value of " + node.getPath()); - } - } catch (GeneralSecurityException e) { - throw new RuntimeException("Cannot change JCR keyring password", e); - } catch (RepositoryException e) { - throw new JcrException("Cannot change JCR keyring password", e); - } finally { - JcrUtils.logoutQuietly(session); - } - } - - // public synchronized void setSession(Session session) { - // this.session = session; - // } - - public void setIterationCountFactor(Integer iterationCountFactor) { - this.iterationCountFactor = iterationCountFactor; - } - - public void setSecretKeyLength(Long keyLength) { - this.secretKeyLength = keyLength; - } - - public void setSecretKeyFactoryName(String secreteKeyFactoryName) { - this.secretKeyFactoryName = secreteKeyFactoryName; - } - - public void setSecretKeyEncryption(String secreteKeyEncryption) { - this.secretKeyEncryption = secreteKeyEncryption; - } - - public void setCipherName(String cipherName) { - this.cipherName = cipherName; - } - -} \ No newline at end of file diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JcrRepositoryFactory.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JcrRepositoryFactory.java deleted file mode 100644 index 342c1add7..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/JcrRepositoryFactory.java +++ /dev/null @@ -1,191 +0,0 @@ -package org.argeo.cms.jcr.internal; - -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Collection; -import java.util.HashMap; -import java.util.Map; - -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.RepositoryFactory; - -import org.apache.jackrabbit.jcr2dav.Jcr2davRepositoryFactory; -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.internal.jcr.RepoConf; -import org.argeo.cms.jcr.internal.osgi.CmsJcrActivator; -import org.osgi.framework.BundleContext; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.framework.ServiceReference; - -/** - * OSGi-aware Jackrabbit repository factory which can retrieve/publish - * {@link Repository} as OSGi services. - */ -public class JcrRepositoryFactory implements RepositoryFactory { - private final CmsLog log = CmsLog.getLog(getClass()); -// private final BundleContext bundleContext = FrameworkUtil.getBundle(getClass()).getBundleContext(); - - // private Resource fileRepositoryConfiguration = new ClassPathResource( - // "/org/argeo/cms/internal/kernel/repository-localfs.xml"); - - protected Repository getRepositoryByAlias(String alias) { - BundleContext bundleContext = CmsJcrActivator.getBundleContext(); - if (bundleContext != null) { - try { - Collection> srs = bundleContext.getServiceReferences(Repository.class, - "(" + CmsConstants.CN + "=" + alias + ")"); - if (srs.size() == 0) - throw new IllegalArgumentException("No repository with alias " + alias + " found in OSGi registry"); - else if (srs.size() > 1) - throw new IllegalArgumentException( - srs.size() + " repositories with alias " + alias + " found in OSGi registry"); - return bundleContext.getService(srs.iterator().next()); - } catch (InvalidSyntaxException e) { - throw new IllegalArgumentException("Cannot find repository with alias " + alias, e); - } - } else { - // TODO ability to filter static services - return null; - } - } - - // private void publish(String alias, Repository repository, Properties - // properties) { - // if (bundleContext != null) { - // // do not modify reference - // Hashtable props = new Hashtable(); - // props.putAll(props); - // props.put(JCR_REPOSITORY_ALIAS, alias); - // bundleContext.registerService(Repository.class.getName(), repository, - // props); - // } - // } - - @SuppressWarnings({ "rawtypes" }) - public Repository getRepository(Map parameters) throws RepositoryException { - // // check if can be found by alias - // Repository repository = super.getRepository(parameters); - // if (repository != null) - // return repository; - - // check if remote - Repository repository; - String uri = null; - if (parameters.containsKey(RepoConf.labeledUri.name())) - uri = parameters.get(CmsConstants.LABELED_URI).toString(); - else if (parameters.containsKey(KernelConstants.JACKRABBIT_REPOSITORY_URI)) - uri = parameters.get(KernelConstants.JACKRABBIT_REPOSITORY_URI).toString(); - - if (uri != null) { - if (uri.startsWith("http")) {// http, https - Object defaultWorkspace = parameters.get(RepoConf.defaultWorkspace.name()); - repository = createRemoteRepository(uri, defaultWorkspace != null ? defaultWorkspace.toString() : null); - } else if (uri.startsWith("file"))// http, https - repository = createFileRepository(uri, parameters); - else if (uri.startsWith("vm")) { - // log.warn("URI " + uri + " should have been managed by generic - // JCR repository factory"); - repository = getRepositoryByAlias(getAliasFromURI(uri)); - } else - throw new IllegalArgumentException("Unrecognized URI format " + uri); - - } - - else if (parameters.containsKey(CmsConstants.CN)) { - // Properties properties = new Properties(); - // properties.putAll(parameters); - String alias = parameters.get(CmsConstants.CN).toString(); - // publish(alias, repository, properties); - // log.info("Registered JCR repository under alias '" + alias + "' - // with properties " + properties); - repository = getRepositoryByAlias(alias); - } else - throw new IllegalArgumentException("Not enough information in " + parameters); - - if (repository == null) - throw new IllegalArgumentException("Repository not found " + parameters); - - return repository; - } - - protected Repository createRemoteRepository(String uri, String defaultWorkspace) throws RepositoryException { - Map params = new HashMap(); - params.put(KernelConstants.JACKRABBIT_REPOSITORY_URI, uri); - if (defaultWorkspace != null) - params.put(KernelConstants.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, defaultWorkspace); - Repository repository = new Jcr2davRepositoryFactory().getRepository(params); - if (repository == null) - throw new IllegalArgumentException("Remote Davex repository " + uri + " not found"); - log.info("Initialized remote Jackrabbit repository from uri " + uri); - return repository; - } - - @SuppressWarnings({ "rawtypes" }) - protected Repository createFileRepository(final String uri, Map parameters) throws RepositoryException { - throw new UnsupportedOperationException(); - // InputStream configurationIn = null; - // try { - // Properties vars = new Properties(); - // vars.putAll(parameters); - // String dirPath = uri.substring("file:".length()); - // File homeDir = new File(dirPath); - // if (homeDir.exists() && !homeDir.isDirectory()) - // throw new ArgeoJcrException("Repository home " + dirPath + " is not a - // directory"); - // if (!homeDir.exists()) - // homeDir.mkdirs(); - // configurationIn = fileRepositoryConfiguration.getInputStream(); - // vars.put(RepositoryConfigurationParser.REPOSITORY_HOME_VARIABLE, - // homeDir.getCanonicalPath()); - // RepositoryConfig repositoryConfig = RepositoryConfig.create(new - // InputSource(configurationIn), vars); - // - // // TransientRepository repository = new - // // TransientRepository(repositoryConfig); - // final RepositoryImpl repository = - // RepositoryImpl.create(repositoryConfig); - // Session session = repository.login(); - // // FIXME make it generic - // org.argeo.jcr.JcrUtils.addPrivilege(session, "/", "ROLE_ADMIN", - // "jcr:all"); - // org.argeo.jcr.JcrUtils.logoutQuietly(session); - // Runtime.getRuntime().addShutdownHook(new Thread("Clean JCR repository - // " + uri) { - // public void run() { - // repository.shutdown(); - // log.info("Destroyed repository " + uri); - // } - // }); - // log.info("Initialized file Jackrabbit repository from uri " + uri); - // return repository; - // } catch (Exception e) { - // throw new ArgeoJcrException("Cannot create repository " + uri, e); - // } finally { - // IOUtils.closeQuietly(configurationIn); - // } - } - - protected String getAliasFromURI(String uri) { - try { - URI uriObj = new URI(uri); - String alias = uriObj.getPath(); - if (alias.charAt(0) == '/') - alias = alias.substring(1); - if (alias.charAt(alias.length() - 1) == '/') - alias = alias.substring(0, alias.length() - 1); - return alias; - } catch (URISyntaxException e) { - throw new IllegalArgumentException("Cannot interpret URI " + uri, e); - } - } - - /** - * Called after the repository has been initialised. Does nothing by default. - */ - @SuppressWarnings("rawtypes") - protected void postInitialization(Repository repository, Map parameters) { - - } -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/KernelConstants.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/KernelConstants.java deleted file mode 100644 index 93f29fbe8..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/KernelConstants.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.argeo.cms.jcr.internal; - -import org.argeo.api.cms.CmsConstants; - -/** Internal CMS constants. */ -@Deprecated -public interface KernelConstants { - // Directories - String DIR_NODE = "node"; - String DIR_REPOS = "repos"; - String DIR_INDEXES = "indexes"; - String DIR_TRANSACTIONS = "transactions"; - - // Files - String DEPLOY_CONFIG_PATH = DIR_NODE + '/' + CmsConstants.DEPLOY_BASEDN + ".ldif"; - String DEFAULT_KEYSTORE_PATH = DIR_NODE + '/' + CmsConstants.NODE + ".p12"; - String DEFAULT_PEM_KEY_PATH = DIR_NODE + '/' + CmsConstants.NODE + ".key"; - String DEFAULT_PEM_CERT_PATH = DIR_NODE + '/' + CmsConstants.NODE + ".crt"; - String NODE_KEY_TAB_PATH = DIR_NODE + "/krb5.keytab"; - - // Security - String JAAS_CONFIG = "/org/argeo/cms/internal/kernel/jaas.cfg"; - String JAAS_CONFIG_IPA = "/org/argeo/cms/internal/kernel/jaas-ipa.cfg"; - - // Java - String JAAS_CONFIG_PROP = "java.security.auth.login.config"; - - // DEFAULTS JCR PATH - String DEFAULT_HOME_BASE_PATH = "/home"; - String DEFAULT_USERS_BASE_PATH = "/users"; - String DEFAULT_GROUPS_BASE_PATH = "/groups"; - - // KERBEROS - String DEFAULT_KERBEROS_SERVICE = "HTTP"; - - // HTTP client - String COOKIE_POLICY_BROWSER_COMPATIBILITY = "compatibility"; - - // RWT / RAP - // String PATH_WORKBENCH = "/ui"; - // String PATH_WORKBENCH_PUBLIC = PATH_WORKBENCH + "/public"; - - String JETTY_FACTORY_PID = "org.eclipse.equinox.http.jetty.config"; - String WHITEBOARD_PATTERN_PROP = "osgi.http.whiteboard.servlet.pattern"; - // default Jetty server configured via JettyConfigurator - String DEFAULT_JETTY_SERVER = "default"; - String CMS_JETTY_CUSTOMIZER_CLASS = "org.argeo.equinox.jetty.CmsJettyCustomizer"; - - // avoid dependencies - String CONTEXT_NAME_PROP = "contextName"; - String JACKRABBIT_REPOSITORY_URI = "org.apache.jackrabbit.repository.uri"; - String JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = "org.apache.jackrabbit.spi2davex.WorkspaceNameDefault"; -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/KernelUtils.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/KernelUtils.java deleted file mode 100644 index edfe87a03..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/KernelUtils.java +++ /dev/null @@ -1,262 +0,0 @@ -package org.argeo.cms.jcr.internal; - -import java.io.File; -import java.io.IOException; -import java.io.PrintStream; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.security.PrivilegedAction; -import java.security.URIParameter; -import java.util.Dictionary; -import java.util.Hashtable; -import java.util.Properties; -import java.util.TreeMap; -import java.util.TreeSet; - -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.security.auth.Subject; -import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; - -import org.argeo.api.cms.CmsAuth; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.jcr.internal.osgi.CmsJcrActivator; -import org.argeo.cms.osgi.DataModelNamespace; -import org.osgi.framework.BundleContext; -import org.osgi.util.tracker.ServiceTracker; - -/** Package utilities */ -class KernelUtils implements KernelConstants { - final static String OSGI_INSTANCE_AREA = "osgi.instance.area"; - final static String OSGI_CONFIGURATION_AREA = "osgi.configuration.area"; - - static void setJaasConfiguration(URL jaasConfigurationUrl) { - try { - URIParameter uriParameter = new URIParameter(jaasConfigurationUrl.toURI()); - javax.security.auth.login.Configuration jaasConfiguration = javax.security.auth.login.Configuration - .getInstance("JavaLoginConfig", uriParameter); - javax.security.auth.login.Configuration.setConfiguration(jaasConfiguration); - } catch (Exception e) { - throw new IllegalArgumentException("Cannot set configuration " + jaasConfigurationUrl, e); - } - } - - static Dictionary asDictionary(Properties props) { - Hashtable hashtable = new Hashtable(); - for (Object key : props.keySet()) { - hashtable.put(key.toString(), props.get(key)); - } - return hashtable; - } - - static Dictionary asDictionary(ClassLoader cl, String resource) { - Properties props = new Properties(); - try { - props.load(cl.getResourceAsStream(resource)); - } catch (IOException e) { - throw new IllegalArgumentException("Cannot load " + resource + " from classpath", e); - } - return asDictionary(props); - } - - static File getExecutionDir(String relativePath) { - File executionDir = new File(getFrameworkProp("user.dir")); - if (relativePath == null) - return executionDir; - try { - return new File(executionDir, relativePath).getCanonicalFile(); - } catch (IOException e) { - throw new IllegalArgumentException("Cannot get canonical file", e); - } - } - - static File getOsgiInstanceDir() { - return new File(getBundleContext().getProperty(OSGI_INSTANCE_AREA).substring("file:".length())) - .getAbsoluteFile(); - } - - static Path getOsgiInstancePath(String relativePath) { - return Paths.get(getOsgiInstanceUri(relativePath)); - } - - static URI getOsgiInstanceUri(String relativePath) { - String osgiInstanceBaseUri = getFrameworkProp(OSGI_INSTANCE_AREA); - if (osgiInstanceBaseUri != null) - return safeUri(osgiInstanceBaseUri + (relativePath != null ? relativePath : "")); - else - return Paths.get(System.getProperty("user.dir")).toUri(); - } - - static File getOsgiConfigurationFile(String relativePath) { - try { - return new File(new URI(getBundleContext().getProperty(OSGI_CONFIGURATION_AREA) + relativePath)) - .getCanonicalFile(); - } catch (Exception e) { - throw new IllegalArgumentException("Cannot get configuration file for " + relativePath, e); - } - } - - static String getFrameworkProp(String key, String def) { - BundleContext bundleContext = CmsJcrActivator.getBundleContext(); - String value; - if (bundleContext != null) - value = bundleContext.getProperty(key); - else - value = System.getProperty(key); - if (value == null) - return def; - return value; - } - - static String getFrameworkProp(String key) { - return getFrameworkProp(key, null); - } - - // Security - // static Subject anonymousLogin() { - // Subject subject = new Subject(); - // LoginContext lc; - // try { - // lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, subject); - // lc.login(); - // return subject; - // } catch (LoginException e) { - // throw new CmsException("Cannot login as anonymous", e); - // } - // } - - static void logFrameworkProperties(CmsLog log) { - BundleContext bc = getBundleContext(); - for (Object sysProp : new TreeSet(System.getProperties().keySet())) { - log.debug(sysProp + "=" + bc.getProperty(sysProp.toString())); - } - // String[] keys = { Constants.FRAMEWORK_STORAGE, - // Constants.FRAMEWORK_OS_NAME, Constants.FRAMEWORK_OS_VERSION, - // Constants.FRAMEWORK_PROCESSOR, Constants.FRAMEWORK_SECURITY, - // Constants.FRAMEWORK_TRUST_REPOSITORIES, - // Constants.FRAMEWORK_WINDOWSYSTEM, Constants.FRAMEWORK_VENDOR, - // Constants.FRAMEWORK_VERSION, Constants.FRAMEWORK_STORAGE_CLEAN, - // Constants.FRAMEWORK_LANGUAGE, Constants.FRAMEWORK_UUID }; - // for (String key : keys) - // log.debug(key + "=" + bc.getProperty(key)); - } - - static void printSystemProperties(PrintStream out) { - TreeMap display = new TreeMap<>(); - for (Object key : System.getProperties().keySet()) - display.put(key.toString(), System.getProperty(key.toString())); - for (String key : display.keySet()) - out.println(key + "=" + display.get(key)); - } - - static Session openAdminSession(Repository repository) { - return openAdminSession(repository, null); - } - - static Session openAdminSession(final Repository repository, final String workspaceName) { - LoginContext loginContext = loginAsDataAdmin(); - return Subject.doAs(loginContext.getSubject(), new PrivilegedAction() { - - @Override - public Session run() { - try { - return repository.login(workspaceName); - } catch (RepositoryException e) { - throw new IllegalStateException("Cannot open admin session", e); - } finally { - try { - loginContext.logout(); - } catch (LoginException e) { - throw new IllegalStateException(e); - } - } - } - - }); - } - - static LoginContext loginAsDataAdmin() { - ClassLoader currentCl = Thread.currentThread().getContextClassLoader(); - Thread.currentThread().setContextClassLoader(KernelUtils.class.getClassLoader()); - LoginContext loginContext; - try { - loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_DATA_ADMIN); - loginContext.login(); - } catch (LoginException e1) { - throw new IllegalStateException("Could not login as data admin", e1); - } finally { - Thread.currentThread().setContextClassLoader(currentCl); - } - return loginContext; - } - - static void doAsDataAdmin(Runnable action) { - LoginContext loginContext = loginAsDataAdmin(); - Subject.doAs(loginContext.getSubject(), new PrivilegedAction() { - - @Override - public Void run() { - try { - action.run(); - return null; - } finally { - try { - loginContext.logout(); - } catch (LoginException e) { - throw new IllegalStateException(e); - } - } - } - - }); - } - - static void asyncOpen(ServiceTracker st) { - Runnable run = new Runnable() { - - @Override - public void run() { - st.open(); - } - }; -// Activator.getInternalExecutorService().execute(run); - new Thread(run, "Open service tracker " + st).start(); - } - - static BundleContext getBundleContext() { - return CmsJcrActivator.getBundleContext(); - } - - static boolean asBoolean(String value) { - if (value == null) - return false; - switch (value) { - case "true": - return true; - case "false": - return false; - default: - throw new IllegalArgumentException( - "Unsupported value for attribute " + DataModelNamespace.ABSTRACT + ": " + value); - } - } - - private static URI safeUri(String uri) { - if (uri == null) - throw new IllegalArgumentException("URI cannot be null"); - try { - return new URI(uri); - } catch (URISyntaxException e) { - throw new IllegalArgumentException("Badly formatted URI " + uri, e); - } - } - - private KernelUtils() { - - } -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/LocalRepository.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/LocalRepository.java deleted file mode 100644 index 0bac94cc0..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/LocalRepository.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.argeo.cms.jcr.internal; - -import javax.jcr.Repository; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.jcr.JcrRepositoryWrapper; - -class LocalRepository extends JcrRepositoryWrapper { - private final String cn; - - public LocalRepository(Repository repository, String cn) { - super(repository); - this.cn = cn; - // Map attrs = dataModelCapability.getAttributes(); - // cn = (String) attrs.get(DataModelNamespace.NAME); - putDescriptor(CmsConstants.CN, cn); - } - - String getCn() { - return cn; - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/NodeKeyRing.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/NodeKeyRing.java deleted file mode 100644 index 9cd1f7269..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/NodeKeyRing.java +++ /dev/null @@ -1,19 +0,0 @@ -package org.argeo.cms.jcr.internal; - -import java.util.Dictionary; - -import javax.jcr.Repository; - -import org.osgi.service.cm.ConfigurationException; -import org.osgi.service.cm.ManagedService; - -class NodeKeyRing extends JcrKeyring implements ManagedService{ - - public NodeKeyRing(Repository repository) { - super(repository); - } - - @Override - public void updated(Dictionary properties) throws ConfigurationException { - } -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/RepositoryContextsFactory.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/RepositoryContextsFactory.java deleted file mode 100644 index e05a0023e..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/RepositoryContextsFactory.java +++ /dev/null @@ -1,143 +0,0 @@ -package org.argeo.cms.jcr.internal; - -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.util.Dictionary; -import java.util.HashMap; -import java.util.Map; - -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.RepositoryFactory; - -import org.apache.jackrabbit.core.RepositoryContext; -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.internal.jcr.RepoConf; -import org.argeo.cms.internal.jcr.RepositoryBuilder; -import org.argeo.cms.jcr.internal.osgi.CmsJcrActivator; -import org.argeo.util.LangUtils; -import org.osgi.framework.Constants; -import org.osgi.service.cm.ConfigurationException; -import org.osgi.service.cm.ManagedServiceFactory; - -/** A {@link ManagedServiceFactory} creating or referencing JCR repositories. */ -public class RepositoryContextsFactory implements ManagedServiceFactory { - private final static CmsLog log = CmsLog.getLog(RepositoryContextsFactory.class); -// private final BundleContext bc = FrameworkUtil.getBundle(RepositoryServiceFactory.class).getBundleContext(); - - private Map repositories = new HashMap(); - private Map pidToCn = new HashMap(); - - public void init() { - - } - - public void destroy() { - for (String pid : repositories.keySet()) { - try { - RepositoryContext repositoryContext = repositories.get(pid); - // Must start in another thread otherwise shutdown is interrupted - // TODO use an executor? - new Thread(() -> { - repositoryContext.getRepository().shutdown(); - if (log.isDebugEnabled()) - log.debug("Shut down repository " + pid - + (pidToCn.containsKey(pid) ? " (" + pidToCn.get(pid) + ")" : "")); - }, "Shutdown JCR repository " + pid).start(); - } catch (Exception e) { - log.error("Error when shutting down Jackrabbit repository " + pid, e); - } - } - } - - @Override - public String getName() { - return "Jackrabbit repository service factory"; - } - - @Override - public void updated(String pid, Dictionary properties) throws ConfigurationException { - if (repositories.containsKey(pid)) - throw new IllegalArgumentException("Already a repository registered for " + pid); - - if (properties == null) - return; - - Object cn = properties.get(CmsConstants.CN); - if (cn != null) - for (String otherPid : pidToCn.keySet()) { - Object o = pidToCn.get(otherPid); - if (cn.equals(o)) { - RepositoryContext repositoryContext = repositories.remove(otherPid); - repositories.put(pid, repositoryContext); - if (log.isDebugEnabled()) - log.debug("Ignoring update of Jackrabbit repository " + cn); - // FIXME perform a proper update (also of the OSGi service) - return; - } - } - - try { - Object labeledUri = properties.get(RepoConf.labeledUri.name()); - if (labeledUri == null) { - RepositoryBuilder repositoryBuilder = new RepositoryBuilder(); - RepositoryContext repositoryContext = repositoryBuilder.createRepositoryContext(properties); - repositories.put(pid, repositoryContext); - Dictionary props = LangUtils.dict(Constants.SERVICE_PID, pid); - // props.put(ArgeoJcrConstants.JCR_REPOSITORY_URI, - // properties.get(RepoConf.labeledUri.name())); - if (cn != null) { - props.put(CmsConstants.CN, cn); - // props.put(NodeConstants.JCR_REPOSITORY_ALIAS, cn); - pidToCn.put(pid, cn); - } - CmsJcrActivator.registerService(RepositoryContext.class, repositoryContext, props); - } else { - Object defaultWorkspace = properties.get(RepoConf.defaultWorkspace.name()); - if (defaultWorkspace == null) - defaultWorkspace = RepoConf.defaultWorkspace.getDefault(); - URI uri = new URI(labeledUri.toString()); -// RepositoryFactory repositoryFactory = bc -// .getService(bc.getServiceReference(RepositoryFactory.class)); - RepositoryFactory repositoryFactory = CmsJcrActivator.getService(RepositoryFactory.class); - Map parameters = new HashMap(); - parameters.put(RepoConf.labeledUri.name(), uri.toString()); - parameters.put(RepoConf.defaultWorkspace.name(), defaultWorkspace.toString()); - Repository repository = repositoryFactory.getRepository(parameters); - // Repository repository = NodeUtils.getRepositoryByUri(repositoryFactory, - // uri.toString()); - Dictionary props = LangUtils.dict(Constants.SERVICE_PID, pid); - props.put(RepoConf.labeledUri.name(), - new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(), uri.getPath(), null, null) - .toString()); - if (cn != null) { - props.put(CmsConstants.CN, cn); - // props.put(NodeConstants.JCR_REPOSITORY_ALIAS, cn); - pidToCn.put(pid, cn); - } - CmsJcrActivator.registerService(Repository.class, repository, props); - - // home - if (cn.equals(CmsConstants.NODE_REPOSITORY)) { - Dictionary homeProps = LangUtils.dict(CmsConstants.CN, CmsConstants.EGO_REPOSITORY); - EgoRepository homeRepository = new EgoRepository(repository, true); - CmsJcrActivator.registerService(Repository.class, homeRepository, homeProps); - } - } - } catch (RepositoryException | URISyntaxException | IOException e) { - throw new IllegalStateException("Cannot create Jackrabbit repository " + pid, e); - } - - } - - @Override - public void deleted(String pid) { - RepositoryContext repositoryContext = repositories.remove(pid); - repositoryContext.getRepository().shutdown(); - if (log.isDebugEnabled()) - log.debug("Deleted repository " + pid); - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/StatisticsThread.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/StatisticsThread.java deleted file mode 100644 index 5a2cd5b7b..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/StatisticsThread.java +++ /dev/null @@ -1,123 +0,0 @@ -package org.argeo.cms.jcr.internal; - -import java.io.File; -import java.lang.management.ManagementFactory; - -import org.apache.jackrabbit.api.stats.RepositoryStatistics; -import org.apache.jackrabbit.stats.RepositoryStatisticsImpl; -import org.argeo.api.cms.CmsLog; - -/** - * Background thread started by the kernel, which gather statistics and - * monitor/control other processes. - */ -public class StatisticsThread extends Thread { - private final static CmsLog log = CmsLog.getLog(StatisticsThread.class); - - private RepositoryStatisticsImpl repoStats; - - /** The smallest period of operation, in ms */ - private final long PERIOD = 60 * 1000l; - /** One ms in ns */ - private final static long m = 1000l * 1000l; - private final static long M = 1024l * 1024l; - - private boolean running = true; - - private CmsLog kernelStatsLog = CmsLog.getLog("argeo.stats.kernel"); - private CmsLog nodeStatsLog = CmsLog.getLog("argeo.stats.node"); - - @SuppressWarnings("unused") - private long cycle = 0l; - - public StatisticsThread(String name) { - super(name); - } - - private void doSmallestPeriod() { - // Clean expired sessions - // FIXME re-enable it in CMS - //CmsSessionImpl.closeInvalidSessions(); - - if (kernelStatsLog.isDebugEnabled()) { - StringBuilder line = new StringBuilder(64); - line.append("§\t"); - long freeMem = Runtime.getRuntime().freeMemory() / M; - long totalMem = Runtime.getRuntime().totalMemory() / M; - long maxMem = Runtime.getRuntime().maxMemory() / M; - double loadAvg = ManagementFactory.getOperatingSystemMXBean().getSystemLoadAverage(); - // in min - boolean min = true; - long uptime = ManagementFactory.getRuntimeMXBean().getUptime() / (1000 * 60); - if (uptime > 24 * 60) { - min = false; - uptime = uptime / 60; - } - line.append(uptime).append(min ? " min" : " h").append('\t'); - line.append(loadAvg).append('\t').append(maxMem).append('\t').append(totalMem).append('\t').append(freeMem) - .append('\t'); - kernelStatsLog.debug(line); - } - - if (nodeStatsLog.isDebugEnabled()) { - File dataDir = KernelUtils.getOsgiInstanceDir(); - long freeSpace = dataDir.getUsableSpace() / M; - // File currentRoot = null; - // for (File root : File.listRoots()) { - // String rootPath = root.getAbsolutePath(); - // if (dataDir.getAbsolutePath().startsWith(rootPath)) { - // if (currentRoot == null - // || (rootPath.length() > currentRoot.getPath() - // .length())) { - // currentRoot = root; - // } - // } - // } - // long totalSpace = currentRoot.getTotalSpace(); - StringBuilder line = new StringBuilder(128); - line.append("§\t").append(freeSpace).append(" MB left in " + dataDir); - line.append('\n'); - if (repoStats != null) - for (RepositoryStatistics.Type type : RepositoryStatistics.Type.values()) { - long[] vals = repoStats.getTimeSeries(type).getValuePerMinute(); - long val = vals[vals.length - 1]; - line.append(type.name()).append('\t').append(val).append('\n'); - } - nodeStatsLog.debug(line); - } - } - - @Override - public void run() { - if (log.isTraceEnabled()) - log.trace("Kernel thread started."); - final long periodNs = PERIOD * m; - while (running) { - long beginNs = System.nanoTime(); - doSmallestPeriod(); - - long waitNs = periodNs - (System.nanoTime() - beginNs); - if (waitNs < 0) - continue; - // wait - try { - sleep(waitNs / m, (int) (waitNs % m)); - } catch (InterruptedException e) { - // silent - } - cycle++; - } - } - - public synchronized void destroyAndJoin() { - running = false; - notifyAll(); -// interrupt(); -// try { -// join(PERIOD * 2); -// } catch (InterruptedException e) { -// // throw new CmsException("Kernel thread destruction was interrupted"); -// log.error("Kernel thread destruction was interrupted", e); -// } - } -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/osgi/CmsJcrActivator.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/osgi/CmsJcrActivator.java deleted file mode 100644 index 57860d84f..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/osgi/CmsJcrActivator.java +++ /dev/null @@ -1,91 +0,0 @@ -package org.argeo.cms.jcr.internal.osgi; - -import java.util.Dictionary; - -import org.argeo.cms.jcr.internal.StatisticsThread; -import org.osgi.framework.BundleActivator; -import org.osgi.framework.BundleContext; - -public class CmsJcrActivator implements BundleActivator { - private static BundleContext bundleContext; - -// private List stopHooks = new ArrayList<>(); - private StatisticsThread kernelThread; - -// private JackrabbitRepositoryContextsFactory repositoryServiceFactory; -// private CmsJcrDeployment jcrDeployment; - - @Override - public void start(BundleContext context) throws Exception { - bundleContext = context; - - // kernel thread - kernelThread = new StatisticsThread("Kernel Thread"); - kernelThread.setContextClassLoader(getClass().getClassLoader()); - kernelThread.start(); - - // JCR -// repositoryServiceFactory = new JackrabbitRepositoryContextsFactory(); -//// stopHooks.add(() -> repositoryServiceFactory.shutdown()); -// registerService(ManagedServiceFactory.class, repositoryServiceFactory, -// LangUtils.dict(Constants.SERVICE_PID, CmsConstants.NODE_REPOS_FACTORY_PID)); - -// JcrRepositoryFactory repositoryFactory = new JcrRepositoryFactory(); -// registerService(RepositoryFactory.class, repositoryFactory, null); - - // File System -// CmsJcrFsProvider cmsFsProvider = new CmsJcrFsProvider(); -// ServiceLoader fspSl = ServiceLoader.load(FileSystemProvider.class); -// for (FileSystemProvider fsp : fspSl) { -// log.debug("FileSystemProvider " + fsp); -// if (fsp instanceof CmsFsProvider) { -// cmsFsProvider = (CmsFsProvider) fsp; -// } -// } -// for (FileSystemProvider fsp : FileSystemProvider.installedProviders()) { -// log.debug("Installed FileSystemProvider " + fsp); -// } -// registerService(FileSystemProvider.class, cmsFsProvider, -// LangUtils.dict(Constants.SERVICE_PID, CmsConstants.NODE_FS_PROVIDER_PID)); - -// jcrDeployment = new CmsJcrDeployment(); -// jcrDeployment.init(); - } - - @Override - public void stop(BundleContext context) throws Exception { -// if (jcrDeployment != null) -// jcrDeployment.destroy(); - -// if (repositoryServiceFactory != null) -// repositoryServiceFactory.shutdown(); - - if (kernelThread != null) - kernelThread.destroyAndJoin(); - - bundleContext = null; - } - - @Deprecated - public static void registerService(Class clss, T service, Dictionary properties) { - if (bundleContext != null) { - bundleContext.registerService(clss, service, properties); - } - - } - - @Deprecated - public static BundleContext getBundleContext() { - return bundleContext; - } - - @Deprecated - public static T getService(Class clss) { - if (bundleContext != null) { - return bundleContext.getService(bundleContext.getServiceReference(clss)); - } else { - return null; - } - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsRemotingServlet.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsRemotingServlet.java deleted file mode 100644 index fa3f87f67..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsRemotingServlet.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.argeo.cms.jcr.internal.servlet; - -import java.util.Map; - -import javax.jcr.Repository; - -import org.apache.jackrabbit.server.SessionProvider; -import org.apache.jackrabbit.server.remoting.davex.JcrRemotingServlet; -import org.argeo.api.cms.CmsConstants; - -/** A {@link JcrRemotingServlet} based on {@link CmsSessionProvider}. */ -public class CmsRemotingServlet extends JcrRemotingServlet { - private static final long serialVersionUID = 6459455509684213633L; - private Repository repository; - private SessionProvider sessionProvider; - - public CmsRemotingServlet() { - } - - public CmsRemotingServlet(String alias, Repository repository) { - this.repository = repository; - this.sessionProvider = new CmsSessionProvider(alias); - } - - @Override - public Repository getRepository() { - return repository; - } - - public void setRepository(Repository repository, Map properties) { - this.repository = repository; - String alias = properties.get(CmsConstants.CN); - if (alias != null) - sessionProvider = new CmsSessionProvider(alias); - else - throw new IllegalArgumentException("Only aliased repositories are supported"); - } - - @Override - protected SessionProvider getSessionProvider() { - return sessionProvider; - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsSessionProvider.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsSessionProvider.java deleted file mode 100644 index 0f27fd005..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsSessionProvider.java +++ /dev/null @@ -1,174 +0,0 @@ -package org.argeo.cms.jcr.internal.servlet; - -import java.io.Serializable; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; -import java.util.HashMap; -import java.util.HashSet; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Set; - -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.security.auth.Subject; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServletRequest; - -import org.apache.jackrabbit.server.SessionProvider; -import org.argeo.api.cms.CmsSession; -import org.argeo.api.cms.CmsLog; -import org.argeo.api.cms.CmsConstants; -import org.argeo.jcr.JcrUtils; - -/** - * Implements an open session in view patter: a new JCR session is created for - * each request - */ -public class CmsSessionProvider implements SessionProvider, Serializable { - private static final long serialVersionUID = -1358136599534938466L; - - private final static CmsLog log = CmsLog.getLog(CmsSessionProvider.class); - - private final String alias; - - private LinkedHashMap cmsSessions = new LinkedHashMap<>(); - - public CmsSessionProvider(String alias) { - this.alias = alias; - } - - public Session getSession(HttpServletRequest request, Repository rep, String workspace) - throws javax.jcr.LoginException, ServletException, RepositoryException { - - // a client is scanning parent URLs. -// if (workspace == null) -// return null; - -// CmsSessionImpl cmsSession = WebCmsSessionImpl.getCmsSession(request); - // FIXME retrieve CMS session - CmsSession cmsSession = null; - if (log.isTraceEnabled()) { - log.trace("Get JCR session from " + cmsSession); - } - if (cmsSession == null) - throw new IllegalStateException("Cannot find a session for request " + request.getRequestURI()); - CmsDataSession cmsDataSession = new CmsDataSession(cmsSession); - Session session = cmsDataSession.getDataSession(alias, workspace, rep); - cmsSessions.put(session, cmsDataSession); - return session; - } - - public void releaseSession(Session session) { -// JcrUtils.logoutQuietly(session); - if (cmsSessions.containsKey(session)) { - CmsDataSession cmsDataSession = cmsSessions.get(session); - cmsDataSession.releaseDataSession(alias, session); - } else { - log.warn("JCR session " + session + " not found in CMS session list. Logging it out..."); - JcrUtils.logoutQuietly(session); - } - } - - static class CmsDataSession { - private CmsSession cmsSession; - - private Map dataSessions = new HashMap<>(); - private Set dataSessionsInUse = new HashSet<>(); - private Set additionalDataSessions = new HashSet<>(); - - private CmsDataSession(CmsSession cmsSession) { - this.cmsSession = cmsSession; - } - - public Session newDataSession(String cn, String workspace, Repository repository) { - checkValid(); - return login(repository, workspace); - } - - public synchronized Session getDataSession(String cn, String workspace, Repository repository) { - checkValid(); - // FIXME make it more robust - if (workspace == null) - workspace = CmsConstants.SYS_WORKSPACE; - String path = cn + '/' + workspace; - if (dataSessionsInUse.contains(path)) { - try { - wait(1000); - if (dataSessionsInUse.contains(path)) { - Session session = login(repository, workspace); - additionalDataSessions.add(session); - if (log.isTraceEnabled()) - log.trace("Additional data session " + path + " for " + cmsSession.getUserDn()); - return session; - } - } catch (InterruptedException e) { - // silent - } - } - - Session session = null; - if (dataSessions.containsKey(path)) { - session = dataSessions.get(path); - } else { - session = login(repository, workspace); - dataSessions.put(path, session); - if (log.isTraceEnabled()) - log.trace("New data session " + path + " for " + cmsSession.getUserDn()); - } - dataSessionsInUse.add(path); - return session; - } - - private Session login(Repository repository, String workspace) { - try { - return Subject.doAs(cmsSession.getSubject(), new PrivilegedExceptionAction() { - @Override - public Session run() throws Exception { - return repository.login(workspace); - } - }); - } catch (PrivilegedActionException e) { - throw new IllegalStateException("Cannot log in " + cmsSession.getUserDn() + " to JCR", e); - } - } - - public synchronized void releaseDataSession(String cn, Session session) { - if (additionalDataSessions.contains(session)) { - JcrUtils.logoutQuietly(session); - additionalDataSessions.remove(session); - if (log.isTraceEnabled()) - log.trace("Remove additional data session " + session); - return; - } - String path = cn + '/' + session.getWorkspace().getName(); - if (!dataSessionsInUse.contains(path)) - log.warn("Data session " + path + " was not in use for " + cmsSession.getUserDn()); - dataSessionsInUse.remove(path); - Session registeredSession = dataSessions.get(path); - if (session != registeredSession) - log.warn("Data session " + path + " not consistent for " + cmsSession.getUserDn()); - if (log.isTraceEnabled()) - log.trace("Released data session " + session + " for " + path); - notifyAll(); - } - - private void checkValid() { - if (!cmsSession.isValid()) - throw new IllegalStateException( - "CMS session " + cmsSession.getUuid() + " is not valid since " + cmsSession.getEnd()); - } - - private void close() { - // FIXME class this when CMS session is closed - synchronized (this) { - // TODO check data session in use ? - for (String path : dataSessions.keySet()) - JcrUtils.logoutQuietly(dataSessions.get(path)); - for (Session session : additionalDataSessions) - JcrUtils.logoutQuietly(session); - } - } - } -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsWebDavServlet.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsWebDavServlet.java deleted file mode 100644 index 0f0858f51..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/CmsWebDavServlet.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.argeo.cms.jcr.internal.servlet; - -import java.util.Map; - -import javax.jcr.Repository; - -import org.apache.jackrabbit.webdav.simple.SimpleWebdavServlet; -import org.argeo.api.cms.CmsConstants; - -/** A {@link SimpleWebdavServlet} based on {@link CmsSessionProvider}. */ -public class CmsWebDavServlet extends SimpleWebdavServlet { - private static final long serialVersionUID = 7485800288686328063L; - private Repository repository; - - public CmsWebDavServlet() { - } - - public CmsWebDavServlet(String alias, Repository repository) { - this.repository = repository; - setSessionProvider(new CmsSessionProvider(alias)); - } - - @Override - public Repository getRepository() { - return repository; - } - - public void setRepository(Repository repository, Map properties) { - this.repository = repository; - String alias = properties.get(CmsConstants.CN); - if (alias != null) - setSessionProvider(new CmsSessionProvider(alias)); - else - throw new IllegalArgumentException("Only aliased repositories are supported"); - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/DataServletContext.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/DataServletContext.java deleted file mode 100644 index 2f60e97d9..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/DataServletContext.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.argeo.cms.jcr.internal.servlet; - -import org.argeo.cms.servlet.CmsServletContext; - -/** Internal subclass, so that config resources can be loaded from our bundle. */ -public class DataServletContext extends CmsServletContext { - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/JcrHttpUtils.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/JcrHttpUtils.java deleted file mode 100644 index 11e903db8..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/JcrHttpUtils.java +++ /dev/null @@ -1,73 +0,0 @@ -package org.argeo.cms.jcr.internal.servlet; - -import java.util.Enumeration; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.argeo.api.cms.CmsLog; - -public class JcrHttpUtils { - public final static String HEADER_AUTHORIZATION = "Authorization"; - public final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate"; - - public final static String DEFAULT_PROTECTED_HANDLERS = "/org/argeo/cms/jcr/internal/servlet/protectedHandlers.xml"; - public final static String WEBDAV_CONFIG = "/org/argeo/cms/jcr/internal/servlet/webdav-config.xml"; - - static boolean isBrowser(String userAgent) { - return userAgent.contains("webkit") || userAgent.contains("gecko") || userAgent.contains("firefox") - || userAgent.contains("msie") || userAgent.contains("chrome") || userAgent.contains("chromium") - || userAgent.contains("opera") || userAgent.contains("browser"); - } - - public static void logResponseHeaders(CmsLog log, HttpServletResponse response) { - if (!log.isDebugEnabled()) - return; - for (String headerName : response.getHeaderNames()) { - Object headerValue = response.getHeader(headerName); - log.debug(headerName + ": " + headerValue); - } - } - - public static void logRequestHeaders(CmsLog log, HttpServletRequest request) { - if (!log.isDebugEnabled()) - return; - for (Enumeration headerNames = request.getHeaderNames(); headerNames.hasMoreElements();) { - String headerName = headerNames.nextElement(); - Object headerValue = request.getHeader(headerName); - log.debug(headerName + ": " + headerValue); - } - log.debug(request.getRequestURI() + "\n"); - } - - public static void logRequest(CmsLog log, HttpServletRequest request) { - log.debug("contextPath=" + request.getContextPath()); - log.debug("servletPath=" + request.getServletPath()); - log.debug("requestURI=" + request.getRequestURI()); - log.debug("queryString=" + request.getQueryString()); - StringBuilder buf = new StringBuilder(); - // headers - Enumeration en = request.getHeaderNames(); - while (en.hasMoreElements()) { - String header = en.nextElement(); - Enumeration values = request.getHeaders(header); - while (values.hasMoreElements()) - buf.append(" " + header + ": " + values.nextElement()); - buf.append('\n'); - } - - // attributed - Enumeration an = request.getAttributeNames(); - while (an.hasMoreElements()) { - String attr = an.nextElement(); - Object value = request.getAttribute(attr); - buf.append(" " + attr + ": " + value); - buf.append('\n'); - } - log.debug("\n" + buf); - } - - private JcrHttpUtils() { - - } -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/JcrServletContext.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/JcrServletContext.java deleted file mode 100644 index 21046f34e..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/JcrServletContext.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.argeo.cms.jcr.internal.servlet; - -import org.argeo.cms.servlet.PrivateWwwAuthServletContext; - -/** Internal subclass, so that config resources can be loaded from our bundle. */ -public class JcrServletContext extends PrivateWwwAuthServletContext { - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/LinkServlet.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/LinkServlet.java deleted file mode 100644 index 62cdc5f6b..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/LinkServlet.java +++ /dev/null @@ -1,259 +0,0 @@ -package org.argeo.cms.jcr.internal.servlet; - -import static javax.jcr.Property.JCR_DESCRIPTION; -import static javax.jcr.Property.JCR_LAST_MODIFIED; -import static javax.jcr.Property.JCR_TITLE; - -import java.io.IOException; -import java.io.PrintWriter; -import java.net.MalformedURLException; -import java.net.URL; -import java.security.PrivilegedExceptionAction; -import java.util.Calendar; -import java.util.Collection; - -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.security.auth.Subject; -import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.argeo.api.cms.CmsAuth; -import org.argeo.api.cms.CmsConstants; -import org.argeo.cms.CmsException; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.jcr.JcrUtils; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; -import org.osgi.framework.ServiceReference; - -public class LinkServlet extends HttpServlet { - private final BundleContext bc = FrameworkUtil.getBundle(getClass()).getBundleContext(); - - private static final long serialVersionUID = 3749990143146845708L; - - @Override - protected void service(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - String path = request.getPathInfo(); - String userAgent = request.getHeader("User-Agent").toLowerCase(); - boolean isBot = false; - // boolean isCompatibleBrowser = false; - if (userAgent.contains("bot") || userAgent.contains("facebook") || userAgent.contains("twitter")) { - isBot = true; - } - // else if (userAgent.contains("webkit") || - // userAgent.contains("gecko") || userAgent.contains("firefox") - // || userAgent.contains("msie") || userAgent.contains("chrome") || - // userAgent.contains("chromium") - // || userAgent.contains("opera") || userAgent.contains("browser")) - // { - // isCompatibleBrowser = true; - // } - - if (isBot) { - // log.warn("# BOT " + request.getHeader("User-Agent")); - canonicalAnswer(request, response, path); - return; - } - - // if (isCompatibleBrowser && log.isTraceEnabled()) - // log.trace("# BWS " + request.getHeader("User-Agent")); - redirectTo(response, "/#" + path); - } - - private void redirectTo(HttpServletResponse response, String location) { - response.setHeader("Location", location); - response.setStatus(HttpServletResponse.SC_FOUND); - } - - // private boolean canonicalAnswerNeededBy(HttpServletRequest request) { - // String userAgent = request.getHeader("User-Agent").toLowerCase(); - // return userAgent.startsWith("facebookexternalhit/"); - // } - - /** For bots which don't understand RWT. */ - private void canonicalAnswer(HttpServletRequest request, HttpServletResponse response, String path) { - Session session = null; - try { - PrintWriter writer = response.getWriter(); - session = Subject.doAs(anonymousLogin(), new PrivilegedExceptionAction() { - - @Override - public Session run() throws Exception { - Collection> srs = bc.getServiceReferences(Repository.class, - "(" + CmsConstants.CN + "=" + CmsConstants.EGO_REPOSITORY + ")"); - Repository repository = bc.getService(srs.iterator().next()); - return repository.login(); - } - - }); - Node node = session.getNode(path); - String title = node.hasProperty(JCR_TITLE) ? node.getProperty(JCR_TITLE).getString() : node.getName(); - String desc = node.hasProperty(JCR_DESCRIPTION) ? node.getProperty(JCR_DESCRIPTION).getString() : null; - Calendar lastUpdate = node.hasProperty(JCR_LAST_MODIFIED) ? node.getProperty(JCR_LAST_MODIFIED).getDate() - : null; - String url = getCanonicalUrl(node, request); - String imgUrl = null; - // TODO support images -// loop: for (NodeIterator it = node.getNodes(); it.hasNext();) { -// // Takes the first found cms:image -// Node child = it.nextNode(); -// if (child.isNodeType(CMS_IMAGE)) { -// imgUrl = getDataUrl(child, request); -// break loop; -// } -// } - StringBuilder buf = new StringBuilder(); - buf.append(""); - buf.append(""); - writeMeta(buf, "og:title", escapeHTML(title)); - writeMeta(buf, "og:type", "website"); - buf.append(""); - buf.append(""); - writeMeta(buf, "og:url", url); - if (desc != null) - writeMeta(buf, "og:description", escapeHTML(desc)); - if (imgUrl != null) - writeMeta(buf, "og:image", imgUrl); - if (lastUpdate != null) - writeMeta(buf, "og:updated_time", Long.toString(lastUpdate.getTime().getTime())); - buf.append(""); - buf.append(""); - buf.append("

!! This page is meant for indexing robots, not for real people," + " visit ").append(escapeHTML(title)).append(" instead.

"); - writeCanonical(buf, node); - buf.append(""); - buf.append(""); - writer.print(buf.toString()); - - response.setHeader("Content-Type", "text/html"); - writer.flush(); - } catch (Exception e) { - throw new CmsException("Cannot write canonical answer", e); - } finally { - JcrUtils.logoutQuietly(session); - } - } - - /** - * From http://stackoverflow.com/questions/1265282/recommended-method-for- - * escaping-html-in-java (+ escaping '). TODO Use - * org.apache.commons.lang.StringEscapeUtils - */ - private String escapeHTML(String s) { - StringBuilder out = new StringBuilder(Math.max(16, s.length())); - for (int i = 0; i < s.length(); i++) { - char c = s.charAt(i); - if (c > 127 || c == '\'' || c == '"' || c == '<' || c == '>' || c == '&') { - out.append("&#"); - out.append((int) c); - out.append(';'); - } else { - out.append(c); - } - } - return out.toString(); - } - - private void writeMeta(StringBuilder buf, String tag, String value) { - buf.append(""); - } - - private void writeCanonical(StringBuilder buf, Node node) throws RepositoryException { - buf.append("
"); - if (node.hasProperty(JCR_TITLE)) - buf.append("

").append(node.getProperty(JCR_TITLE).getString()).append("

"); - if (node.hasProperty(JCR_DESCRIPTION)) - buf.append("

").append(node.getProperty(JCR_DESCRIPTION).getString()).append("

"); - NodeIterator children = node.getNodes(); - while (children.hasNext()) { - writeCanonical(buf, children.nextNode()); - } - buf.append("
"); - } - - // DATA - private StringBuilder getServerBaseUrl(HttpServletRequest request) { - try { - URL url = new URL(request.getRequestURL().toString()); - StringBuilder buf = new StringBuilder(); - buf.append(url.getProtocol()).append("://").append(url.getHost()); - if (url.getPort() != -1) - buf.append(':').append(url.getPort()); - return buf; - } catch (MalformedURLException e) { - throw new CmsException("Cannot extract server base URL from " + request.getRequestURL(), e); - } - } - - private String getDataUrl(Node node, HttpServletRequest request) throws RepositoryException { - try { - StringBuilder buf = getServerBaseUrl(request); - buf.append(CmsJcrUtils.getDataPath(CmsConstants.EGO_REPOSITORY, node)); - return new URL(buf.toString()).toString(); - } catch (MalformedURLException e) { - throw new CmsException("Cannot build data URL for " + node, e); - } - } - - // public static String getDataPath(Node node) throws - // RepositoryException { - // assert node != null; - // String userId = node.getSession().getUserID(); - //// if (log.isTraceEnabled()) - //// log.trace(userId + " : " + node.getPath()); - // StringBuilder buf = new StringBuilder(); - // boolean isAnonymous = - // userId.equalsIgnoreCase(NodeConstants.ROLE_ANONYMOUS); - // if (isAnonymous) - // buf.append(WEBDAV_PUBLIC); - // else - // buf.append(WEBDAV_PRIVATE); - // Session session = node.getSession(); - // Repository repository = session.getRepository(); - // String cn; - // if (repository.isSingleValueDescriptor(NodeConstants.CN)) { - // cn = repository.getDescriptor(NodeConstants.CN); - // } else { - //// log.warn("No cn defined in repository, using " + - // NodeConstants.NODE); - // cn = NodeConstants.NODE; - // } - // return - // buf.append('/').append(cn).append('/').append(session.getWorkspace().getName()).append(node.getPath()) - // .toString(); - // } - - private String getCanonicalUrl(Node node, HttpServletRequest request) throws RepositoryException { - try { - StringBuilder buf = getServerBaseUrl(request); - buf.append('/').append('!').append(node.getPath()); - return new URL(buf.toString()).toString(); - } catch (MalformedURLException e) { - throw new CmsException("Cannot build data URL for " + node, e); - } - // return request.getRequestURL().append('!').append(node.getPath()) - // .toString(); - } - - private Subject anonymousLogin() { - Subject subject = new Subject(); - LoginContext lc; - try { - lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS, subject); - lc.login(); - return subject; - } catch (LoginException e) { - throw new CmsException("Cannot login as anonymous", e); - } - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/protectedHandlers.xml b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/protectedHandlers.xml deleted file mode 100644 index 59f22cd5e..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/protectedHandlers.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/webdav-config.xml b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/webdav-config.xml deleted file mode 100644 index 436389898..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/internal/servlet/webdav-config.xml +++ /dev/null @@ -1,207 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - nt:file - nt:resource - - - - - - - - - - - - - rep - jcr - - node - argeo - cms - slc - connect - activities - people - documents - tracker - - - - - - - diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/ldap.cnd b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/ldap.cnd deleted file mode 100644 index a2306c60e..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/ldap.cnd +++ /dev/null @@ -1 +0,0 @@ - diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/node.cnd b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/node.cnd deleted file mode 100644 index d8a26b64e..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/node.cnd +++ /dev/null @@ -1,9 +0,0 @@ - - -[node:userHome] -mixin -- ldap:uid (STRING) m - -[node:groupHome] -mixin -- ldap:cn (STRING) m diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/CsvTabularWriter.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/CsvTabularWriter.java deleted file mode 100644 index ccd543f4d..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/CsvTabularWriter.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.argeo.cms.jcr.tabular; - -import java.io.OutputStream; - -import org.argeo.cms.tabular.TabularWriter; -import org.argeo.util.CsvWriter; - -/** Write tabular content in a stream as CSV. Wraps a {@link CsvWriter}. */ -public class CsvTabularWriter implements TabularWriter { - private CsvWriter csvWriter; - - public CsvTabularWriter(OutputStream out) { - this.csvWriter = new CsvWriter(out); - } - - public void appendRow(Object[] row) { - csvWriter.writeLine(row); - } - - public void close() { - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/JcrTabularRowIterator.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/JcrTabularRowIterator.java deleted file mode 100644 index d1d9b583b..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/JcrTabularRowIterator.java +++ /dev/null @@ -1,170 +0,0 @@ -package org.argeo.cms.jcr.tabular; - -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ArrayBlockingQueue; - -import javax.jcr.Binary; -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.Property; -import javax.jcr.PropertyType; -import javax.jcr.RepositoryException; - -import org.apache.commons.io.IOUtils; -import org.argeo.cms.ArgeoTypes; -import org.argeo.cms.tabular.ArrayTabularRow; -import org.argeo.cms.tabular.TabularColumn; -import org.argeo.cms.tabular.TabularRow; -import org.argeo.cms.tabular.TabularRowIterator; -import org.argeo.jcr.JcrException; -import org.argeo.util.CsvParser; - -/** Iterates over the rows of a {@link ArgeoTypes#ARGEO_TABLE} node. */ -public class JcrTabularRowIterator implements TabularRowIterator { - private Boolean hasNext = null; - private Boolean parsingCompleted = false; - - private Long currentRowNumber = 0l; - - private List header = new ArrayList(); - - /** referenced so that we can close it */ - private Binary binary; - private InputStream in; - - private CsvParser csvParser; - private ArrayBlockingQueue> textLines; - - public JcrTabularRowIterator(Node tableNode) { - try { - for (NodeIterator it = tableNode.getNodes(); it.hasNext();) { - Node node = it.nextNode(); - if (node.isNodeType(ArgeoTypes.ARGEO_COLUMN)) { - Integer type = PropertyType.valueFromName(node.getProperty( - Property.JCR_REQUIRED_TYPE).getString()); - TabularColumn tc = new TabularColumn(node.getProperty( - Property.JCR_TITLE).getString(), type); - header.add(tc); - } - } - Node contentNode = tableNode.getNode(Property.JCR_CONTENT); - if (contentNode.isNodeType(ArgeoTypes.ARGEO_CSV)) { - textLines = new ArrayBlockingQueue>(1000); - csvParser = new CsvParser() { - protected void processLine(Integer lineNumber, - List header, List tokens) { - try { - textLines.put(tokens); - } catch (InterruptedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - // textLines.add(tokens); - if (hasNext == null) { - hasNext = true; - synchronized (JcrTabularRowIterator.this) { - JcrTabularRowIterator.this.notifyAll(); - } - } - } - }; - csvParser.setNoHeader(true); - binary = contentNode.getProperty(Property.JCR_DATA).getBinary(); - in = binary.getStream(); - Thread thread = new Thread(contentNode.getPath() + " reader") { - public void run() { - try { - csvParser.parse(in); - } finally { - parsingCompleted = true; - IOUtils.closeQuietly(in); - } - } - }; - thread.start(); - } - } catch (RepositoryException e) { - throw new JcrException("Cannot read table " + tableNode, e); - } - } - - public synchronized boolean hasNext() { - // we don't know if there is anything available - // while (hasNext == null) - // try { - // wait(); - // } catch (InterruptedException e) { - // // silent - // // FIXME better deal with interruption - // Thread.currentThread().interrupt(); - // break; - // } - - // buffer not empty - if (!textLines.isEmpty()) - return true; - - // maybe the parsing is finished but the flag has not been set - while (!parsingCompleted && textLines.isEmpty()) - try { - wait(100); - } catch (InterruptedException e) { - // silent - // FIXME better deal with interruption - Thread.currentThread().interrupt(); - break; - } - - // buffer not empty - if (!textLines.isEmpty()) - return true; - - // (parsingCompleted && textLines.isEmpty()) - return false; - - // if (!hasNext && textLines.isEmpty()) { - // if (in != null) { - // IOUtils.closeQuietly(in); - // in = null; - // } - // if (binary != null) { - // JcrUtils.closeQuietly(binary); - // binary = null; - // } - // return false; - // } else - // return true; - } - - public synchronized TabularRow next() { - try { - List tokens = textLines.take(); - List objs = new ArrayList(tokens.size()); - for (String token : tokens) { - // TODO convert to other formats using header - objs.add(token); - } - currentRowNumber++; - return new ArrayTabularRow(objs); - } catch (InterruptedException e) { - // silent - // FIXME better deal with interruption - } - return null; - } - - public void remove() { - throw new UnsupportedOperationException(); - } - - public Long getCurrentRowNumber() { - return currentRowNumber; - } - - public List getHeader() { - return header; - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/JcrTabularWriter.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/JcrTabularWriter.java deleted file mode 100644 index cc3e0d7a9..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/JcrTabularWriter.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.argeo.cms.jcr.tabular; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.InputStream; -import java.util.List; - -import javax.jcr.Binary; -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.PropertyType; -import javax.jcr.RepositoryException; - -import org.apache.commons.io.IOUtils; -import org.argeo.cms.ArgeoTypes; -import org.argeo.cms.tabular.TabularColumn; -import org.argeo.cms.tabular.TabularWriter; -import org.argeo.jcr.JcrException; -import org.argeo.jcr.JcrUtils; -import org.argeo.util.CsvWriter; - -/** Write / reference tabular content in a JCR repository. */ -public class JcrTabularWriter implements TabularWriter { - private Node contentNode; - private ByteArrayOutputStream out; - private CsvWriter csvWriter; - - @SuppressWarnings("unused") - private final List columns; - - /** Creates a table node */ - public JcrTabularWriter(Node tableNode, List columns, - String contentNodeType) { - try { - this.columns = columns; - for (TabularColumn column : columns) { - String normalized = JcrUtils.replaceInvalidChars(column - .getName()); - Node columnNode = tableNode.addNode(normalized, - ArgeoTypes.ARGEO_COLUMN); - columnNode.setProperty(Property.JCR_TITLE, column.getName()); - if (column.getType() != null) - columnNode.setProperty(Property.JCR_REQUIRED_TYPE, - PropertyType.nameFromValue(column.getType())); - else - columnNode.setProperty(Property.JCR_REQUIRED_TYPE, - PropertyType.TYPENAME_STRING); - } - contentNode = tableNode.addNode(Property.JCR_CONTENT, - contentNodeType); - if (contentNodeType.equals(ArgeoTypes.ARGEO_CSV)) { - contentNode.setProperty(Property.JCR_MIMETYPE, "text/csv"); - contentNode.setProperty(Property.JCR_ENCODING, "UTF-8"); - out = new ByteArrayOutputStream(); - csvWriter = new CsvWriter(out); - } - } catch (RepositoryException e) { - throw new JcrException("Cannot create table node " + tableNode, e); - } - } - - public void appendRow(Object[] row) { - csvWriter.writeLine(row); - } - - public void close() { - Binary binary = null; - InputStream in = null; - try { - // TODO parallelize with pipes and writing from another thread - in = new ByteArrayInputStream(out.toByteArray()); - binary = contentNode.getSession().getValueFactory() - .createBinary(in); - contentNode.setProperty(Property.JCR_DATA, binary); - } catch (RepositoryException e) { - throw new JcrException("Cannot store data in " + contentNode, e); - } finally { - IOUtils.closeQuietly(in); - JcrUtils.closeQuietly(binary); - } - } -} diff --git a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/package-info.java b/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/package-info.java deleted file mode 100644 index 506a6ac98..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/cms/jcr/tabular/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Argeo CMS implementation of the Argeo Tabular API (CSV, JCR). */ -package org.argeo.cms.jcr.tabular; \ No newline at end of file diff --git a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/JackrabbitAdminLoginModule.java b/org.argeo.cms.jcr/src/org/argeo/jackrabbit/JackrabbitAdminLoginModule.java deleted file mode 100644 index 7396c87e7..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/JackrabbitAdminLoginModule.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.argeo.jackrabbit; - -import java.util.Map; - -import javax.security.auth.Subject; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.login.LoginException; -import javax.security.auth.spi.LoginModule; - -import org.apache.jackrabbit.core.security.SecurityConstants; -import org.apache.jackrabbit.core.security.principal.AdminPrincipal; - -@Deprecated -public class JackrabbitAdminLoginModule implements LoginModule { - private Subject subject; - - @Override - public void initialize(Subject subject, CallbackHandler callbackHandler, - Map sharedState, Map options) { - this.subject = subject; - } - - @Override - public boolean login() throws LoginException { - // TODO check permission? - return true; - } - - @Override - public boolean commit() throws LoginException { - subject.getPrincipals().add( - new AdminPrincipal(SecurityConstants.ADMIN_ID)); - return true; - } - - @Override - public boolean abort() throws LoginException { - return true; - } - - @Override - public boolean logout() throws LoginException { - subject.getPrincipals().removeAll( - subject.getPrincipals(AdminPrincipal.class)); - return true; - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/JackrabbitDataModelMigration.java b/org.argeo.cms.jcr/src/org/argeo/jackrabbit/JackrabbitDataModelMigration.java deleted file mode 100644 index 8c267e314..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/JackrabbitDataModelMigration.java +++ /dev/null @@ -1,172 +0,0 @@ -package org.argeo.jackrabbit; - -import java.io.IOException; -import java.io.InputStreamReader; -import java.io.Reader; -import java.net.URL; - -import javax.jcr.RepositoryException; -import javax.jcr.Session; - -import org.apache.commons.io.IOUtils; -import org.apache.jackrabbit.commons.cnd.CndImporter; -import org.apache.jackrabbit.commons.cnd.ParseException; -import org.apache.jackrabbit.core.config.RepositoryConfig; -import org.apache.jackrabbit.core.fs.FileSystemException; -import org.argeo.api.cms.CmsLog; -import org.argeo.jcr.JcrCallback; -import org.argeo.jcr.JcrException; -import org.argeo.jcr.JcrUtils; - -/** Migrate the data in a Jackrabbit repository. */ -@Deprecated -public class JackrabbitDataModelMigration implements Comparable { - private final static CmsLog log = CmsLog.getLog(JackrabbitDataModelMigration.class); - - private String dataModelNodePath; - private String targetVersion; - private URL migrationCnd; - private JcrCallback dataModification; - - /** - * Expects an already started repository with the old data model to migrate. - * Expects to be run with admin rights (Repository.login() will be used). - * - * @return true if a migration was performed and the repository needs to be - * restarted and its caches cleared. - */ - public Boolean migrate(Session session) { - long begin = System.currentTimeMillis(); - Reader reader = null; - try { - // check if already migrated - if (!session.itemExists(dataModelNodePath)) { -// log.warn("Node " + dataModelNodePath + " does not exist: nothing to migrate."); - return false; - } -// Node dataModelNode = session.getNode(dataModelNodePath); -// if (dataModelNode.hasProperty(ArgeoNames.ARGEO_DATA_MODEL_VERSION)) { -// String currentVersion = dataModelNode.getProperty( -// ArgeoNames.ARGEO_DATA_MODEL_VERSION).getString(); -// if (compareVersions(currentVersion, targetVersion) >= 0) { -// log.info("Data model at version " + currentVersion -// + ", no need to migrate."); -// return false; -// } -// } - - // apply transitional CND - if (migrationCnd != null) { - reader = new InputStreamReader(migrationCnd.openStream()); - CndImporter.registerNodeTypes(reader, session, true); - session.save(); -// log.info("Registered migration node types from " + migrationCnd); - } - - // modify data - dataModification.execute(session); - - // apply changes - session.save(); - - long duration = System.currentTimeMillis() - begin; -// log.info("Migration of data model " + dataModelNodePath + " to " + targetVersion + " performed in " -// + duration + "ms"); - return true; - } catch (RepositoryException e) { - JcrUtils.discardQuietly(session); - throw new JcrException("Migration of data model " + dataModelNodePath + " to " + targetVersion + " failed.", - e); - } catch (ParseException | IOException e) { - JcrUtils.discardQuietly(session); - throw new RuntimeException( - "Migration of data model " + dataModelNodePath + " to " + targetVersion + " failed.", e); - } finally { - JcrUtils.logoutQuietly(session); - IOUtils.closeQuietly(reader); - } - } - - protected static int compareVersions(String version1, String version2) { - // TODO do a proper version analysis and comparison - return version1.compareTo(version2); - } - - /** To be called on a stopped repository. */ - public static void clearRepositoryCaches(RepositoryConfig repositoryConfig) { - try { - String customeNodeTypesPath = "/nodetypes/custom_nodetypes.xml"; - // FIXME causes weird error in Eclipse - repositoryConfig.getFileSystem().deleteFile(customeNodeTypesPath); - if (log.isDebugEnabled()) - log.debug("Cleared " + customeNodeTypesPath); - } catch (RuntimeException e) { - throw e; - } catch (RepositoryException e) { - throw new JcrException(e); - } catch (FileSystemException e) { - throw new RuntimeException("Cannot clear node types cache.",e); - } - - // File customNodeTypes = new File(home.getPath() - // + "/repository/nodetypes/custom_nodetypes.xml"); - // if (customNodeTypes.exists()) { - // customNodeTypes.delete(); - // if (log.isDebugEnabled()) - // log.debug("Cleared " + customNodeTypes); - // } else { - // log.warn("File " + customNodeTypes + " not found."); - // } - } - - /* - * FOR USE IN (SORTED) SETS - */ - - public int compareTo(JackrabbitDataModelMigration dataModelMigration) { - // TODO make ordering smarter - if (dataModelNodePath.equals(dataModelMigration.dataModelNodePath)) - return compareVersions(targetVersion, dataModelMigration.targetVersion); - else - return dataModelNodePath.compareTo(dataModelMigration.dataModelNodePath); - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof JackrabbitDataModelMigration)) - return false; - JackrabbitDataModelMigration dataModelMigration = (JackrabbitDataModelMigration) obj; - return dataModelNodePath.equals(dataModelMigration.dataModelNodePath) - && targetVersion.equals(dataModelMigration.targetVersion); - } - - @Override - public int hashCode() { - return targetVersion.hashCode(); - } - - public void setDataModelNodePath(String dataModelNodePath) { - this.dataModelNodePath = dataModelNodePath; - } - - public void setTargetVersion(String targetVersion) { - this.targetVersion = targetVersion; - } - - public void setMigrationCnd(URL migrationCnd) { - this.migrationCnd = migrationCnd; - } - - public void setDataModification(JcrCallback dataModification) { - this.dataModification = dataModification; - } - - public String getDataModelNodePath() { - return dataModelNodePath; - } - - public String getTargetVersion() { - return targetVersion; - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryFactory.java b/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryFactory.java deleted file mode 100644 index 77ad527e1..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryFactory.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.argeo.jackrabbit.client; - -import java.util.Map; - -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.RepositoryFactory; - -import org.apache.jackrabbit.jcr2spi.Jcr2spiRepositoryFactory; -import org.apache.jackrabbit.jcr2spi.RepositoryImpl; -import org.apache.jackrabbit.spi.RepositoryServiceFactory; - -/** A customised {@link RepositoryFactory} access a remote DAVEX service. */ -public class ClientDavexRepositoryFactory implements RepositoryFactory { - public final static String JACKRABBIT_DAVEX_URI = ClientDavexRepositoryServiceFactory.PARAM_REPOSITORY_URI; - public final static String JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = ClientDavexRepositoryServiceFactory.PARAM_WORKSPACE_NAME_DEFAULT; - - @SuppressWarnings("rawtypes") - @Override - public Repository getRepository(Map parameters) throws RepositoryException { - RepositoryServiceFactory repositoryServiceFactory = new ClientDavexRepositoryServiceFactory(); - return RepositoryImpl - .create(new Jcr2spiRepositoryFactory.RepositoryConfigImpl(repositoryServiceFactory, parameters)); - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryService.java b/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryService.java deleted file mode 100644 index 0f9db8772..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryService.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.argeo.jackrabbit.client; - -import javax.jcr.RepositoryException; - -import org.apache.http.client.protocol.HttpClientContext; -import org.apache.http.protocol.HttpContext; -import org.apache.jackrabbit.spi.SessionInfo; -import org.apache.jackrabbit.spi2davex.BatchReadConfig; -import org.apache.jackrabbit.spi2davex.RepositoryServiceImpl; - -/** - * Wrapper for {@link RepositoryServiceImpl} in order to access the underlying - * {@link HttpClientContext}. - */ -public class ClientDavexRepositoryService extends RepositoryServiceImpl { - - public ClientDavexRepositoryService(String jcrServerURI, BatchReadConfig batchReadConfig) - throws RepositoryException { - super(jcrServerURI, batchReadConfig); - } - - public ClientDavexRepositoryService(String jcrServerURI, String defaultWorkspaceName, - BatchReadConfig batchReadConfig, int itemInfoCacheSize, int maximumHttpConnections) - throws RepositoryException { - super(jcrServerURI, defaultWorkspaceName, batchReadConfig, itemInfoCacheSize, maximumHttpConnections); - } - - public ClientDavexRepositoryService(String jcrServerURI, String defaultWorkspaceName, - BatchReadConfig batchReadConfig, int itemInfoCacheSize) throws RepositoryException { - super(jcrServerURI, defaultWorkspaceName, batchReadConfig, itemInfoCacheSize); - } - - @Override - protected HttpContext getContext(SessionInfo sessionInfo) throws RepositoryException { - HttpClientContext result = HttpClientContext.create(); - result.setAuthCache(new NonSerialBasicAuthCache()); - return result; - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryServiceFactory.java b/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryServiceFactory.java deleted file mode 100644 index 4b240f060..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/ClientDavexRepositoryServiceFactory.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.argeo.jackrabbit.client; - -import java.util.Map; - -import javax.jcr.RepositoryException; - -import org.apache.jackrabbit.spi.RepositoryService; -import org.apache.jackrabbit.spi.commons.ItemInfoCacheImpl; -import org.apache.jackrabbit.spi2davex.BatchReadConfig; -import org.apache.jackrabbit.spi2davex.Spi2davexRepositoryServiceFactory; - -/** - * Wrapper for {@link Spi2davexRepositoryServiceFactory} in order to create a - * {@link ClientDavexRepositoryService}. - */ -public class ClientDavexRepositoryServiceFactory extends Spi2davexRepositoryServiceFactory { - @Override - public RepositoryService createRepositoryService(Map parameters) throws RepositoryException { - // retrieve the repository uri - String uri; - if (parameters == null) { - uri = System.getProperty(PARAM_REPOSITORY_URI); - } else { - Object repoUri = parameters.get(PARAM_REPOSITORY_URI); - uri = (repoUri == null) ? null : repoUri.toString(); - } - if (uri == null) { - uri = DEFAULT_REPOSITORY_URI; - } - - // load other optional configuration parameters - BatchReadConfig brc = null; - int itemInfoCacheSize = ItemInfoCacheImpl.DEFAULT_CACHE_SIZE; - int maximumHttpConnections = 0; - - // since JCR-4120 the default workspace name is no longer set to 'default' - // note: if running with JCR Server < 1.5 a default workspace name must - // therefore be configured - String workspaceNameDefault = null; - - if (parameters != null) { - // batchRead config - Object param = parameters.get(PARAM_BATCHREAD_CONFIG); - if (param != null && param instanceof BatchReadConfig) { - brc = (BatchReadConfig) param; - } - - // itemCache size config - param = parameters.get(PARAM_ITEMINFO_CACHE_SIZE); - if (param != null) { - try { - itemInfoCacheSize = Integer.parseInt(param.toString()); - } catch (NumberFormatException e) { - // ignore, use default - } - } - - // max connections config - param = parameters.get(PARAM_MAX_CONNECTIONS); - if (param != null) { - try { - maximumHttpConnections = Integer.parseInt(param.toString()); - } catch (NumberFormatException e) { - // using default - } - } - - param = parameters.get(PARAM_WORKSPACE_NAME_DEFAULT); - if (param != null) { - workspaceNameDefault = param.toString(); - } - } - - if (maximumHttpConnections > 0) { - return new ClientDavexRepositoryService(uri, workspaceNameDefault, brc, itemInfoCacheSize, - maximumHttpConnections); - } else { - return new ClientDavexRepositoryService(uri, workspaceNameDefault, brc, itemInfoCacheSize); - } - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/JackrabbitClient.java b/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/JackrabbitClient.java deleted file mode 100644 index e08f4d6c7..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/JackrabbitClient.java +++ /dev/null @@ -1,125 +0,0 @@ -package org.argeo.jackrabbit.client; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.HashMap; -import java.util.Map; - -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.RepositoryFactory; -import javax.jcr.Session; - -import org.apache.http.client.protocol.HttpClientContext; -import org.apache.http.protocol.HttpContext; -import org.apache.jackrabbit.jcr2dav.Jcr2davRepositoryFactory; -import org.apache.jackrabbit.jcr2spi.Jcr2spiRepositoryFactory; -import org.apache.jackrabbit.jcr2spi.RepositoryImpl; -import org.apache.jackrabbit.spi.RepositoryService; -import org.apache.jackrabbit.spi.RepositoryServiceFactory; -import org.apache.jackrabbit.spi.SessionInfo; -import org.apache.jackrabbit.spi.commons.ItemInfoCacheImpl; -import org.apache.jackrabbit.spi2davex.BatchReadConfig; -import org.apache.jackrabbit.spi2davex.RepositoryServiceImpl; -import org.apache.jackrabbit.spi2davex.Spi2davexRepositoryServiceFactory; -import org.argeo.jcr.JcrUtils; - -/** Minimal client to test JCR DAVEX connectivity. */ -public class JackrabbitClient { - final static String JACKRABBIT_REPOSITORY_URI = "org.apache.jackrabbit.repository.uri"; - final static String JACKRABBIT_DAVEX_URI = "org.apache.jackrabbit.spi2davex.uri"; - final static String JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = "org.apache.jackrabbit.spi2davex.WorkspaceNameDefault"; - - public static void main(String[] args) { - String repoUri = args.length == 0 ? "http://root:demo@localhost:7070/jcr/ego" : args[0]; - String workspace = args.length < 2 ? "home" : args[1]; - - Repository repository = null; - Session session = null; - - URI uri; - try { - uri = new URI(repoUri); - } catch (URISyntaxException e1) { - throw new IllegalArgumentException(e1); - } - - if (uri.getScheme().equals("http") || uri.getScheme().equals("https")) { - - RepositoryFactory repositoryFactory = new Jcr2davRepositoryFactory() { - @SuppressWarnings("rawtypes") - public Repository getRepository(Map parameters) throws RepositoryException { - RepositoryServiceFactory repositoryServiceFactory = new Spi2davexRepositoryServiceFactory() { - - @Override - public RepositoryService createRepositoryService(Map parameters) - throws RepositoryException { - Object uri = parameters.get(JACKRABBIT_DAVEX_URI); - Object defaultWorkspace = parameters.get(JACKRABBIT_REMOTE_DEFAULT_WORKSPACE); - BatchReadConfig brc = null; - return new RepositoryServiceImpl(uri.toString(), defaultWorkspace.toString(), brc, - ItemInfoCacheImpl.DEFAULT_CACHE_SIZE) { - - @Override - protected HttpContext getContext(SessionInfo sessionInfo) throws RepositoryException { - HttpClientContext result = HttpClientContext.create(); - result.setAuthCache(new NonSerialBasicAuthCache()); - return result; - } - - }; - } - }; - return RepositoryImpl.create( - new Jcr2spiRepositoryFactory.RepositoryConfigImpl(repositoryServiceFactory, parameters)); - } - }; - Map params = new HashMap(); - params.put(JACKRABBIT_DAVEX_URI, repoUri.toString()); - // FIXME make it configurable - params.put(JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, "sys"); - - try { - repository = repositoryFactory.getRepository(params); - if (repository != null) - session = repository.login(workspace); - else - throw new IllegalArgumentException("Repository " + repoUri + " not found"); - } catch (RepositoryException e) { - e.printStackTrace(); - } - - } else { - Path path = Paths.get(uri.getPath()); - } - - try { - Node rootNode = session.getRootNode(); - NodeIterator nit = rootNode.getNodes(); - while (nit.hasNext()) { - System.out.println(nit.nextNode().getPath()); - } - - Node newNode = JcrUtils.mkdirs(rootNode, "dir/subdir"); - System.out.println("Created folder " + newNode.getPath()); - Node newFile = JcrUtils.copyBytesAsFile(newNode, "test.txt", "TEST".getBytes()); - System.out.println("Created file " + newFile.getPath()); - try (BufferedReader reader = new BufferedReader(new InputStreamReader(JcrUtils.getFileAsStream(newFile)))) { - System.out.println("Read " + reader.readLine()); - } catch (IOException e) { - e.printStackTrace(); - } - newNode.getParent().remove(); - System.out.println("Removed new nodes"); - } catch (RepositoryException e) { - e.printStackTrace(); - } - } -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/NonSerialBasicAuthCache.java b/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/NonSerialBasicAuthCache.java deleted file mode 100644 index 3fb0db9a0..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/client/NonSerialBasicAuthCache.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.argeo.jackrabbit.client; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import org.apache.http.HttpHost; -import org.apache.http.auth.AuthScheme; -import org.apache.http.client.AuthCache; - -/** - * Implementation of {@link AuthCache} which doesn't use serialization, as it is - * not supported by GraalVM at this stage. - */ -public class NonSerialBasicAuthCache implements AuthCache { - private final Map cache; - - public NonSerialBasicAuthCache() { - cache = new ConcurrentHashMap(); - } - - @Override - public void put(HttpHost host, AuthScheme authScheme) { - cache.put(host, authScheme); - } - - @Override - public AuthScheme get(HttpHost host) { - return cache.get(host); - } - - @Override - public void remove(HttpHost host) { - cache.remove(host); - } - - @Override - public void clear() { - cache.clear(); - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/AbstractJackrabbitFsProvider.java b/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/AbstractJackrabbitFsProvider.java deleted file mode 100644 index a2eb98302..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/AbstractJackrabbitFsProvider.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.argeo.jackrabbit.fs; - -import org.argeo.jcr.fs.JcrFileSystemProvider; - -public abstract class AbstractJackrabbitFsProvider extends JcrFileSystemProvider { - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/DavexFsProvider.java b/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/DavexFsProvider.java deleted file mode 100644 index 1cae6e493..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/DavexFsProvider.java +++ /dev/null @@ -1,149 +0,0 @@ -package org.argeo.jackrabbit.fs; - -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.file.DirectoryStream; -import java.nio.file.FileSystem; -import java.nio.file.FileSystemAlreadyExistsException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.HashMap; -import java.util.Map; - -import javax.jcr.Repository; -import javax.jcr.RepositoryFactory; -import javax.jcr.Session; - -import org.argeo.jackrabbit.client.ClientDavexRepositoryFactory; -import org.argeo.jcr.fs.JcrFileSystem; -import org.argeo.jcr.fs.JcrFsException; - -/** - * A file system provider based on a JCR repository remotely accessed via the - * DAVEX protocol. - */ -public class DavexFsProvider extends AbstractJackrabbitFsProvider { - final static String DEFAULT_JACKRABBIT_REMOTE_DEFAULT_WORKSPACE = "sys"; - - private Map fileSystems = new HashMap<>(); - - @Override - public String getScheme() { - return "davex"; - } - - @Override - public FileSystem newFileSystem(URI uri, Map env) throws IOException { - if (uri.getHost() == null) - throw new IllegalArgumentException("An host should be provided"); - try { - URI repoUri = new URI("http", uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath(), null, null); - String repoKey = repoUri.toString(); - if (fileSystems.containsKey(repoKey)) - throw new FileSystemAlreadyExistsException("CMS file system already exists for " + repoKey); - RepositoryFactory repositoryFactory = new ClientDavexRepositoryFactory(); - return tryGetRepo(repositoryFactory, repoUri, "home"); - } catch (URISyntaxException e) { - throw new IllegalArgumentException("Cannot open file system " + uri, e); - } - } - - private JcrFileSystem tryGetRepo(RepositoryFactory repositoryFactory, URI repoUri, String workspace) - throws IOException { - Map params = new HashMap(); - params.put(ClientDavexRepositoryFactory.JACKRABBIT_DAVEX_URI, repoUri.toString()); - // TODO better integrate with OSGi or other configuration than system - // properties. - String remoteDefaultWorkspace = System.getProperty( - ClientDavexRepositoryFactory.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, - DEFAULT_JACKRABBIT_REMOTE_DEFAULT_WORKSPACE); - params.put(ClientDavexRepositoryFactory.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, remoteDefaultWorkspace); - Repository repository = null; - Session session = null; - try { - repository = repositoryFactory.getRepository(params); - if (repository != null) - session = repository.login(workspace); - } catch (Exception e) { - // silent - } - - if (session == null) { - if (repoUri.getPath() == null || repoUri.getPath().equals("/")) - return null; - String repoUriStr = repoUri.toString(); - if (repoUriStr.endsWith("/")) - repoUriStr = repoUriStr.substring(0, repoUriStr.length() - 1); - String nextRepoUriStr = repoUriStr.substring(0, repoUriStr.lastIndexOf('/')); - String nextWorkspace = repoUriStr.substring(repoUriStr.lastIndexOf('/') + 1); - URI nextUri; - try { - nextUri = new URI(nextRepoUriStr); - } catch (URISyntaxException e) { - throw new IllegalArgumentException("Badly formatted URI", e); - } - return tryGetRepo(repositoryFactory, nextUri, nextWorkspace); - } else { - JcrFileSystem fileSystem = new JcrFileSystem(this, repository); - fileSystems.put(repoUri.toString() + "/" + workspace, fileSystem); - return fileSystem; - } - } - - @Override - public FileSystem getFileSystem(URI uri) { - return currentUserFileSystem(uri); - } - - @Override - public Path getPath(URI uri) { - JcrFileSystem fileSystem = currentUserFileSystem(uri); - if (fileSystem == null) - try { - fileSystem = (JcrFileSystem) newFileSystem(uri, new HashMap()); - if (fileSystem == null) - throw new IllegalArgumentException("No file system found for " + uri); - } catch (IOException e) { - throw new JcrFsException("Could not autocreate file system", e); - } - URI repoUri = null; - try { - repoUri = new URI("http", uri.getUserInfo(), uri.getHost(), uri.getPort(), uri.getPath(), null, null); - } catch (URISyntaxException e) { - throw new IllegalArgumentException(e); - } - String uriStr = repoUri.toString(); - String localPath = null; - for (String key : fileSystems.keySet()) { - if (uriStr.startsWith(key)) { - localPath = uriStr.toString().substring(key.length()); - } - } - if ("".equals(localPath)) - localPath = "/"; - return fileSystem.getPath(localPath); - } - - private JcrFileSystem currentUserFileSystem(URI uri) { - for (String key : fileSystems.keySet()) { - if (uri.toString().startsWith(key)) - return fileSystems.get(key); - } - return null; - } - - public static void main(String args[]) { - try { - DavexFsProvider fsProvider = new DavexFsProvider(); - Path path = fsProvider.getPath(new URI("davex://root:demo@localhost:7070/jcr/ego/")); - System.out.println(path); - DirectoryStream ds = Files.newDirectoryStream(path); - for (Path p : ds) { - System.out.println("- " + p); - } - } catch (Exception e) { - e.printStackTrace(); - } - } -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/JackrabbitMemoryFsProvider.java b/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/JackrabbitMemoryFsProvider.java deleted file mode 100644 index e3a70d084..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/JackrabbitMemoryFsProvider.java +++ /dev/null @@ -1,87 +0,0 @@ -package org.argeo.jackrabbit.fs; - -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.file.FileSystem; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.HashMap; -import java.util.Map; - -import javax.jcr.Credentials; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.SimpleCredentials; - -import org.apache.jackrabbit.core.RepositoryImpl; -import org.apache.jackrabbit.core.config.RepositoryConfig; -import org.argeo.jcr.fs.JcrFileSystem; -import org.argeo.jcr.fs.JcrFsException; - -public class JackrabbitMemoryFsProvider extends AbstractJackrabbitFsProvider { - private RepositoryImpl repository; - private JcrFileSystem fileSystem; - - private Credentials credentials; - - public JackrabbitMemoryFsProvider() { - String username = System.getProperty("user.name"); - credentials = new SimpleCredentials(username, username.toCharArray()); - } - - @Override - public String getScheme() { - return "jcr+memory"; - } - - @Override - public FileSystem newFileSystem(URI uri, Map env) throws IOException { - try { - Path tempDir = Files.createTempDirectory("fs-memory"); - URL confUrl = JackrabbitMemoryFsProvider.class.getResource("fs-memory.xml"); - RepositoryConfig repositoryConfig = RepositoryConfig.create(confUrl.toURI(), tempDir.toString()); - repository = RepositoryImpl.create(repositoryConfig); - postRepositoryCreation(repository); - fileSystem = new JcrFileSystem(this, repository, credentials); - return fileSystem; - } catch (RepositoryException | URISyntaxException e) { - throw new IOException("Cannot login to repository", e); - } - } - - @Override - public FileSystem getFileSystem(URI uri) { - return fileSystem; - } - - @Override - public Path getPath(URI uri) { - String path = uri.getPath(); - if (fileSystem == null) - try { - newFileSystem(uri, new HashMap()); - } catch (IOException e) { - throw new JcrFsException("Could not autocreate file system", e); - } - return fileSystem.getPath(path); - } - - public Repository getRepository() { - return repository; - } - - public Session login() throws RepositoryException { - return getRepository().login(credentials); - } - - /** - * Called after the repository has been created and before the file system is - * created. - */ - protected void postRepositoryCreation(RepositoryImpl repositoryImpl) throws RepositoryException { - - } -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/fs-memory.xml b/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/fs-memory.xml deleted file mode 100644 index f2541fb4e..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/fs-memory.xml +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/package-info.java b/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/package-info.java deleted file mode 100644 index c9ec2c3b9..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/fs/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Java NIO file system implementation based on Jackrabbit. */ -package org.argeo.jackrabbit.fs; \ No newline at end of file diff --git a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/package-info.java b/org.argeo.cms.jcr/src/org/argeo/jackrabbit/package-info.java deleted file mode 100644 index 17497d62c..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Generic Jackrabbit utilities. */ -package org.argeo.jackrabbit; \ No newline at end of file diff --git a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-h2.xml b/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-h2.xml deleted file mode 100644 index 05267621f..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-h2.xml +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-localfs.xml b/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-localfs.xml deleted file mode 100644 index 3d2470863..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-localfs.xml +++ /dev/null @@ -1,60 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-memory.xml b/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-memory.xml deleted file mode 100644 index ecee5bdad..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-memory.xml +++ /dev/null @@ -1,55 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-postgresql-ds.xml b/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-postgresql-ds.xml deleted file mode 100644 index 07a0d0428..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-postgresql-ds.xml +++ /dev/null @@ -1,82 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-postgresql.xml b/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-postgresql.xml deleted file mode 100644 index 967782820..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/repository-postgresql.xml +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/security/JackrabbitSecurityUtils.java b/org.argeo.cms.jcr/src/org/argeo/jackrabbit/security/JackrabbitSecurityUtils.java deleted file mode 100644 index f98cf9947..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/security/JackrabbitSecurityUtils.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.argeo.jackrabbit.security; - -import java.security.Principal; -import java.util.ArrayList; -import java.util.List; - -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.security.Privilege; - -import org.apache.jackrabbit.api.security.JackrabbitAccessControlList; -import org.apache.jackrabbit.api.security.JackrabbitAccessControlManager; -import org.argeo.api.cms.CmsLog; -import org.argeo.jcr.JcrUtils; - -/** Utilities around Jackrabbit security extensions. */ -public class JackrabbitSecurityUtils { - private final static CmsLog log = CmsLog.getLog(JackrabbitSecurityUtils.class); - - /** - * Convenience method for denying a single privilege to a principal (user or - * role), typically jcr:all - */ - public synchronized static void denyPrivilege(Session session, String path, String principal, String privilege) - throws RepositoryException { - List privileges = new ArrayList(); - privileges.add(session.getAccessControlManager().privilegeFromName(privilege)); - denyPrivileges(session, path, () -> principal, privileges); - } - - /** - * Deny privileges on a path to a {@link Principal}. The path must already - * exist. Session is saved. Synchronized to prevent concurrent modifications of - * the same node. - */ - public synchronized static Boolean denyPrivileges(Session session, String path, Principal principal, - List privs) throws RepositoryException { - // make sure the session is in line with the persisted state - session.refresh(false); - JackrabbitAccessControlManager acm = (JackrabbitAccessControlManager) session.getAccessControlManager(); - JackrabbitAccessControlList acl = (JackrabbitAccessControlList) JcrUtils.getAccessControlList(acm, path); - -// accessControlEntries: for (AccessControlEntry ace : acl.getAccessControlEntries()) { -// Principal currentPrincipal = ace.getPrincipal(); -// if (currentPrincipal.getName().equals(principal.getName())) { -// Privilege[] currentPrivileges = ace.getPrivileges(); -// if (currentPrivileges.length != privs.size()) -// break accessControlEntries; -// for (int i = 0; i < currentPrivileges.length; i++) { -// Privilege currP = currentPrivileges[i]; -// Privilege p = privs.get(i); -// if (!currP.getName().equals(p.getName())) { -// break accessControlEntries; -// } -// } -// return false; -// } -// } - - Privilege[] privileges = privs.toArray(new Privilege[privs.size()]); - acl.addEntry(principal, privileges, false); - acm.setPolicy(path, acl); - if (log.isDebugEnabled()) { - StringBuffer privBuf = new StringBuffer(); - for (Privilege priv : privs) - privBuf.append(priv.getName()); - log.debug("Denied privileges " + privBuf + " to " + principal.getName() + " on " + path + " in '" - + session.getWorkspace().getName() + "'"); - } - session.refresh(true); - session.save(); - return true; - } - - /** Singleton. */ - private JackrabbitSecurityUtils() { - - } -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/security/package-info.java b/org.argeo.cms.jcr/src/org/argeo/jackrabbit/security/package-info.java deleted file mode 100644 index f3a282c4e..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/security/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Generic Jackrabbit security utilities. */ -package org.argeo.jackrabbit.security; \ No newline at end of file diff --git a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/AbstractJackrabbitTestCase.java b/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/AbstractJackrabbitTestCase.java deleted file mode 100644 index f65432eb7..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/AbstractJackrabbitTestCase.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.argeo.jackrabbit.unit; - -import java.net.URL; - -import javax.jcr.Repository; - -import org.apache.commons.io.FileUtils; -import org.apache.jackrabbit.core.RepositoryImpl; -import org.apache.jackrabbit.core.config.RepositoryConfig; -import org.argeo.jcr.unit.AbstractJcrTestCase; - -/** Factorizes configuration of an in memory transient repository */ -public abstract class AbstractJackrabbitTestCase extends AbstractJcrTestCase { - protected RepositoryImpl repositoryImpl; - - // protected File getRepositoryFile() throws Exception { - // Resource res = new ClassPathResource( - // "org/argeo/jackrabbit/unit/repository-memory.xml"); - // return res.getFile(); - // } - - public AbstractJackrabbitTestCase() { - URL url = AbstractJackrabbitTestCase.class.getResource("jaas.config"); - assert url != null; - System.setProperty("java.security.auth.login.config", url.toString()); - } - - protected Repository createRepository() throws Exception { - // Repository repository = new TransientRepository(getRepositoryFile(), - // getHomeDir()); - RepositoryConfig repositoryConfig = RepositoryConfig.create( - AbstractJackrabbitTestCase.class - .getResourceAsStream(getRepositoryConfigResource()), - getHomeDir().getAbsolutePath()); - RepositoryImpl repositoryImpl = RepositoryImpl.create(repositoryConfig); - return repositoryImpl; - } - - protected String getRepositoryConfigResource() { - return "repository-memory.xml"; - } - - @Override - protected void clearRepository(Repository repository) throws Exception { - RepositoryImpl repositoryImpl = (RepositoryImpl) repository; - if (repositoryImpl != null) - repositoryImpl.shutdown(); - FileUtils.deleteDirectory(getHomeDir()); - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/jaas.config b/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/jaas.config deleted file mode 100644 index 0313f91e5..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/jaas.config +++ /dev/null @@ -1,7 +0,0 @@ -TEST_JACKRABBIT_ADMIN { - org.argeo.cms.auth.DataAdminLoginModule requisite; -}; - -Jackrabbit { - org.argeo.security.jackrabbit.SystemJackrabbitLoginModule requisite; -}; diff --git a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/package-info.java b/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/package-info.java deleted file mode 100644 index 3b6143b34..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Helpers for unit tests with Jackrabbit repositories. */ -package org.argeo.jackrabbit.unit; \ No newline at end of file diff --git a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/repository-h2.xml b/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/repository-h2.xml deleted file mode 100644 index 348dc288b..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/repository-h2.xml +++ /dev/null @@ -1,81 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/repository-memory.xml b/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/repository-memory.xml deleted file mode 100644 index 839542417..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jackrabbit/unit/repository-memory.xml +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/Bin.java b/org.argeo.cms.jcr/src/org/argeo/jcr/Bin.java deleted file mode 100644 index 0418810ed..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/Bin.java +++ /dev/null @@ -1,60 +0,0 @@ -package org.argeo.jcr; - -import java.io.IOException; -import java.io.InputStream; - -import javax.jcr.Binary; -import javax.jcr.Property; -import javax.jcr.RepositoryException; - -/** - * A {@link Binary} wrapper implementing {@link AutoCloseable} for ease of use - * in try/catch blocks. - */ -public class Bin implements Binary, AutoCloseable { - private final Binary wrappedBinary; - - public Bin(Property property) throws RepositoryException { - this(property.getBinary()); - } - - public Bin(Binary wrappedBinary) { - if (wrappedBinary == null) - throw new IllegalArgumentException("Wrapped binary cannot be null"); - this.wrappedBinary = wrappedBinary; - } - - // private static Binary getBinary(Property property) throws IOException { - // try { - // return property.getBinary(); - // } catch (RepositoryException e) { - // throw new IOException("Cannot get binary from property " + property, e); - // } - // } - - @Override - public void close() { - dispose(); - } - - @Override - public InputStream getStream() throws RepositoryException { - return wrappedBinary.getStream(); - } - - @Override - public int read(byte[] b, long position) throws IOException, RepositoryException { - return wrappedBinary.read(b, position); - } - - @Override - public long getSize() throws RepositoryException { - return wrappedBinary.getSize(); - } - - @Override - public void dispose() { - wrappedBinary.dispose(); - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/CollectionNodeIterator.java b/org.argeo.cms.jcr/src/org/argeo/jcr/CollectionNodeIterator.java deleted file mode 100644 index b4124eea5..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/CollectionNodeIterator.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.argeo.jcr; - -import java.util.Collection; -import java.util.Iterator; -import java.util.NoSuchElementException; - -import javax.jcr.Node; -import javax.jcr.NodeIterator; - -/** Wraps a collection of nodes in order to read it as a {@link NodeIterator} */ -public class CollectionNodeIterator implements NodeIterator { - private final Long collectionSize; - private final Iterator iterator; - private Integer position = 0; - - public CollectionNodeIterator(Collection nodes) { - super(); - this.collectionSize = (long) nodes.size(); - this.iterator = nodes.iterator(); - } - - public void skip(long skipNum) { - if (skipNum < 0) - throw new IllegalArgumentException( - "Skip count has to be positive: " + skipNum); - - for (long i = 0; i < skipNum; i++) { - if (!hasNext()) - throw new NoSuchElementException("Last element past (position=" - + getPosition() + ")"); - nextNode(); - } - } - - public long getSize() { - return collectionSize; - } - - public long getPosition() { - return position; - } - - public boolean hasNext() { - return iterator.hasNext(); - } - - public Object next() { - return nextNode(); - } - - public void remove() { - iterator.remove(); - } - - public Node nextNode() { - Node node = iterator.next(); - position++; - return node; - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/DefaultJcrListener.java b/org.argeo.cms.jcr/src/org/argeo/jcr/DefaultJcrListener.java deleted file mode 100644 index d873ef652..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/DefaultJcrListener.java +++ /dev/null @@ -1,77 +0,0 @@ -package org.argeo.jcr; - -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.observation.ObservationManager; - -import org.argeo.api.cms.CmsLog; - -/** To be overridden */ -public class DefaultJcrListener implements EventListener { - private final static CmsLog log = CmsLog.getLog(DefaultJcrListener.class); - private Session session; - private String path = "/"; - private Boolean deep = true; - - public void start() { - try { - addEventListener(session().getWorkspace().getObservationManager()); - if (log.isDebugEnabled()) - log.debug("Registered JCR event listener on " + path); - } catch (RepositoryException e) { - throw new JcrException("Cannot register event listener", e); - } - } - - public void stop() { - try { - session().getWorkspace().getObservationManager() - .removeEventListener(this); - if (log.isDebugEnabled()) - log.debug("Unregistered JCR event listener on " + path); - } catch (RepositoryException e) { - throw new JcrException("Cannot unregister event listener", e); - } - } - - /** Default is listen to all events */ - protected Integer getEvents() { - return Event.NODE_ADDED | Event.NODE_REMOVED | Event.PROPERTY_ADDED - | Event.PROPERTY_CHANGED | Event.PROPERTY_REMOVED; - } - - /** To be overidden */ - public void onEvent(EventIterator events) { - while (events.hasNext()) { - Event event = events.nextEvent(); - log.debug(event); - } - } - - /** To be overidden */ - protected void addEventListener(ObservationManager observationManager) - throws RepositoryException { - observationManager.addEventListener(this, getEvents(), path, deep, - null, null, false); - } - - private Session session() { - return session; - } - - public void setPath(String path) { - this.path = path; - } - - public void setDeep(Boolean deep) { - this.deep = deep; - } - - public void setSession(Session session) { - this.session = session; - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/Jcr.java b/org.argeo.cms.jcr/src/org/argeo/jcr/Jcr.java deleted file mode 100644 index bf5de1260..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/Jcr.java +++ /dev/null @@ -1,985 +0,0 @@ -package org.argeo.jcr; - -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.math.BigDecimal; -import java.text.MessageFormat; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Calendar; -import java.util.Collections; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.Iterator; -import java.util.List; - -import javax.jcr.Binary; -import javax.jcr.ItemNotFoundException; -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.Property; -import javax.jcr.PropertyType; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.Value; -import javax.jcr.Workspace; -import javax.jcr.nodetype.NodeType; -import javax.jcr.query.Query; -import javax.jcr.query.QueryManager; -import javax.jcr.security.Privilege; -import javax.jcr.version.Version; -import javax.jcr.version.VersionHistory; -import javax.jcr.version.VersionIterator; -import javax.jcr.version.VersionManager; - -import org.apache.commons.io.IOUtils; - -/** - * Utility class whose purpose is to make using JCR less verbose by - * systematically using unchecked exceptions and returning null - * when something is not found. This is especially useful when writing user - * interfaces (such as with SWT) where listeners and callbacks expect unchecked - * exceptions. Loosely inspired by Java's Files singleton. - */ -public class Jcr { - /** - * The name of a node which will be serialized as XML text, as per section 7.3.1 - * of the JCR 2.0 specifications. - */ - public final static String JCR_XMLTEXT = "jcr:xmltext"; - /** - * The name of a property which will be serialized as XML text, as per section - * 7.3.1 of the JCR 2.0 specifications. - */ - public final static String JCR_XMLCHARACTERS = "jcr:xmlcharacters"; - /** - * jcr:name, when used in another context than - * {@link Property#JCR_NAME}, typically to name a node rather than a property. - */ - public final static String JCR_NAME = "jcr:name"; - /** - * jcr:path, when used in another context than - * {@link Property#JCR_PATH}, typically to name a node rather than a property. - */ - public final static String JCR_PATH = "jcr:path"; - /** - * jcr:primaryType with prefix instead of namespace (as in - * {@link Property#JCR_PRIMARY_TYPE}. - */ - public final static String JCR_PRIMARY_TYPE = "jcr:primaryType"; - /** - * jcr:mixinTypes with prefix instead of namespace (as in - * {@link Property#JCR_MIXIN_TYPES}. - */ - public final static String JCR_MIXIN_TYPES = "jcr:mixinTypes"; - /** - * jcr:uuid with prefix instead of namespace (as in - * {@link Property#JCR_UUID}. - */ - public final static String JCR_UUID = "jcr:uuid"; - /** - * jcr:created with prefix instead of namespace (as in - * {@link Property#JCR_CREATED}. - */ - public final static String JCR_CREATED = "jcr:created"; - /** - * jcr:createdBy with prefix instead of namespace (as in - * {@link Property#JCR_CREATED_BY}. - */ - public final static String JCR_CREATED_BY = "jcr:createdBy"; - /** - * jcr:lastModified with prefix instead of namespace (as in - * {@link Property#JCR_LAST_MODIFIED}. - */ - public final static String JCR_LAST_MODIFIED = "jcr:lastModified"; - /** - * jcr:lastModifiedBy with prefix instead of namespace (as in - * {@link Property#JCR_LAST_MODIFIED_BY}. - */ - public final static String JCR_LAST_MODIFIED_BY = "jcr:lastModifiedBy"; - - /** - * @see Node#isNodeType(String) - * @throws JcrException caused by {@link RepositoryException} - */ - public static boolean isNodeType(Node node, String nodeTypeName) { - try { - return node.isNodeType(nodeTypeName); - } catch (RepositoryException e) { - throw new JcrException("Cannot get whether " + node + " is of type " + nodeTypeName, e); - } - } - - /** - * @see Node#hasNodes() - * @throws JcrException caused by {@link RepositoryException} - */ - public static boolean hasNodes(Node node) { - try { - return node.hasNodes(); - } catch (RepositoryException e) { - throw new JcrException("Cannot get whether " + node + " has children.", e); - } - } - - /** - * @see Node#getParent() - * @throws JcrException caused by {@link RepositoryException} - */ - public static Node getParent(Node node) { - try { - return isRoot(node) ? null : node.getParent(); - } catch (RepositoryException e) { - throw new JcrException("Cannot get parent of " + node, e); - } - } - - /** - * @see Node#getParent() - * @throws JcrException caused by {@link RepositoryException} - */ - public static String getParentPath(Node node) { - return getPath(getParent(node)); - } - - /** - * Whether this node is the root node. - * - * @throws JcrException caused by {@link RepositoryException} - */ - public static boolean isRoot(Node node) { - try { - return node.getDepth() == 0; - } catch (RepositoryException e) { - throw new JcrException("Cannot get depth of " + node, e); - } - } - - /** - * @see Node#getPath() - * @throws JcrException caused by {@link RepositoryException} - */ - public static String getPath(Node node) { - try { - return node.getPath(); - } catch (RepositoryException e) { - throw new JcrException("Cannot get path of " + node, e); - } - } - - /** - * @see Node#getSession() - * @see Session#getWorkspace() - * @see Workspace#getName() - */ - public static String getWorkspaceName(Node node) { - return session(node).getWorkspace().getName(); - } - - /** - * @see Node#getIdentifier() - * @throws JcrException caused by {@link RepositoryException} - */ - public static String getIdentifier(Node node) { - try { - return node.getIdentifier(); - } catch (RepositoryException e) { - throw new JcrException("Cannot get identifier of " + node, e); - } - } - - /** - * @see Node#getName() - * @throws JcrException caused by {@link RepositoryException} - */ - public static String getName(Node node) { - try { - return node.getName(); - } catch (RepositoryException e) { - throw new JcrException("Cannot get name of " + node, e); - } - } - - /** - * Returns the node name with its current index (useful for re-ordering). - * - * @see Node#getName() - * @see Node#getIndex() - * @throws JcrException caused by {@link RepositoryException} - */ - public static String getIndexedName(Node node) { - try { - return node.getName() + "[" + node.getIndex() + "]"; - } catch (RepositoryException e) { - throw new JcrException("Cannot get name of " + node, e); - } - } - - /** - * @see Node#getProperty(String) - * @throws JcrException caused by {@link RepositoryException} - */ - public static Property getProperty(Node node, String property) { - try { - if (node.hasProperty(property)) - return node.getProperty(property); - else - return null; - } catch (RepositoryException e) { - throw new JcrException("Cannot get property " + property + " of " + node, e); - } - } - - /** - * @see Node#getIndex() - * @throws JcrException caused by {@link RepositoryException} - */ - public static int getIndex(Node node) { - try { - return node.getIndex(); - } catch (RepositoryException e) { - throw new JcrException("Cannot get index of " + node, e); - } - } - - /** - * If node has mixin {@link NodeType#MIX_TITLE}, return - * {@link Property#JCR_TITLE}, otherwise return {@link #getName(Node)}. - */ - public static String getTitle(Node node) { - if (Jcr.isNodeType(node, NodeType.MIX_TITLE)) - return get(node, Property.JCR_TITLE); - else - return Jcr.getName(node); - } - - /** Accesses a {@link NodeIterator} as an {@link Iterable}. */ - @SuppressWarnings("unchecked") - public static Iterable iterate(NodeIterator nodeIterator) { - return new Iterable() { - - @Override - public Iterator iterator() { - return nodeIterator; - } - }; - } - - /** - * @return the children as an {@link Iterable} for use in for-each llops. - * @see Node#getNodes() - * @throws JcrException caused by {@link RepositoryException} - */ - public static Iterable nodes(Node node) { - try { - return iterate(node.getNodes()); - } catch (RepositoryException e) { - throw new JcrException("Cannot get children of " + node, e); - } - } - - /** - * @return the children as a (possibly empty) {@link List}. - * @see Node#getNodes() - * @throws JcrException caused by {@link RepositoryException} - */ - public static List getNodes(Node node) { - List nodes = new ArrayList<>(); - try { - if (node.hasNodes()) { - NodeIterator nit = node.getNodes(); - while (nit.hasNext()) - nodes.add(nit.nextNode()); - return nodes; - } else - return nodes; - } catch (RepositoryException e) { - throw new JcrException("Cannot get children of " + node, e); - } - } - - /** - * @return the child or null if not found - * @see Node#getNode(String) - * @throws JcrException caused by {@link RepositoryException} - */ - public static Node getNode(Node node, String child) { - try { - if (node.hasNode(child)) - return node.getNode(child); - else - return null; - } catch (RepositoryException e) { - throw new JcrException("Cannot get child of " + node, e); - } - } - - /** - * @return the node at this path or null if not found - * @see Session#getNode(String) - * @throws JcrException caused by {@link RepositoryException} - */ - public static Node getNode(Session session, String path) { - try { - if (session.nodeExists(path)) - return session.getNode(path); - else - return null; - } catch (RepositoryException e) { - throw new JcrException("Cannot get node " + path, e); - } - } - - /** - * Add a node to this parent, setting its primary type and its mixins. - * - * @param parent the parent node - * @param name the name of the node, if null, the primary - * type will be used (typically for XML structures) - * @param primaryType the primary type, if null - * {@link NodeType#NT_UNSTRUCTURED} will be used. - * @param mixins the mixins - * @return the created node - * @see Node#addNode(String, String) - * @see Node#addMixin(String) - */ - public static Node addNode(Node parent, String name, String primaryType, String... mixins) { - if (name == null && primaryType == null) - throw new IllegalArgumentException("Both node name and primary type cannot be null"); - try { - Node newNode = parent.addNode(name == null ? primaryType : name, - primaryType == null ? NodeType.NT_UNSTRUCTURED : primaryType); - for (String mixin : mixins) { - newNode.addMixin(mixin); - } - return newNode; - } catch (RepositoryException e) { - throw new JcrException("Cannot add node " + name + " to " + parent, e); - } - } - - /** - * Add an {@link NodeType#NT_BASE} node to this parent. - * - * @param parent the parent node - * @param name the name of the node, cannot be null - * @return the created node - * - * @see Node#addNode(String) - */ - public static Node addNode(Node parent, String name) { - if (name == null) - throw new IllegalArgumentException("Node name cannot be null"); - try { - Node newNode = parent.addNode(name); - return newNode; - } catch (RepositoryException e) { - throw new JcrException("Cannot add node " + name + " to " + parent, e); - } - } - - /** - * Add mixins to a node. - * - * @param node the node - * @param mixins the mixins - * @return the created node - * @see Node#addMixin(String) - */ - public static void addMixin(Node node, String... mixins) { - try { - for (String mixin : mixins) { - node.addMixin(mixin); - } - } catch (RepositoryException e) { - throw new JcrException("Cannot add mixins " + Arrays.asList(mixins) + " to " + node, e); - } - } - - /** - * Removes this node. - * - * @see Node#remove() - */ - public static void remove(Node node) { - try { - node.remove(); - } catch (RepositoryException e) { - throw new JcrException("Cannot remove node " + node, e); - } - } - - /** - * @return the node with htis id or null if not found - * @see Session#getNodeByIdentifier(String) - * @throws JcrException caused by {@link RepositoryException} - */ - public static Node getNodeById(Session session, String id) { - try { - return session.getNodeByIdentifier(id); - } catch (ItemNotFoundException e) { - return null; - } catch (RepositoryException e) { - throw new JcrException("Cannot get node with id " + id, e); - } - } - - /** - * Set a property to the given value, or remove it if the value is - * null. - * - * @throws JcrException caused by {@link RepositoryException} - */ - public static void set(Node node, String property, Object value) { - try { - if (!node.hasProperty(property)) { - if (value != null) { - if (value instanceof List) {// multiple - List lst = (List) value; - String[] values = new String[lst.size()]; - for (int i = 0; i < lst.size(); i++) { - values[i] = lst.get(i).toString(); - } - node.setProperty(property, values); - } else { - node.setProperty(property, value.toString()); - } - } - return; - } - Property prop = node.getProperty(property); - if (value == null) { - prop.remove(); - return; - } - - // multiple - if (value instanceof List) { - List lst = (List) value; - String[] values = new String[lst.size()]; - // TODO better cast? - for (int i = 0; i < lst.size(); i++) { - values[i] = lst.get(i).toString(); - } - if (!prop.isMultiple()) - prop.remove(); - node.setProperty(property, values); - return; - } - - // single - if (prop.isMultiple()) { - prop.remove(); - node.setProperty(property, value.toString()); - return; - } - - if (value instanceof String) - prop.setValue((String) value); - else if (value instanceof Long) - prop.setValue((Long) value); - else if (value instanceof Integer) - prop.setValue(((Integer) value).longValue()); - else if (value instanceof Double) - prop.setValue((Double) value); - else if (value instanceof Float) - prop.setValue(((Float) value).doubleValue()); - else if (value instanceof Calendar) - prop.setValue((Calendar) value); - else if (value instanceof BigDecimal) - prop.setValue((BigDecimal) value); - else if (value instanceof Boolean) - prop.setValue((Boolean) value); - else if (value instanceof byte[]) - JcrUtils.setBinaryAsBytes(prop, (byte[]) value); - else if (value instanceof Instant) { - Instant instant = (Instant) value; - GregorianCalendar calendar = new GregorianCalendar(); - calendar.setTime(Date.from(instant)); - prop.setValue(calendar); - } else // try with toString() - prop.setValue(value.toString()); - } catch (RepositoryException e) { - throw new JcrException("Cannot set property " + property + " of " + node + " to " + value, e); - } - } - - /** - * Get property as {@link String}. - * - * @return the value of - * {@link Node#getProperty(String)}.{@link Property#getString()} or - * null if the property does not exist. - * @throws JcrException caused by {@link RepositoryException} - */ - public static String get(Node node, String property) { - return get(node, property, null); - } - - /** - * Get property as a {@link String}. If the property is multiple it returns the - * first value. - * - * @return the value of - * {@link Node#getProperty(String)}.{@link Property#getString()} or - * defaultValue if the property does not exist. - * @throws JcrException caused by {@link RepositoryException} - */ - public static String get(Node node, String property, String defaultValue) { - try { - if (node.hasProperty(property)) { - Property p = node.getProperty(property); - if (!p.isMultiple()) - return p.getString(); - else { - Value[] values = p.getValues(); - if (values.length == 0) - return defaultValue; - else - return values[0].getString(); - } - } else - return defaultValue; - } catch (RepositoryException e) { - throw new JcrException("Cannot retrieve property " + property + " from " + node, e); - } - } - - /** - * Get property as a {@link Value}. - * - * @return {@link Node#getProperty(String)} or null if the property - * does not exist. - * @throws JcrException caused by {@link RepositoryException} - */ - public static Value getValue(Node node, String property) { - try { - if (node.hasProperty(property)) - return node.getProperty(property).getValue(); - else - return null; - } catch (RepositoryException e) { - throw new JcrException("Cannot retrieve property " + property + " from " + node, e); - } - } - - /** - * Get property doing a best effort to cast it as the target object. - * - * @return the value of {@link Node#getProperty(String)} or - * defaultValue if the property does not exist. - * @throws IllegalArgumentException if the value could not be cast - * @throws JcrException in case of unexpected - * {@link RepositoryException} - */ - @SuppressWarnings("unchecked") - public static T getAs(Node node, String property, T defaultValue) { - try { - // TODO deal with multiple - if (node.hasProperty(property)) { - Property p = node.getProperty(property); - try { - if (p.isMultiple()) { - throw new UnsupportedOperationException("Multiple values properties are not supported"); - } - Value value = p.getValue(); - return (T) get(value); - } catch (ClassCastException e) { - throw new IllegalArgumentException( - "Cannot cast property of type " + PropertyType.nameFromValue(p.getType()), e); - } - } else { - return defaultValue; - } - } catch (RepositoryException e) { - throw new JcrException("Cannot retrieve property " + property + " from " + node, e); - } - } - - public static T getAs(Node node, String property, Class clss) { - if(String.class.isAssignableFrom(clss)) { - return (T)get(node,property); - } else if(Long.class.isAssignableFrom(clss)) { - return (T)get(node,property); - }else { - throw new IllegalArgumentException("Unsupported format "+clss); - } - } - - /** - * Get a multiple property as a list, doing a best effort to cast it as the - * target list. - * - * @return the value of {@link Node#getProperty(String)}. - * @throws IllegalArgumentException if the value could not be cast - * @throws JcrException in case of unexpected - * {@link RepositoryException} - */ - public static List getMultiple(Node node, String property) { - try { - if (node.hasProperty(property)) { - Property p = node.getProperty(property); - return getMultiple(p); - } else { - return null; - } - } catch (RepositoryException e) { - throw new JcrException("Cannot retrieve multiple values property " + property + " from " + node, e); - } - } - - /** - * Get a multiple property as a list, doing a best effort to cast it as the - * target list. - */ - @SuppressWarnings("unchecked") - public static List getMultiple(Property p) { - try { - List res = new ArrayList<>(); - if (!p.isMultiple()) { - res.add((T) get(p.getValue())); - return res; - } - Value[] values = p.getValues(); - for (Value value : values) { - res.add((T) get(value)); - } - return res; - } catch (ClassCastException | RepositoryException e) { - throw new IllegalArgumentException("Cannot get property " + p, e); - } - } - - /** Cast a {@link Value} to a standard Java object. */ - public static Object get(Value value) { - Binary binary = null; - try { - switch (value.getType()) { - case PropertyType.STRING: - return value.getString(); - case PropertyType.DOUBLE: - return (Double) value.getDouble(); - case PropertyType.LONG: - return (Long) value.getLong(); - case PropertyType.BOOLEAN: - return (Boolean) value.getBoolean(); - case PropertyType.DATE: - return value.getDate(); - case PropertyType.BINARY: - binary = value.getBinary(); - byte[] arr = null; - try (InputStream in = binary.getStream(); ByteArrayOutputStream out = new ByteArrayOutputStream();) { - IOUtils.copy(in, out); - arr = out.toByteArray(); - } catch (IOException e) { - throw new RuntimeException("Cannot read binary from " + value, e); - } - return arr; - default: - return value.getString(); - } - } catch (RepositoryException e) { - throw new JcrException("Cannot cast value from " + value, e); - } finally { - if (binary != null) - binary.dispose(); - } - } - - /** - * Retrieves the {@link Session} related to this node. - * - * @deprecated Use {@link #getSession(Node)} instead. - */ - @Deprecated - public static Session session(Node node) { - return getSession(node); - } - - /** Retrieves the {@link Session} related to this node. */ - public static Session getSession(Node node) { - try { - return node.getSession(); - } catch (RepositoryException e) { - throw new JcrException("Cannot retrieve session related to " + node, e); - } - } - - /** Retrieves the root node related to this session. */ - public static Node getRootNode(Session session) { - try { - return session.getRootNode(); - } catch (RepositoryException e) { - throw new JcrException("Cannot get root node for " + session, e); - } - } - - /** Whether this item exists. */ - public static boolean itemExists(Session session, String path) { - try { - return session.itemExists(path); - } catch (RepositoryException e) { - throw new JcrException("Cannot check whether " + path + " exists", e); - } - } - - /** - * Saves the {@link Session} related to this node. Note that all other unrelated - * modifications in this session will also be saved. - */ - public static void save(Node node) { - try { - Session session = node.getSession(); -// if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) { -// set(node, Property.JCR_LAST_MODIFIED, Instant.now()); -// set(node, Property.JCR_LAST_MODIFIED_BY, session.getUserID()); -// } - if (session.hasPendingChanges()) - session.save(); - } catch (RepositoryException e) { - throw new JcrException("Cannot save session related to " + node + " in workspace " - + session(node).getWorkspace().getName(), e); - } - } - - /** Login to a JCR repository. */ - public static Session login(Repository repository, String workspace) { - try { - return repository.login(workspace); - } catch (RepositoryException e) { - throw new IllegalArgumentException("Cannot login to repository", e); - } - } - - /** Safely and silently logs out a session. */ - public static void logout(Session session) { - try { - if (session != null) - if (session.isLive()) - session.logout(); - } catch (Exception e) { - // silent - } - } - - /** Safely and silently logs out the underlying session. */ - public static void logout(Node node) { - Jcr.logout(session(node)); - } - - /* - * SECURITY - */ - /** - * Add a single privilege to a node. - * - * @see Privilege - */ - public static void addPrivilege(Node node, String principal, String privilege) { - try { - Session session = node.getSession(); - JcrUtils.addPrivilege(session, node.getPath(), principal, privilege); - } catch (RepositoryException e) { - throw new JcrException("Cannot add privilege " + privilege + " to " + node, e); - } - } - - /* - * VERSIONING - */ - /** Get checked out status. */ - public static boolean isCheckedOut(Node node) { - try { - return node.isCheckedOut(); - } catch (RepositoryException e) { - throw new JcrException("Cannot retrieve checked out status of " + node, e); - } - } - - /** @see VersionManager#checkpoint(String) */ - public static void checkpoint(Node node) { - try { - versionManager(node).checkpoint(node.getPath()); - } catch (RepositoryException e) { - throw new JcrException("Cannot check in " + node, e); - } - } - - /** @see VersionManager#checkin(String) */ - public static void checkin(Node node) { - try { - versionManager(node).checkin(node.getPath()); - } catch (RepositoryException e) { - throw new JcrException("Cannot check in " + node, e); - } - } - - /** @see VersionManager#checkout(String) */ - public static void checkout(Node node) { - try { - versionManager(node).checkout(node.getPath()); - } catch (RepositoryException e) { - throw new JcrException("Cannot check out " + node, e); - } - } - - /** Get the {@link VersionManager} related to this node. */ - public static VersionManager versionManager(Node node) { - try { - return node.getSession().getWorkspace().getVersionManager(); - } catch (RepositoryException e) { - throw new JcrException("Cannot get version manager from " + node, e); - } - } - - /** Get the {@link VersionHistory} related to this node. */ - public static VersionHistory getVersionHistory(Node node) { - try { - return versionManager(node).getVersionHistory(node.getPath()); - } catch (RepositoryException e) { - throw new JcrException("Cannot get version history from " + node, e); - } - } - - /** - * The linear versions of this version history in reverse order and without the - * root version. - */ - public static List getLinearVersions(VersionHistory versionHistory) { - try { - List lst = new ArrayList<>(); - VersionIterator vit = versionHistory.getAllLinearVersions(); - while (vit.hasNext()) - lst.add(vit.nextVersion()); - lst.remove(0); - Collections.reverse(lst); - return lst; - } catch (RepositoryException e) { - throw new JcrException("Cannot get linear versions from " + versionHistory, e); - } - } - - /** The frozen node related to this {@link Version}. */ - public static Node getFrozenNode(Version version) { - try { - return version.getFrozenNode(); - } catch (RepositoryException e) { - throw new JcrException("Cannot get frozen node from " + version, e); - } - } - - /** Get the base {@link Version} related to this node. */ - public static Version getBaseVersion(Node node) { - try { - return versionManager(node).getBaseVersion(node.getPath()); - } catch (RepositoryException e) { - throw new JcrException("Cannot get base version from " + node, e); - } - } - - /* - * FILES - */ - /** - * Returns the size of this file. - * - * @see NodeType#NT_FILE - */ - public static long getFileSize(Node fileNode) { - try { - if (!fileNode.isNodeType(NodeType.NT_FILE)) - throw new IllegalArgumentException(fileNode + " must be a file."); - return getBinarySize(fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary()); - } catch (RepositoryException e) { - throw new JcrException("Cannot get file size of " + fileNode, e); - } - } - - /** Returns the size of this {@link Binary}. */ - public static long getBinarySize(Binary binaryArg) { - try { - try (Bin binary = new Bin(binaryArg)) { - return binary.getSize(); - } - } catch (RepositoryException e) { - throw new JcrException("Cannot get file size of binary " + binaryArg, e); - } - } - - // QUERY - /** Creates a JCR-SQL2 query using {@link MessageFormat}. */ - public static Query createQuery(QueryManager qm, String sql, Object... args) { - // fix single quotes - sql = sql.replaceAll("'", "''"); - String query = MessageFormat.format(sql, args); - try { - return qm.createQuery(query, Query.JCR_SQL2); - } catch (RepositoryException e) { - throw new JcrException("Cannot create JCR-SQL2 query from " + query, e); - } - } - - /** Executes a JCR-SQL2 query using {@link MessageFormat}. */ - public static NodeIterator executeQuery(QueryManager qm, String sql, Object... args) { - Query query = createQuery(qm, sql, args); - try { - return query.execute().getNodes(); - } catch (RepositoryException e) { - throw new JcrException("Cannot execute query " + sql + " with arguments " + Arrays.asList(args), e); - } - } - - /** Executes a JCR-SQL2 query using {@link MessageFormat}. */ - public static NodeIterator executeQuery(Session session, String sql, Object... args) { - QueryManager queryManager; - try { - queryManager = session.getWorkspace().getQueryManager(); - } catch (RepositoryException e) { - throw new JcrException("Cannot get query manager from session " + session, e); - } - return executeQuery(queryManager, sql, args); - } - - /** - * Executes a JCR-SQL2 query using {@link MessageFormat}, which must return a - * single node at most. - * - * @return the node or null if not found. - */ - public static Node getNode(QueryManager qm, String sql, Object... args) { - NodeIterator nit = executeQuery(qm, sql, args); - if (nit.hasNext()) { - Node node = nit.nextNode(); - if (nit.hasNext()) - throw new IllegalStateException( - "Query " + sql + " with arguments " + Arrays.asList(args) + " returned more than one node."); - return node; - } else { - return null; - } - } - - /** - * Executes a JCR-SQL2 query using {@link MessageFormat}, which must return a - * single node at most. - * - * @return the node or null if not found. - */ - public static Node getNode(Session session, String sql, Object... args) { - QueryManager queryManager; - try { - queryManager = session.getWorkspace().getQueryManager(); - } catch (RepositoryException e) { - throw new JcrException("Cannot get query manager from session " + session, e); - } - return getNode(queryManager, sql, args); - } - - /** Singleton. */ - private Jcr() { - - } -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/JcrAuthorizations.java b/org.argeo.cms.jcr/src/org/argeo/jcr/JcrAuthorizations.java deleted file mode 100644 index 351929f8d..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/JcrAuthorizations.java +++ /dev/null @@ -1,207 +0,0 @@ -package org.argeo.jcr; - -import java.security.Principal; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.security.AccessControlManager; -import javax.jcr.security.Privilege; -import javax.naming.InvalidNameException; -import javax.naming.ldap.LdapName; - -/** Apply authorizations to a JCR repository. */ -public class JcrAuthorizations implements Runnable { - // private final static Log log = - // LogFactory.getLog(JcrAuthorizations.class); - - private Repository repository; - private String workspace = null; - - private String securityWorkspace = "security"; - - /** - * key := privilege1,privilege2/path/to/node
- * value := group1,group2,user1 - */ - private Map principalPrivileges = new HashMap(); - - public void run() { - String currentWorkspace = workspace; - Session session = null; - try { - if (workspace != null && workspace.equals("*")) { - session = repository.login(); - String[] workspaces = session.getWorkspace().getAccessibleWorkspaceNames(); - JcrUtils.logoutQuietly(session); - for (String wksp : workspaces) { - currentWorkspace = wksp; - if (currentWorkspace.equals(securityWorkspace)) - continue; - session = repository.login(currentWorkspace); - initAuthorizations(session); - JcrUtils.logoutQuietly(session); - } - } else { - session = repository.login(workspace); - initAuthorizations(session); - } - } catch (RepositoryException e) { - JcrUtils.discardQuietly(session); - throw new JcrException( - "Cannot set authorizations " + principalPrivileges + " on workspace " + currentWorkspace, e); - } finally { - JcrUtils.logoutQuietly(session); - } - } - - protected void processWorkspace(String workspace) { - Session session = null; - try { - session = repository.login(workspace); - initAuthorizations(session); - } catch (RepositoryException e) { - JcrUtils.discardQuietly(session); - throw new JcrException( - "Cannot set authorizations " + principalPrivileges + " on repository " + repository, e); - } finally { - JcrUtils.logoutQuietly(session); - } - } - - /** @deprecated call {@link #run()} instead. */ - @Deprecated - public void init() { - run(); - } - - protected void initAuthorizations(Session session) throws RepositoryException { - AccessControlManager acm = session.getAccessControlManager(); - - for (String privileges : principalPrivileges.keySet()) { - String path = null; - int slashIndex = privileges.indexOf('/'); - if (slashIndex == 0) { - throw new IllegalArgumentException("Privilege " + privileges + " badly formatted it starts with /"); - } else if (slashIndex > 0) { - path = privileges.substring(slashIndex); - privileges = privileges.substring(0, slashIndex); - } - - if (path == null) - path = "/"; - - List privs = new ArrayList(); - for (String priv : privileges.split(",")) { - privs.add(acm.privilegeFromName(priv)); - } - - String principalNames = principalPrivileges.get(privileges); - try { - new LdapName(principalNames); - // TODO differentiate groups and users ? - Principal principal = getOrCreatePrincipal(session, principalNames); - JcrUtils.addPrivileges(session, path, principal, privs); - } catch (InvalidNameException e) { - for (String principalName : principalNames.split(",")) { - Principal principal = getOrCreatePrincipal(session, principalName); - JcrUtils.addPrivileges(session, path, principal, privs); - // if (log.isDebugEnabled()) { - // StringBuffer privBuf = new StringBuffer(); - // for (Privilege priv : privs) - // privBuf.append(priv.getName()); - // log.debug("Added privileges " + privBuf + " to " - // + principal.getName() + " on " + path + " in '" - // + session.getWorkspace().getName() + "'"); - // } - } - } - } - - // if (log.isDebugEnabled()) - // log.debug("JCR authorizations applied on '" - // + session.getWorkspace().getName() + "'"); - } - - /** - * Returns a {@link SimplePrincipal}, does not check whether it exists since - * such capabilities is not provided by the standard JCR API. Can be - * overridden to provide smarter handling - */ - protected Principal getOrCreatePrincipal(Session session, String principalName) throws RepositoryException { - return new SimplePrincipal(principalName); - } - - // public static void addPrivileges(Session session, Principal principal, - // String path, List privs) throws RepositoryException { - // AccessControlManager acm = session.getAccessControlManager(); - // // search for an access control list - // AccessControlList acl = null; - // AccessControlPolicyIterator policyIterator = acm - // .getApplicablePolicies(path); - // if (policyIterator.hasNext()) { - // while (policyIterator.hasNext()) { - // AccessControlPolicy acp = policyIterator - // .nextAccessControlPolicy(); - // if (acp instanceof AccessControlList) - // acl = ((AccessControlList) acp); - // } - // } else { - // AccessControlPolicy[] existingPolicies = acm.getPolicies(path); - // for (AccessControlPolicy acp : existingPolicies) { - // if (acp instanceof AccessControlList) - // acl = ((AccessControlList) acp); - // } - // } - // - // if (acl != null) { - // acl.addAccessControlEntry(principal, - // privs.toArray(new Privilege[privs.size()])); - // acm.setPolicy(path, acl); - // session.save(); - // if (log.isDebugEnabled()) { - // StringBuffer buf = new StringBuffer(""); - // for (int i = 0; i < privs.size(); i++) { - // if (i != 0) - // buf.append(','); - // buf.append(privs.get(i).getName()); - // } - // log.debug("Added privilege(s) '" + buf + "' to '" - // + principal.getName() + "' on " + path - // + " from workspace '" - // + session.getWorkspace().getName() + "'"); - // } - // } else { - // throw new ArgeoJcrException("Don't know how to apply privileges " - // + privs + " to " + principal + " on " + path - // + " from workspace '" + session.getWorkspace().getName() - // + "'"); - // } - // } - - @Deprecated - public void setGroupPrivileges(Map groupPrivileges) { - this.principalPrivileges = groupPrivileges; - } - - public void setPrincipalPrivileges(Map principalPrivileges) { - this.principalPrivileges = principalPrivileges; - } - - public void setRepository(Repository repository) { - this.repository = repository; - } - - public void setWorkspace(String workspace) { - this.workspace = workspace; - } - - public void setSecurityWorkspace(String securityWorkspace) { - this.securityWorkspace = securityWorkspace; - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/JcrCallback.java b/org.argeo.cms.jcr/src/org/argeo/jcr/JcrCallback.java deleted file mode 100644 index efbaabe82..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/JcrCallback.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.argeo.jcr; - -import java.util.function.Function; - -import javax.jcr.Session; - -/** An arbitrary execution on a JCR session, optionally returning a result. */ -@FunctionalInterface -public interface JcrCallback extends Function { - /** @deprecated Use {@link #apply(Session)} instead. */ - @Deprecated - public default Object execute(Session session) { - return apply(session); - } -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/JcrException.java b/org.argeo.cms.jcr/src/org/argeo/jcr/JcrException.java deleted file mode 100644 index c77874376..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/JcrException.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.argeo.jcr; - -import javax.jcr.RepositoryException; - -/** - * Wraps a {@link RepositoryException} in a {@link RuntimeException}. - */ -public class JcrException extends IllegalStateException { - private static final long serialVersionUID = -4530350094877964989L; - - public JcrException(String message, RepositoryException e) { - super(message, e); - } - - public JcrException(RepositoryException e) { - super(e); - } - - public RepositoryException getRepositoryCause() { - return (RepositoryException) getCause(); - } -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/JcrMonitor.java b/org.argeo.cms.jcr/src/org/argeo/jcr/JcrMonitor.java deleted file mode 100644 index 71cf961e0..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/JcrMonitor.java +++ /dev/null @@ -1,87 +0,0 @@ -package org.argeo.jcr; - - -/** - * Simple monitor abstraction. Inspired by Eclipse IProgressMOnitor, but without - * dependency to it. - */ -public interface JcrMonitor { - /** - * Constant indicating an unknown amount of work. - */ - public final static int UNKNOWN = -1; - - /** - * Notifies that the main task is beginning. This must only be called once - * on a given progress monitor instance. - * - * @param name - * the name (or description) of the main task - * @param totalWork - * the total number of work units into which the main task is - * been subdivided. If the value is UNKNOWN the - * implementation is free to indicate progress in a way which - * doesn't require the total number of work units in advance. - */ - public void beginTask(String name, int totalWork); - - /** - * Notifies that the work is done; that is, either the main task is - * completed or the user canceled it. This method may be called more than - * once (implementations should be prepared to handle this case). - */ - public void done(); - - /** - * Returns whether cancelation of current operation has been requested. - * Long-running operations should poll to see if cancelation has been - * requested. - * - * @return true if cancellation has been requested, and - * false otherwise - * @see #setCanceled(boolean) - */ - public boolean isCanceled(); - - /** - * Sets the cancel state to the given value. - * - * @param value - * true indicates that cancelation has been - * requested (but not necessarily acknowledged); - * false clears this flag - * @see #isCanceled() - */ - public void setCanceled(boolean value); - - /** - * Sets the task name to the given value. This method is used to restore the - * task label after a nested operation was executed. Normally there is no - * need for clients to call this method. - * - * @param name - * the name (or description) of the main task - * @see #beginTask(java.lang.String, int) - */ - public void setTaskName(String name); - - /** - * Notifies that a subtask of the main task is beginning. Subtasks are - * optional; the main task might not have subtasks. - * - * @param name - * the name (or description) of the subtask - */ - public void subTask(String name); - - /** - * Notifies that a given number of work unit of the main task has been - * completed. Note that this amount represents an installment, as opposed to - * a cumulative amount of work done to date. - * - * @param work - * a non-negative number of work units just completed - */ - public void worked(int work); - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/JcrRepositoryWrapper.java b/org.argeo.cms.jcr/src/org/argeo/jcr/JcrRepositoryWrapper.java deleted file mode 100644 index 3228eee74..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/JcrRepositoryWrapper.java +++ /dev/null @@ -1,244 +0,0 @@ -package org.argeo.jcr; - -import java.io.InputStream; -import java.math.BigDecimal; -import java.util.Arrays; -import java.util.Calendar; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.jcr.Binary; -import javax.jcr.Credentials; -import javax.jcr.LoginException; -import javax.jcr.NoSuchWorkspaceException; -import javax.jcr.PropertyType; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.Value; -import javax.jcr.ValueFormatException; - -/** - * Wrapper around a JCR repository which allows to simplify configuration and - * intercept some actions. It exposes itself as a {@link Repository}. - */ -public abstract class JcrRepositoryWrapper implements Repository { - // private final static Log log = LogFactory - // .getLog(JcrRepositoryWrapper.class); - - // wrapped repository - private Repository repository; - - private Map additionalDescriptors = new HashMap<>(); - - private Boolean autocreateWorkspaces = false; - - public JcrRepositoryWrapper(Repository repository) { - setRepository(repository); - } - - /** - * Empty constructor - */ - public JcrRepositoryWrapper() { - } - - // /** Initializes */ - // public void init() { - // } - // - // /** Shutdown the repository */ - // public void destroy() throws Exception { - // } - - protected void putDescriptor(String key, String value) { - if (Arrays.asList(getRepository().getDescriptorKeys()).contains(key)) - throw new IllegalArgumentException("Descriptor key " + key + " is already defined in wrapped repository"); - if (value == null) - additionalDescriptors.remove(key); - else - additionalDescriptors.put(key, value); - } - - /* - * DELEGATED JCR REPOSITORY METHODS - */ - - public String getDescriptor(String key) { - if (additionalDescriptors.containsKey(key)) - return additionalDescriptors.get(key); - return getRepository().getDescriptor(key); - } - - public String[] getDescriptorKeys() { - if (additionalDescriptors.size() == 0) - return getRepository().getDescriptorKeys(); - List keys = Arrays.asList(getRepository().getDescriptorKeys()); - keys.addAll(additionalDescriptors.keySet()); - return keys.toArray(new String[keys.size()]); - } - - /** Central login method */ - public Session login(Credentials credentials, String workspaceName) - throws LoginException, NoSuchWorkspaceException, RepositoryException { - Session session; - try { - session = getRepository(workspaceName).login(credentials, workspaceName); - } catch (NoSuchWorkspaceException e) { - if (autocreateWorkspaces && workspaceName != null) - session = createWorkspaceAndLogsIn(credentials, workspaceName); - else - throw e; - } - processNewSession(session, workspaceName); - return session; - } - - public Session login() throws LoginException, RepositoryException { - return login(null, null); - } - - public Session login(Credentials credentials) throws LoginException, RepositoryException { - return login(credentials, null); - } - - public Session login(String workspaceName) throws LoginException, NoSuchWorkspaceException, RepositoryException { - return login(null, workspaceName); - } - - /** Called after a session has been created, does nothing by default. */ - protected void processNewSession(Session session, String workspaceName) { - } - - /** - * Wraps access to the repository, making sure it is available. - * - * @deprecated Use {@link #getDefaultRepository()} instead. - */ - @Deprecated - protected synchronized Repository getRepository() { - return getDefaultRepository(); - } - - protected synchronized Repository getDefaultRepository() { - return repository; - } - - protected synchronized Repository getRepository(String workspaceName) { - return getDefaultRepository(); - } - - /** - * Logs in to the default workspace, creates the required workspace, logs out, - * logs in to the required workspace. - */ - protected Session createWorkspaceAndLogsIn(Credentials credentials, String workspaceName) - throws RepositoryException { - if (workspaceName == null) - throw new IllegalArgumentException("No workspace specified."); - Session session = getRepository(workspaceName).login(credentials); - session.getWorkspace().createWorkspace(workspaceName); - session.logout(); - return getRepository(workspaceName).login(credentials, workspaceName); - } - - public boolean isStandardDescriptor(String key) { - return getRepository().isStandardDescriptor(key); - } - - public boolean isSingleValueDescriptor(String key) { - if (additionalDescriptors.containsKey(key)) - return true; - return getRepository().isSingleValueDescriptor(key); - } - - public Value getDescriptorValue(String key) { - if (additionalDescriptors.containsKey(key)) - return new StrValue(additionalDescriptors.get(key)); - return getRepository().getDescriptorValue(key); - } - - public Value[] getDescriptorValues(String key) { - return getRepository().getDescriptorValues(key); - } - - public synchronized void setRepository(Repository repository) { - this.repository = repository; - } - - public void setAutocreateWorkspaces(Boolean autocreateWorkspaces) { - this.autocreateWorkspaces = autocreateWorkspaces; - } - - protected static class StrValue implements Value { - private final String str; - - public StrValue(String str) { - this.str = str; - } - - @Override - public String getString() throws ValueFormatException, IllegalStateException, RepositoryException { - return str; - } - - @Override - public InputStream getStream() throws RepositoryException { - throw new UnsupportedOperationException(); - } - - @Override - public Binary getBinary() throws RepositoryException { - throw new UnsupportedOperationException(); - } - - @Override - public long getLong() throws ValueFormatException, RepositoryException { - try { - return Long.parseLong(str); - } catch (NumberFormatException e) { - throw new ValueFormatException("Cannot convert", e); - } - } - - @Override - public double getDouble() throws ValueFormatException, RepositoryException { - try { - return Double.parseDouble(str); - } catch (NumberFormatException e) { - throw new ValueFormatException("Cannot convert", e); - } - } - - @Override - public BigDecimal getDecimal() throws ValueFormatException, RepositoryException { - try { - return new BigDecimal(str); - } catch (NumberFormatException e) { - throw new ValueFormatException("Cannot convert", e); - } - } - - @Override - public Calendar getDate() throws ValueFormatException, RepositoryException { - throw new UnsupportedOperationException(); - } - - @Override - public boolean getBoolean() throws ValueFormatException, RepositoryException { - try { - return Boolean.parseBoolean(str); - } catch (NumberFormatException e) { - throw new ValueFormatException("Cannot convert", e); - } - } - - @Override - public int getType() { - return PropertyType.STRING; - } - - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/JcrUrlStreamHandler.java b/org.argeo.cms.jcr/src/org/argeo/jcr/JcrUrlStreamHandler.java deleted file mode 100644 index 82a65e7f1..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/JcrUrlStreamHandler.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.argeo.jcr; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.net.URLConnection; -import java.net.URLStreamHandler; - -import javax.jcr.Item; -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.PropertyType; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.nodetype.NodeType; - -/** URL stream handler able to deal with nt:file node and properties. NOT FINISHED */ -public class JcrUrlStreamHandler extends URLStreamHandler { - private final Session session; - - public JcrUrlStreamHandler(Session session) { - this.session = session; - } - - @Override - protected URLConnection openConnection(final URL u) throws IOException { - // TODO Auto-generated method stub - return new URLConnection(u) { - - @Override - public void connect() throws IOException { - String itemPath = u.getPath(); - try { - if (!session.itemExists(itemPath)) - throw new IOException("No item under " + itemPath); - - Item item = session.getItem(u.getPath()); - if (item.isNode()) { - // this should be a nt:file node - Node node = (Node) item; - if (!node.getPrimaryNodeType().isNodeType( - NodeType.NT_FILE)) - throw new IOException("Node " + node + " is not a " - + NodeType.NT_FILE); - - } else { - Property property = (Property) item; - if(property.getType()==PropertyType.BINARY){ - //Binary binary = property.getBinary(); - - } - } - } catch (RepositoryException e) { - IOException ioe = new IOException( - "Unexpected JCR exception"); - ioe.initCause(e); - throw ioe; - } - } - - @Override - public InputStream getInputStream() throws IOException { - // TODO Auto-generated method stub - return super.getInputStream(); - } - - }; - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/JcrUtils.java b/org.argeo.cms.jcr/src/org/argeo/jcr/JcrUtils.java deleted file mode 100644 index 3be8be184..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/JcrUtils.java +++ /dev/null @@ -1,1778 +0,0 @@ -package org.argeo.jcr; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.PipedInputStream; -import java.io.PipedOutputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.security.Principal; -import java.text.DateFormat; -import java.text.ParseException; -import java.time.Instant; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collections; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; - -import javax.jcr.Binary; -import javax.jcr.Credentials; -import javax.jcr.ImportUUIDBehavior; -import javax.jcr.NamespaceRegistry; -import javax.jcr.NoSuchWorkspaceException; -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.Property; -import javax.jcr.PropertyIterator; -import javax.jcr.PropertyType; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.Value; -import javax.jcr.Workspace; -import javax.jcr.nodetype.NoSuchNodeTypeException; -import javax.jcr.nodetype.NodeType; -import javax.jcr.observation.EventListener; -import javax.jcr.query.Query; -import javax.jcr.query.QueryResult; -import javax.jcr.security.AccessControlEntry; -import javax.jcr.security.AccessControlList; -import javax.jcr.security.AccessControlManager; -import javax.jcr.security.AccessControlPolicy; -import javax.jcr.security.AccessControlPolicyIterator; -import javax.jcr.security.Privilege; - -import org.apache.commons.io.IOUtils; - -/** Utility methods to simplify common JCR operations. */ -public class JcrUtils { - -// final private static Log log = LogFactory.getLog(JcrUtils.class); - - /** - * Not complete yet. See - * http://www.day.com/specs/jcr/2.0/3_Repository_Model.html#3.2.2%20Local - * %20Names - */ - public final static char[] INVALID_NAME_CHARACTERS = { '/', ':', '[', ']', '|', '*', /* invalid for XML: */ '<', - '>', '&' }; - - /** Prevents instantiation */ - private JcrUtils() { - } - - /** - * Queries one single node. - * - * @return one single node or null if none was found - * @throws JcrException if more than one node was found - */ - public static Node querySingleNode(Query query) { - NodeIterator nodeIterator; - try { - QueryResult queryResult = query.execute(); - nodeIterator = queryResult.getNodes(); - } catch (RepositoryException e) { - throw new JcrException("Cannot execute query " + query, e); - } - Node node; - if (nodeIterator.hasNext()) - node = nodeIterator.nextNode(); - else - return null; - - if (nodeIterator.hasNext()) - throw new IllegalArgumentException("Query returned more than one node."); - return node; - } - - /** Retrieves the node name from the provided path */ - public static String nodeNameFromPath(String path) { - if (path.equals("/")) - return ""; - if (path.charAt(0) != '/') - throw new IllegalArgumentException("Path " + path + " must start with a '/'"); - String pathT = path; - if (pathT.charAt(pathT.length() - 1) == '/') - pathT = pathT.substring(0, pathT.length() - 2); - - int index = pathT.lastIndexOf('/'); - return pathT.substring(index + 1); - } - - /** Retrieves the parent path of the provided path */ - public static String parentPath(String path) { - if (path.equals("/")) - throw new IllegalArgumentException("Root path '/' has no parent path"); - if (path.charAt(0) != '/') - throw new IllegalArgumentException("Path " + path + " must start with a '/'"); - String pathT = path; - if (pathT.charAt(pathT.length() - 1) == '/') - pathT = pathT.substring(0, pathT.length() - 2); - - int index = pathT.lastIndexOf('/'); - return pathT.substring(0, index); - } - - /** The provided data as a path ('/' at the end, not the beginning) */ - public static String dateAsPath(Calendar cal) { - return dateAsPath(cal, false); - } - - /** - * Creates a deep path based on a URL: - * http://subdomain.example.com/to/content?args becomes - * com/example/subdomain/to/content - */ - public static String urlAsPath(String url) { - try { - URL u = new URL(url); - StringBuffer path = new StringBuffer(url.length()); - // invert host - path.append(hostAsPath(u.getHost())); - // we don't put port since it may not always be there and may change - path.append(u.getPath()); - return path.toString(); - } catch (MalformedURLException e) { - throw new IllegalArgumentException("Cannot generate URL path for " + url, e); - } - } - - /** Set the {@link NodeType#NT_ADDRESS} properties based on this URL. */ - public static void urlToAddressProperties(Node node, String url) { - try { - URL u = new URL(url); - node.setProperty(Property.JCR_PROTOCOL, u.getProtocol()); - node.setProperty(Property.JCR_HOST, u.getHost()); - node.setProperty(Property.JCR_PORT, Integer.toString(u.getPort())); - node.setProperty(Property.JCR_PATH, normalizePath(u.getPath())); - } catch (RepositoryException e) { - throw new JcrException("Cannot set URL " + url + " as nt:address properties", e); - } catch (MalformedURLException e) { - throw new IllegalArgumentException("Cannot set URL " + url + " as nt:address properties", e); - } - } - - /** Build URL based on the {@link NodeType#NT_ADDRESS} properties. */ - public static String urlFromAddressProperties(Node node) { - try { - URL u = new URL(node.getProperty(Property.JCR_PROTOCOL).getString(), - node.getProperty(Property.JCR_HOST).getString(), - (int) node.getProperty(Property.JCR_PORT).getLong(), - node.getProperty(Property.JCR_PATH).getString()); - return u.toString(); - } catch (RepositoryException e) { - throw new JcrException("Cannot get URL from nt:address properties of " + node, e); - } catch (MalformedURLException e) { - throw new IllegalArgumentException("Cannot get URL from nt:address properties of " + node, e); - } - } - - /* - * PATH UTILITIES - */ - - /** - * Make sure that: starts with '/', do not end with '/', do not have '//' - */ - public static String normalizePath(String path) { - List tokens = tokenize(path); - StringBuffer buf = new StringBuffer(path.length()); - for (String token : tokens) { - buf.append('/'); - buf.append(token); - } - return buf.toString(); - } - - /** - * Creates a path from a FQDN, inverting the order of the component: - * www.argeo.org becomes org.argeo.www - */ - public static String hostAsPath(String host) { - StringBuffer path = new StringBuffer(host.length()); - String[] hostTokens = host.split("\\."); - for (int i = hostTokens.length - 1; i >= 0; i--) { - path.append(hostTokens[i]); - if (i != 0) - path.append('/'); - } - return path.toString(); - } - - /** - * Creates a path from a UUID (e.g. 6ebda899-217d-4bf1-abe4-2839085c8f3c becomes - * 6ebda899-217d/4bf1/abe4/2839085c8f3c/). '/' at the end, not the beginning - */ - public static String uuidAsPath(String uuid) { - StringBuffer path = new StringBuffer(uuid.length()); - String[] tokens = uuid.split("-"); - for (int i = 0; i < tokens.length; i++) { - path.append(tokens[i]); - if (i != 0) - path.append('/'); - } - return path.toString(); - } - - /** - * The provided data as a path ('/' at the end, not the beginning) - * - * @param cal the date - * @param addHour whether to add hour as well - */ - public static String dateAsPath(Calendar cal, Boolean addHour) { - StringBuffer buf = new StringBuffer(14); - buf.append('Y'); - buf.append(cal.get(Calendar.YEAR)); - buf.append('/'); - - int month = cal.get(Calendar.MONTH) + 1; - buf.append('M'); - if (month < 10) - buf.append(0); - buf.append(month); - buf.append('/'); - - int day = cal.get(Calendar.DAY_OF_MONTH); - buf.append('D'); - if (day < 10) - buf.append(0); - buf.append(day); - buf.append('/'); - - if (addHour) { - int hour = cal.get(Calendar.HOUR_OF_DAY); - buf.append('H'); - if (hour < 10) - buf.append(0); - buf.append(hour); - buf.append('/'); - } - return buf.toString(); - - } - - /** Converts in one call a string into a gregorian calendar. */ - public static Calendar parseCalendar(DateFormat dateFormat, String value) { - try { - Date date = dateFormat.parse(value); - Calendar calendar = new GregorianCalendar(); - calendar.setTime(date); - return calendar; - } catch (ParseException e) { - throw new IllegalArgumentException("Cannot parse " + value + " with date format " + dateFormat, e); - } - - } - - /** The last element of a path. */ - public static String lastPathElement(String path) { - if (path.charAt(path.length() - 1) == '/') - throw new IllegalArgumentException("Path " + path + " cannot end with '/'"); - int index = path.lastIndexOf('/'); - if (index < 0) - return path; - return path.substring(index + 1); - } - - /** - * Call {@link Node#getName()} without exceptions (useful in super - * constructors). - */ - public static String getNameQuietly(Node node) { - try { - return node.getName(); - } catch (RepositoryException e) { - throw new JcrException("Cannot get name from " + node, e); - } - } - - /** - * Call {@link Node#getProperty(String)} without exceptions (useful in super - * constructors). - */ - public static String getStringPropertyQuietly(Node node, String propertyName) { - try { - return node.getProperty(propertyName).getString(); - } catch (RepositoryException e) { - throw new JcrException("Cannot get name from " + node, e); - } - } - -// /** -// * Routine that get the child with this name, adding it if it does not already -// * exist -// */ -// public static Node getOrAdd(Node parent, String name, String primaryNodeType) throws RepositoryException { -// return parent.hasNode(name) ? parent.getNode(name) : parent.addNode(name, primaryNodeType); -// } - - /** - * Routine that get the child with this name, adding it if it does not already - * exist - */ - public static Node getOrAdd(Node parent, String name, String primaryNodeType, String... mixinNodeTypes) - throws RepositoryException { - Node node; - if (parent.hasNode(name)) { - node = parent.getNode(name); - if (primaryNodeType != null && !node.isNodeType(primaryNodeType)) - throw new IllegalArgumentException("Node " + node + " exists but is of primary node type " - + node.getPrimaryNodeType().getName() + ", not " + primaryNodeType); - for (String mixin : mixinNodeTypes) { - if (!node.isNodeType(mixin)) - node.addMixin(mixin); - } - return node; - } else { - node = primaryNodeType != null ? parent.addNode(name, primaryNodeType) : parent.addNode(name); - for (String mixin : mixinNodeTypes) { - node.addMixin(mixin); - } - return node; - } - } - - /** - * Routine that get the child with this name, adding it if it does not already - * exist - */ - public static Node getOrAdd(Node parent, String name) throws RepositoryException { - return parent.hasNode(name) ? parent.getNode(name) : parent.addNode(name); - } - - /** Convert a {@link NodeIterator} to a list of {@link Node} */ - public static List nodeIteratorToList(NodeIterator nodeIterator) { - List nodes = new ArrayList(); - while (nodeIterator.hasNext()) { - nodes.add(nodeIterator.nextNode()); - } - return nodes; - } - - /* - * PROPERTIES - */ - - /** - * Concisely get the string value of a property or null if this node doesn't - * have this property - */ - public static String get(Node node, String propertyName) { - try { - if (!node.hasProperty(propertyName)) - return null; - return node.getProperty(propertyName).getString(); - } catch (RepositoryException e) { - throw new JcrException("Cannot get property " + propertyName + " of " + node, e); - } - } - - /** Concisely get the path of the given node. */ - public static String getPath(Node node) { - try { - return node.getPath(); - } catch (RepositoryException e) { - throw new JcrException("Cannot get path of " + node, e); - } - } - - /** Concisely get the boolean value of a property */ - public static Boolean check(Node node, String propertyName) { - try { - return node.getProperty(propertyName).getBoolean(); - } catch (RepositoryException e) { - throw new JcrException("Cannot get property " + propertyName + " of " + node, e); - } - } - - /** Concisely get the bytes array value of a property */ - public static byte[] getBytes(Node node, String propertyName) { - try { - return getBinaryAsBytes(node.getProperty(propertyName)); - } catch (RepositoryException e) { - throw new JcrException("Cannot get property " + propertyName + " of " + node, e); - } - } - - /* - * MKDIRS - */ - - /** - * Create sub nodes relative to a parent node - */ - public static Node mkdirs(Node parentNode, String relativePath) { - return mkdirs(parentNode, relativePath, null, null); - } - - /** - * Create sub nodes relative to a parent node - * - * @param nodeType the type of the leaf node - */ - public static Node mkdirs(Node parentNode, String relativePath, String nodeType) { - return mkdirs(parentNode, relativePath, nodeType, null); - } - - /** - * Create sub nodes relative to a parent node - * - * @param nodeType the type of the leaf node - */ - public static Node mkdirs(Node parentNode, String relativePath, String nodeType, String intermediaryNodeType) { - List tokens = tokenize(relativePath); - Node currParent = parentNode; - try { - for (int i = 0; i < tokens.size(); i++) { - String name = tokens.get(i); - if (currParent.hasNode(name)) { - currParent = currParent.getNode(name); - } else { - if (i != (tokens.size() - 1)) {// intermediary - currParent = currParent.addNode(name, intermediaryNodeType); - } else {// leaf - currParent = currParent.addNode(name, nodeType); - } - } - } - return currParent; - } catch (RepositoryException e) { - throw new JcrException("Cannot mkdirs relative path " + relativePath + " from " + parentNode, e); - } - } - - /** - * Synchronized and save is performed, to avoid race conditions in initializers - * leading to duplicate nodes. - */ - public synchronized static Node mkdirsSafe(Session session, String path, String type) { - try { - if (session.hasPendingChanges()) - throw new IllegalStateException("Session has pending changes, save them first."); - Node node = mkdirs(session, path, type); - session.save(); - return node; - } catch (RepositoryException e) { - discardQuietly(session); - throw new JcrException("Cannot safely make directories", e); - } - } - - public synchronized static Node mkdirsSafe(Session session, String path) { - return mkdirsSafe(session, path, null); - } - - /** Creates the nodes making path, if they don't exist. */ - public static Node mkdirs(Session session, String path) { - return mkdirs(session, path, null, null, false); - } - - /** - * @param type the type of the leaf node - */ - public static Node mkdirs(Session session, String path, String type) { - return mkdirs(session, path, type, null, false); - } - - /** - * Creates the nodes making path, if they don't exist. This is up to the caller - * to save the session. Use with caution since it can create duplicate nodes if - * used concurrently. Requires read access to the root node of the workspace. - */ - public static Node mkdirs(Session session, String path, String type, String intermediaryNodeType, - Boolean versioning) { - try { - if (path.equals("/")) - return session.getRootNode(); - - if (session.itemExists(path)) { - Node node = session.getNode(path); - // check type - if (type != null && !node.isNodeType(type) && !node.getPath().equals("/")) - throw new IllegalArgumentException("Node " + node + " exists but is of type " - + node.getPrimaryNodeType().getName() + " not of type " + type); - // TODO: check versioning - return node; - } - - // StringBuffer current = new StringBuffer("/"); - // Node currentNode = session.getRootNode(); - - Node currentNode = findClosestExistingParent(session, path); - String closestExistingParentPath = currentNode.getPath(); - StringBuffer current = new StringBuffer(closestExistingParentPath); - if (!closestExistingParentPath.endsWith("/")) - current.append('/'); - Iterator it = tokenize(path.substring(closestExistingParentPath.length())).iterator(); - while (it.hasNext()) { - String part = it.next(); - current.append(part).append('/'); - if (!session.itemExists(current.toString())) { - if (!it.hasNext() && type != null) - currentNode = currentNode.addNode(part, type); - else if (it.hasNext() && intermediaryNodeType != null) - currentNode = currentNode.addNode(part, intermediaryNodeType); - else - currentNode = currentNode.addNode(part); - if (versioning) - currentNode.addMixin(NodeType.MIX_VERSIONABLE); -// if (log.isTraceEnabled()) -// log.debug("Added folder " + part + " as " + current); - } else { - currentNode = (Node) session.getItem(current.toString()); - } - } - return currentNode; - } catch (RepositoryException e) { - discardQuietly(session); - throw new JcrException("Cannot mkdirs " + path, e); - } finally { - } - } - - private static Node findClosestExistingParent(Session session, String path) throws RepositoryException { - int idx = path.lastIndexOf('/'); - if (idx == 0) - return session.getRootNode(); - String parentPath = path.substring(0, idx); - if (session.itemExists(parentPath)) - return session.getNode(parentPath); - else - return findClosestExistingParent(session, parentPath); - } - - /** Convert a path to the list of its tokens */ - public static List tokenize(String path) { - List tokens = new ArrayList(); - boolean optimized = false; - if (!optimized) { - String[] rawTokens = path.split("/"); - for (String token : rawTokens) { - if (!token.equals("")) - tokens.add(token); - } - } else { - StringBuffer curr = new StringBuffer(); - char[] arr = path.toCharArray(); - chars: for (int i = 0; i < arr.length; i++) { - char c = arr[i]; - if (c == '/') { - if (i == 0 || (i == arr.length - 1)) - continue chars; - if (curr.length() > 0) { - tokens.add(curr.toString()); - curr = new StringBuffer(); - } - } else - curr.append(c); - } - if (curr.length() > 0) { - tokens.add(curr.toString()); - curr = new StringBuffer(); - } - } - return Collections.unmodifiableList(tokens); - } - - // /** - // * use {@link #mkdirs(Session, String, String, String, Boolean)} instead. - // * - // * @deprecated - // */ - // @Deprecated - // public static Node mkdirs(Session session, String path, String type, - // Boolean versioning) { - // return mkdirs(session, path, type, type, false); - // } - - /** - * Safe and repository implementation independent registration of a namespace. - */ - public static void registerNamespaceSafely(Session session, String prefix, String uri) { - try { - registerNamespaceSafely(session.getWorkspace().getNamespaceRegistry(), prefix, uri); - } catch (RepositoryException e) { - throw new JcrException("Cannot find namespace registry", e); - } - } - - /** - * Safe and repository implementation independent registration of a namespace. - */ - public static void registerNamespaceSafely(NamespaceRegistry nr, String prefix, String uri) { - try { - String[] prefixes = nr.getPrefixes(); - for (String pref : prefixes) - if (pref.equals(prefix)) { - String registeredUri = nr.getURI(pref); - if (!registeredUri.equals(uri)) - throw new IllegalArgumentException("Prefix " + pref + " already registered for URI " - + registeredUri + " which is different from provided URI " + uri); - else - return;// skip - } - nr.registerNamespace(prefix, uri); - } catch (RepositoryException e) { - throw new JcrException("Cannot register namespace " + uri + " under prefix " + prefix, e); - } - } - -// /** Recursively outputs the contents of the given node. */ -// public static void debug(Node node) { -// debug(node, log); -// } -// -// /** Recursively outputs the contents of the given node. */ -// public static void debug(Node node, Log log) { -// try { -// // First output the node path -// log.debug(node.getPath()); -// // Skip the virtual (and large!) jcr:system subtree -// if (node.getName().equals("jcr:system")) { -// return; -// } -// -// // Then the children nodes (recursive) -// NodeIterator it = node.getNodes(); -// while (it.hasNext()) { -// Node childNode = it.nextNode(); -// debug(childNode, log); -// } -// -// // Then output the properties -// PropertyIterator properties = node.getProperties(); -// // log.debug("Property are : "); -// -// properties: while (properties.hasNext()) { -// Property property = properties.nextProperty(); -// if (property.getType() == PropertyType.BINARY) -// continue properties;// skip -// if (property.getDefinition().isMultiple()) { -// // A multi-valued property, print all values -// Value[] values = property.getValues(); -// for (int i = 0; i < values.length; i++) { -// log.debug(property.getPath() + "=" + values[i].getString()); -// } -// } else { -// // A single-valued property -// log.debug(property.getPath() + "=" + property.getString()); -// } -// } -// } catch (Exception e) { -// log.error("Could not debug " + node, e); -// } -// -// } - -// /** Logs the effective access control policies */ -// public static void logEffectiveAccessPolicies(Node node) { -// try { -// logEffectiveAccessPolicies(node.getSession(), node.getPath()); -// } catch (RepositoryException e) { -// log.error("Cannot log effective access policies of " + node, e); -// } -// } -// -// /** Logs the effective access control policies */ -// public static void logEffectiveAccessPolicies(Session session, String path) { -// if (!log.isDebugEnabled()) -// return; -// -// try { -// AccessControlPolicy[] effectivePolicies = session.getAccessControlManager().getEffectivePolicies(path); -// if (effectivePolicies.length > 0) { -// for (AccessControlPolicy policy : effectivePolicies) { -// if (policy instanceof AccessControlList) { -// AccessControlList acl = (AccessControlList) policy; -// log.debug("Access control list for " + path + "\n" + accessControlListSummary(acl)); -// } -// } -// } else { -// log.debug("No effective access control policy for " + path); -// } -// } catch (RepositoryException e) { -// log.error("Cannot log effective access policies of " + path, e); -// } -// } - - /** Returns a human-readable summary of this access control list. */ - public static String accessControlListSummary(AccessControlList acl) { - StringBuffer buf = new StringBuffer(""); - try { - for (AccessControlEntry ace : acl.getAccessControlEntries()) { - buf.append('\t').append(ace.getPrincipal().getName()).append('\n'); - for (Privilege priv : ace.getPrivileges()) - buf.append("\t\t").append(priv.getName()).append('\n'); - } - return buf.toString(); - } catch (RepositoryException e) { - throw new JcrException("Cannot write summary of " + acl, e); - } - } - - /** Copy the whole workspace via a system view XML. */ - public static void copyWorkspaceXml(Session fromSession, Session toSession) { - Workspace fromWorkspace = fromSession.getWorkspace(); - Workspace toWorkspace = toSession.getWorkspace(); - String errorMsg = "Cannot copy workspace " + fromWorkspace + " to " + toWorkspace + " via XML."; - - try (PipedInputStream in = new PipedInputStream(1024 * 1024);) { - new Thread(() -> { - try (PipedOutputStream out = new PipedOutputStream(in)) { - fromSession.exportSystemView("/", out, false, false); - out.flush(); - } catch (IOException e) { - throw new RuntimeException(errorMsg, e); - } catch (RepositoryException e) { - throw new JcrException(errorMsg, e); - } - }, "Copy workspace" + fromWorkspace + " to " + toWorkspace).start(); - - toSession.importXML("/", in, ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING); - toSession.save(); - } catch (IOException e) { - throw new RuntimeException(errorMsg, e); - } catch (RepositoryException e) { - throw new JcrException(errorMsg, e); - } - } - - /** - * Copies recursively the content of a node to another one. Do NOT copy the - * property values of {@link NodeType#MIX_CREATED} and - * {@link NodeType#MIX_LAST_MODIFIED}, but update the - * {@link Property#JCR_LAST_MODIFIED} and {@link Property#JCR_LAST_MODIFIED_BY} - * properties if the target node has the {@link NodeType#MIX_LAST_MODIFIED} - * mixin. - */ - public static void copy(Node fromNode, Node toNode) { - try { - if (toNode.getDefinition().isProtected()) - return; - - // add mixins - for (NodeType mixinType : fromNode.getMixinNodeTypes()) { - try { - toNode.addMixin(mixinType.getName()); - } catch (NoSuchNodeTypeException e) { - // ignore unknown mixins - // TODO log it - } - } - - // process properties - PropertyIterator pit = fromNode.getProperties(); - properties: while (pit.hasNext()) { - Property fromProperty = pit.nextProperty(); - String propertyName = fromProperty.getName(); - if (toNode.hasProperty(propertyName) && toNode.getProperty(propertyName).getDefinition().isProtected()) - continue properties; - - if (fromProperty.getDefinition().isProtected()) - continue properties; - - if (propertyName.equals("jcr:created") || propertyName.equals("jcr:createdBy") - || propertyName.equals("jcr:lastModified") || propertyName.equals("jcr:lastModifiedBy")) - continue properties; - - if (fromProperty.isMultiple()) { - toNode.setProperty(propertyName, fromProperty.getValues()); - } else { - toNode.setProperty(propertyName, fromProperty.getValue()); - } - } - - // update jcr:lastModified and jcr:lastModifiedBy in toNode in case - // they existed, before adding the mixins - updateLastModified(toNode, true); - - // process children nodes - NodeIterator nit = fromNode.getNodes(); - while (nit.hasNext()) { - Node fromChild = nit.nextNode(); - Integer index = fromChild.getIndex(); - String nodeRelPath = fromChild.getName() + "[" + index + "]"; - Node toChild; - if (toNode.hasNode(nodeRelPath)) - toChild = toNode.getNode(nodeRelPath); - else { - try { - toChild = toNode.addNode(fromChild.getName(), fromChild.getPrimaryNodeType().getName()); - } catch (NoSuchNodeTypeException e) { - // ignore unknown primary types - // TODO log it - return; - } - } - copy(fromChild, toChild); - } - } catch (RepositoryException e) { - throw new JcrException("Cannot copy " + fromNode + " to " + toNode, e); - } - } - - /** - * Check whether all first-level properties (except jcr:* properties) are equal. - * Skip jcr:* properties - */ - public static Boolean allPropertiesEquals(Node reference, Node observed, Boolean onlyCommonProperties) { - try { - PropertyIterator pit = reference.getProperties(); - props: while (pit.hasNext()) { - Property propReference = pit.nextProperty(); - String propName = propReference.getName(); - if (propName.startsWith("jcr:")) - continue props; - - if (!observed.hasProperty(propName)) - if (onlyCommonProperties) - continue props; - else - return false; - // TODO: deal with multiple property values? - if (!observed.getProperty(propName).getValue().equals(propReference.getValue())) - return false; - } - return true; - } catch (RepositoryException e) { - throw new JcrException("Cannot check all properties equals of " + reference + " and " + observed, e); - } - } - - public static Map diffProperties(Node reference, Node observed) { - Map diffs = new TreeMap(); - diffPropertiesLevel(diffs, null, reference, observed); - return diffs; - } - - /** - * Compare the properties of two nodes. Recursivity to child nodes is not yet - * supported. Skip jcr:* properties. - */ - static void diffPropertiesLevel(Map diffs, String baseRelPath, Node reference, - Node observed) { - try { - // check removed and modified - PropertyIterator pit = reference.getProperties(); - props: while (pit.hasNext()) { - Property p = pit.nextProperty(); - String name = p.getName(); - if (name.startsWith("jcr:")) - continue props; - - if (!observed.hasProperty(name)) { - String relPath = propertyRelPath(baseRelPath, name); - PropertyDiff pDiff = new PropertyDiff(PropertyDiff.REMOVED, relPath, p.getValue(), null); - diffs.put(relPath, pDiff); - } else { - if (p.isMultiple()) { - // FIXME implement multiple - } else { - Value referenceValue = p.getValue(); - Value newValue = observed.getProperty(name).getValue(); - if (!referenceValue.equals(newValue)) { - String relPath = propertyRelPath(baseRelPath, name); - PropertyDiff pDiff = new PropertyDiff(PropertyDiff.MODIFIED, relPath, referenceValue, - newValue); - diffs.put(relPath, pDiff); - } - } - } - } - // check added - pit = observed.getProperties(); - props: while (pit.hasNext()) { - Property p = pit.nextProperty(); - String name = p.getName(); - if (name.startsWith("jcr:")) - continue props; - if (!reference.hasProperty(name)) { - if (p.isMultiple()) { - // FIXME implement multiple - } else { - String relPath = propertyRelPath(baseRelPath, name); - PropertyDiff pDiff = new PropertyDiff(PropertyDiff.ADDED, relPath, null, p.getValue()); - diffs.put(relPath, pDiff); - } - } - } - } catch (RepositoryException e) { - throw new JcrException("Cannot diff " + reference + " and " + observed, e); - } - } - - /** - * Compare only a restricted list of properties of two nodes. No recursivity. - * - */ - public static Map diffProperties(Node reference, Node observed, List properties) { - Map diffs = new TreeMap(); - try { - Iterator pit = properties.iterator(); - - props: while (pit.hasNext()) { - String name = pit.next(); - if (!reference.hasProperty(name)) { - if (!observed.hasProperty(name)) - continue props; - Value val = observed.getProperty(name).getValue(); - try { - // empty String but not null - if ("".equals(val.getString())) - continue props; - } catch (Exception e) { - // not parseable as String, silent - } - PropertyDiff pDiff = new PropertyDiff(PropertyDiff.ADDED, name, null, val); - diffs.put(name, pDiff); - } else if (!observed.hasProperty(name)) { - PropertyDiff pDiff = new PropertyDiff(PropertyDiff.REMOVED, name, - reference.getProperty(name).getValue(), null); - diffs.put(name, pDiff); - } else { - Value referenceValue = reference.getProperty(name).getValue(); - Value newValue = observed.getProperty(name).getValue(); - if (!referenceValue.equals(newValue)) { - PropertyDiff pDiff = new PropertyDiff(PropertyDiff.MODIFIED, name, referenceValue, newValue); - diffs.put(name, pDiff); - } - } - } - } catch (RepositoryException e) { - throw new JcrException("Cannot diff " + reference + " and " + observed, e); - } - return diffs; - } - - /** Builds a property relPath to be used in the diff. */ - private static String propertyRelPath(String baseRelPath, String propertyName) { - if (baseRelPath == null) - return propertyName; - else - return baseRelPath + '/' + propertyName; - } - - /** - * Normalizes a name so that it can be stored in contexts not supporting names - * with ':' (typically databases). Replaces ':' by '_'. - */ - public static String normalize(String name) { - return name.replace(':', '_'); - } - - /** - * Replaces characters which are invalid in a JCR name by '_'. Currently not - * exhaustive. - * - * @see JcrUtils#INVALID_NAME_CHARACTERS - */ - public static String replaceInvalidChars(String name) { - return replaceInvalidChars(name, '_'); - } - - /** - * Replaces characters which are invalid in a JCR name. Currently not - * exhaustive. - * - * @see JcrUtils#INVALID_NAME_CHARACTERS - */ - public static String replaceInvalidChars(String name, char replacement) { - boolean modified = false; - char[] arr = name.toCharArray(); - for (int i = 0; i < arr.length; i++) { - char c = arr[i]; - invalid: for (char invalid : INVALID_NAME_CHARACTERS) { - if (c == invalid) { - arr[i] = replacement; - modified = true; - break invalid; - } - } - } - if (modified) - return new String(arr); - else - // do not create new object if unnecessary - return name; - } - - // /** - // * Removes forbidden characters from a path, replacing them with '_' - // * - // * @deprecated use {@link #replaceInvalidChars(String)} instead - // */ - // public static String removeForbiddenCharacters(String str) { - // return str.replace('[', '_').replace(']', '_').replace('/', '_').replace('*', - // '_'); - // - // } - - /** Cleanly disposes a {@link Binary} even if it is null. */ - public static void closeQuietly(Binary binary) { - if (binary == null) - return; - binary.dispose(); - } - - /** Retrieve a {@link Binary} as a byte array */ - public static byte[] getBinaryAsBytes(Property property) { - try (ByteArrayOutputStream out = new ByteArrayOutputStream(); - Bin binary = new Bin(property); - InputStream in = binary.getStream()) { - IOUtils.copy(in, out); - return out.toByteArray(); - } catch (RepositoryException e) { - throw new JcrException("Cannot read binary " + property + " as bytes", e); - } catch (IOException e) { - throw new RuntimeException("Cannot read binary " + property + " as bytes", e); - } - } - - /** Writes a {@link Binary} from a byte array */ - public static void setBinaryAsBytes(Node node, String property, byte[] bytes) { - Binary binary = null; - try (InputStream in = new ByteArrayInputStream(bytes)) { - binary = node.getSession().getValueFactory().createBinary(in); - node.setProperty(property, binary); - } catch (RepositoryException e) { - throw new JcrException("Cannot set binary " + property + " as bytes", e); - } catch (IOException e) { - throw new RuntimeException("Cannot set binary " + property + " as bytes", e); - } finally { - closeQuietly(binary); - } - } - - /** Writes a {@link Binary} from a byte array */ - public static void setBinaryAsBytes(Property prop, byte[] bytes) { - Binary binary = null; - try (InputStream in = new ByteArrayInputStream(bytes)) { - binary = prop.getSession().getValueFactory().createBinary(in); - prop.setValue(binary); - } catch (RepositoryException e) { - throw new JcrException("Cannot set binary " + prop + " as bytes", e); - } catch (IOException e) { - throw new RuntimeException("Cannot set binary " + prop + " as bytes", e); - } finally { - closeQuietly(binary); - } - } - - /** - * Creates depth from a string (typically a username) by adding levels based on - * its first characters: "aBcD",2 becomes a/aB - */ - public static String firstCharsToPath(String str, Integer nbrOfChars) { - if (str.length() < nbrOfChars) - throw new IllegalArgumentException("String " + str + " length must be greater or equal than " + nbrOfChars); - StringBuffer path = new StringBuffer(""); - StringBuffer curr = new StringBuffer(""); - for (int i = 0; i < nbrOfChars; i++) { - curr.append(str.charAt(i)); - path.append(curr); - if (i < nbrOfChars - 1) - path.append('/'); - } - return path.toString(); - } - - /** - * Discards the current changes in the session attached to this node. To be used - * typically in a catch block. - * - * @see #discardQuietly(Session) - */ - public static void discardUnderlyingSessionQuietly(Node node) { - try { - discardQuietly(node.getSession()); - } catch (RepositoryException e) { - // silent - } - } - - /** - * Discards the current changes in a session by calling - * {@link Session#refresh(boolean)} with false, only logging - * potential errors when doing so. To be used typically in a catch block. - */ - public static void discardQuietly(Session session) { - try { - if (session != null) - session.refresh(false); - } catch (RepositoryException e) { - // silent - } - } - - /** - * Login to a workspace with implicit credentials, creates the workspace with - * these credentials if it does not already exist. - */ - public static Session loginOrCreateWorkspace(Repository repository, String workspaceName) - throws RepositoryException { - return loginOrCreateWorkspace(repository, workspaceName, null); - } - - /** - * Login to a workspace with implicit credentials, creates the workspace with - * these credentials if it does not already exist. - */ - public static Session loginOrCreateWorkspace(Repository repository, String workspaceName, Credentials credentials) - throws RepositoryException { - Session workspaceSession = null; - Session defaultSession = null; - try { - try { - workspaceSession = repository.login(credentials, workspaceName); - } catch (NoSuchWorkspaceException e) { - // try to create workspace - defaultSession = repository.login(credentials); - defaultSession.getWorkspace().createWorkspace(workspaceName); - workspaceSession = repository.login(credentials, workspaceName); - } - return workspaceSession; - } finally { - logoutQuietly(defaultSession); - } - } - - /** - * Logs out the session, not throwing any exception, even if it is null. - * {@link Jcr#logout(Session)} should rather be used. - */ - public static void logoutQuietly(Session session) { - Jcr.logout(session); -// try { -// if (session != null) -// if (session.isLive()) -// session.logout(); -// } catch (Exception e) { -// // silent -// } - } - - /** - * Convenient method to add a listener. uuids passed as null, deep=true, - * local=true, only one node type - */ - public static void addListener(Session session, EventListener listener, int eventTypes, String basePath, - String nodeType) { - try { - session.getWorkspace().getObservationManager().addEventListener(listener, eventTypes, basePath, true, null, - nodeType == null ? null : new String[] { nodeType }, true); - } catch (RepositoryException e) { - throw new JcrException("Cannot add JCR listener " + listener + " to session " + session, e); - } - } - - /** Removes a listener without throwing exception */ - public static void removeListenerQuietly(Session session, EventListener listener) { - if (session == null || !session.isLive()) - return; - try { - session.getWorkspace().getObservationManager().removeEventListener(listener); - } catch (RepositoryException e) { - // silent - } - } - - /** - * Quietly unregisters an {@link EventListener} from the udnerlying workspace of - * this node. - */ - public static void unregisterQuietly(Node node, EventListener eventListener) { - try { - unregisterQuietly(node.getSession().getWorkspace(), eventListener); - } catch (RepositoryException e) { - // silent - } - } - - /** Quietly unregisters an {@link EventListener} from this workspace */ - public static void unregisterQuietly(Workspace workspace, EventListener eventListener) { - if (eventListener == null) - return; - try { - workspace.getObservationManager().removeEventListener(eventListener); - } catch (RepositoryException e) { - // silent - } - } - - /** - * Checks whether {@link Property#JCR_LAST_MODIFIED} or (afterwards) - * {@link Property#JCR_CREATED} are set and returns it as an {@link Instant}. - */ - public static Instant getModified(Node node) { - Calendar calendar = null; - try { - if (node.hasProperty(Property.JCR_LAST_MODIFIED)) - calendar = node.getProperty(Property.JCR_LAST_MODIFIED).getDate(); - else if (node.hasProperty(Property.JCR_CREATED)) - calendar = node.getProperty(Property.JCR_CREATED).getDate(); - else - throw new IllegalArgumentException("No modification time found in " + node); - return calendar.toInstant(); - } catch (RepositoryException e) { - throw new JcrException("Cannot get modification time for " + node, e); - } - - } - - /** - * Get {@link Property#JCR_CREATED} as an {@link Instant}, if it is set. - */ - public static Instant getCreated(Node node) { - Calendar calendar = null; - try { - if (node.hasProperty(Property.JCR_CREATED)) - calendar = node.getProperty(Property.JCR_CREATED).getDate(); - else - throw new IllegalArgumentException("No created time found in " + node); - return calendar.toInstant(); - } catch (RepositoryException e) { - throw new JcrException("Cannot get created time for " + node, e); - } - - } - - /** - * Updates the {@link Property#JCR_LAST_MODIFIED} property with the current time - * and the {@link Property#JCR_LAST_MODIFIED_BY} property with the underlying - * session user id. - */ - public static void updateLastModified(Node node) { - updateLastModified(node, false); - } - - /** - * Updates the {@link Property#JCR_LAST_MODIFIED} property with the current time - * and the {@link Property#JCR_LAST_MODIFIED_BY} property with the underlying - * session user id. In Jackrabbit 2.x, - * these properties are - * not automatically updated, hence the need for manual update. The session - * is not saved. - */ - public static void updateLastModified(Node node, boolean addMixin) { - try { - if (addMixin && !node.isNodeType(NodeType.MIX_LAST_MODIFIED)) - node.addMixin(NodeType.MIX_LAST_MODIFIED); - node.setProperty(Property.JCR_LAST_MODIFIED, new GregorianCalendar()); - node.setProperty(Property.JCR_LAST_MODIFIED_BY, node.getSession().getUserID()); - } catch (RepositoryException e) { - throw new JcrException("Cannot update last modified on " + node, e); - } - } - - /** - * Update lastModified recursively until this parent. - * - * @param node the node - * @param untilPath the base path, null is equivalent to "/" - */ - public static void updateLastModifiedAndParents(Node node, String untilPath) { - updateLastModifiedAndParents(node, untilPath, true); - } - - /** - * Update lastModified recursively until this parent. - * - * @param node the node - * @param untilPath the base path, null is equivalent to "/" - */ - public static void updateLastModifiedAndParents(Node node, String untilPath, boolean addMixin) { - try { - if (untilPath != null && !node.getPath().startsWith(untilPath)) - throw new IllegalArgumentException(node + " is not under " + untilPath); - updateLastModified(node, addMixin); - if (untilPath == null) { - if (!node.getPath().equals("/")) - updateLastModifiedAndParents(node.getParent(), untilPath, addMixin); - } else { - if (!node.getPath().equals(untilPath)) - updateLastModifiedAndParents(node.getParent(), untilPath, addMixin); - } - } catch (RepositoryException e) { - throw new JcrException("Cannot update lastModified from " + node + " until " + untilPath, e); - } - } - - /** - * Returns a String representing the short version (see - * Node type - * Notation attributes grammar) of the main business attributes of this - * property definition - * - * @param prop - */ - public static String getPropertyDefinitionAsString(Property prop) { - StringBuffer sbuf = new StringBuffer(); - try { - if (prop.getDefinition().isAutoCreated()) - sbuf.append("a"); - if (prop.getDefinition().isMandatory()) - sbuf.append("m"); - if (prop.getDefinition().isProtected()) - sbuf.append("p"); - if (prop.getDefinition().isMultiple()) - sbuf.append("*"); - } catch (RepositoryException re) { - throw new JcrException("unexpected error while getting property definition as String", re); - } - return sbuf.toString(); - } - - /** - * Estimate the sub tree size from current node. Computation is based on the Jcr - * {@link Property#getLength()} method. Note : it is not the exact size used on - * the disk by the current part of the JCR Tree. - */ - - public static long getNodeApproxSize(Node node) { - long curNodeSize = 0; - try { - PropertyIterator pi = node.getProperties(); - while (pi.hasNext()) { - Property prop = pi.nextProperty(); - if (prop.isMultiple()) { - int nb = prop.getLengths().length; - for (int i = 0; i < nb; i++) { - curNodeSize += (prop.getLengths()[i] > 0 ? prop.getLengths()[i] : 0); - } - } else - curNodeSize += (prop.getLength() > 0 ? prop.getLength() : 0); - } - - NodeIterator ni = node.getNodes(); - while (ni.hasNext()) - curNodeSize += getNodeApproxSize(ni.nextNode()); - return curNodeSize; - } catch (RepositoryException re) { - throw new JcrException("Unexpected error while recursively determining node size.", re); - } - } - - /* - * SECURITY - */ - - /** - * Convenience method for adding a single privilege to a principal (user or - * role), typically jcr:all - */ - public synchronized static void addPrivilege(Session session, String path, String principal, String privilege) - throws RepositoryException { - List privileges = new ArrayList(); - privileges.add(session.getAccessControlManager().privilegeFromName(privilege)); - addPrivileges(session, path, new SimplePrincipal(principal), privileges); - } - - /** - * Add privileges on a path to a {@link Principal}. The path must already exist. - * Session is saved. Synchronized to prevent concurrent modifications of the - * same node. - */ - public synchronized static Boolean addPrivileges(Session session, String path, Principal principal, - List privs) throws RepositoryException { - // make sure the session is in line with the persisted state - session.refresh(false); - AccessControlManager acm = session.getAccessControlManager(); - AccessControlList acl = getAccessControlList(acm, path); - - accessControlEntries: for (AccessControlEntry ace : acl.getAccessControlEntries()) { - Principal currentPrincipal = ace.getPrincipal(); - if (currentPrincipal.getName().equals(principal.getName())) { - Privilege[] currentPrivileges = ace.getPrivileges(); - if (currentPrivileges.length != privs.size()) - break accessControlEntries; - for (int i = 0; i < currentPrivileges.length; i++) { - Privilege currP = currentPrivileges[i]; - Privilege p = privs.get(i); - if (!currP.getName().equals(p.getName())) { - break accessControlEntries; - } - } - return false; - } - } - - Privilege[] privileges = privs.toArray(new Privilege[privs.size()]); - acl.addAccessControlEntry(principal, privileges); - acm.setPolicy(path, acl); -// if (log.isDebugEnabled()) { -// StringBuffer privBuf = new StringBuffer(); -// for (Privilege priv : privs) -// privBuf.append(priv.getName()); -// log.debug("Added privileges " + privBuf + " to " + principal.getName() + " on " + path + " in '" -// + session.getWorkspace().getName() + "'"); -// } - session.refresh(true); - session.save(); - return true; - } - - /** - * Gets the first available access control list for this path, throws exception - * if not found - */ - public synchronized static AccessControlList getAccessControlList(AccessControlManager acm, String path) - throws RepositoryException { - // search for an access control list - AccessControlList acl = null; - AccessControlPolicyIterator policyIterator = acm.getApplicablePolicies(path); - applicablePolicies: if (policyIterator.hasNext()) { - while (policyIterator.hasNext()) { - AccessControlPolicy acp = policyIterator.nextAccessControlPolicy(); - if (acp instanceof AccessControlList) { - acl = ((AccessControlList) acp); - break applicablePolicies; - } - } - } else { - AccessControlPolicy[] existingPolicies = acm.getPolicies(path); - existingPolicies: for (AccessControlPolicy acp : existingPolicies) { - if (acp instanceof AccessControlList) { - acl = ((AccessControlList) acp); - break existingPolicies; - } - } - } - if (acl != null) - return acl; - else - throw new IllegalArgumentException("ACL not found at " + path); - } - - /** Clear authorizations for a user at this path */ - public synchronized static void clearAccessControList(Session session, String path, String username) - throws RepositoryException { - AccessControlManager acm = session.getAccessControlManager(); - AccessControlList acl = getAccessControlList(acm, path); - for (AccessControlEntry ace : acl.getAccessControlEntries()) { - if (ace.getPrincipal().getName().equals(username)) { - acl.removeAccessControlEntry(ace); - } - } - // the new access control list must be applied otherwise this call: - // acl.removeAccessControlEntry(ace); has no effect - acm.setPolicy(path, acl); - session.refresh(true); - session.save(); - } - - /* - * FILES UTILITIES - */ - /** - * Creates the nodes making the path as {@link NodeType#NT_FOLDER} - */ - public static Node mkfolders(Session session, String path) { - return mkdirs(session, path, NodeType.NT_FOLDER, NodeType.NT_FOLDER, false); - } - - /** - * Copy only nt:folder and nt:file, without their additional types and - * properties. - * - * @param recursive if true copies folders as well, otherwise only first level - * files - * @return how many files were copied - */ - public static Long copyFiles(Node fromNode, Node toNode, Boolean recursive, JcrMonitor monitor, boolean onlyAdd) { - long count = 0l; - - // Binary binary = null; - // InputStream in = null; - try { - NodeIterator fromChildren = fromNode.getNodes(); - children: while (fromChildren.hasNext()) { - if (monitor != null && monitor.isCanceled()) - throw new IllegalStateException("Copy cancelled before it was completed"); - - Node fromChild = fromChildren.nextNode(); - String fileName = fromChild.getName(); - if (fromChild.isNodeType(NodeType.NT_FILE)) { - if (onlyAdd && toNode.hasNode(fileName)) { - monitor.subTask("Skip existing " + fileName); - continue children; - } - - if (monitor != null) - monitor.subTask("Copy " + fileName); - try (Bin binary = new Bin(fromChild.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA)); - InputStream in = binary.getStream();) { - copyStreamAsFile(toNode, fileName, in); - } catch (IOException e) { - throw new RuntimeException("Cannot copy " + fileName + " to " + toNode, e); - } - - // save session - toNode.getSession().save(); - count++; - -// if (log.isDebugEnabled()) -// log.debug("Copied file " + fromChild.getPath()); - if (monitor != null) - monitor.worked(1); - } else if (fromChild.isNodeType(NodeType.NT_FOLDER) && recursive) { - Node toChildFolder; - if (toNode.hasNode(fileName)) { - toChildFolder = toNode.getNode(fileName); - if (!toChildFolder.isNodeType(NodeType.NT_FOLDER)) - throw new IllegalArgumentException(toChildFolder + " is not of type nt:folder"); - } else { - toChildFolder = toNode.addNode(fileName, NodeType.NT_FOLDER); - - // save session - toNode.getSession().save(); - } - count = count + copyFiles(fromChild, toChildFolder, recursive, monitor, onlyAdd); - } - } - return count; - } catch (RepositoryException e) { - throw new JcrException("Cannot copy files between " + fromNode + " and " + toNode, e); - } finally { - // in case there was an exception - // IOUtils.closeQuietly(in); - // closeQuietly(binary); - } - } - - /** - * Iteratively count all file nodes in subtree, inefficient but can be useful - * when query are poorly supported, such as in remoting. - */ - public static Long countFiles(Node node) { - Long localCount = 0l; - try { - for (NodeIterator nit = node.getNodes(); nit.hasNext();) { - Node child = nit.nextNode(); - if (child.isNodeType(NodeType.NT_FOLDER)) - localCount = localCount + countFiles(child); - else if (child.isNodeType(NodeType.NT_FILE)) - localCount = localCount + 1; - } - } catch (RepositoryException e) { - throw new JcrException("Cannot count all children of " + node, e); - } - return localCount; - } - - /** - * Copy a file as an nt:file, assuming an nt:folder hierarchy. The session is - * NOT saved. - * - * @return the created file node - */ - @Deprecated - public static Node copyFile(Node folderNode, File file) { - try (InputStream in = new FileInputStream(file)) { - return copyStreamAsFile(folderNode, file.getName(), in); - } catch (IOException e) { - throw new RuntimeException("Cannot copy file " + file + " under " + folderNode, e); - } - } - - /** Copy bytes as an nt:file */ - public static Node copyBytesAsFile(Node folderNode, String fileName, byte[] bytes) { - // InputStream in = null; - try (InputStream in = new ByteArrayInputStream(bytes)) { - // in = new ByteArrayInputStream(bytes); - return copyStreamAsFile(folderNode, fileName, in); - } catch (IOException e) { - throw new RuntimeException("Cannot copy file " + fileName + " under " + folderNode, e); - // } finally { - // IOUtils.closeQuietly(in); - } - } - - /** - * Copy a stream as an nt:file, assuming an nt:folder hierarchy. The session is - * NOT saved. - * - * @return the created file node - */ - public static Node copyStreamAsFile(Node folderNode, String fileName, InputStream in) { - Binary binary = null; - try { - Node fileNode; - Node contentNode; - if (folderNode.hasNode(fileName)) { - fileNode = folderNode.getNode(fileName); - if (!fileNode.isNodeType(NodeType.NT_FILE)) - throw new IllegalArgumentException(fileNode + " is not of type nt:file"); - // we assume that the content node is already there - contentNode = fileNode.getNode(Node.JCR_CONTENT); - } else { - fileNode = folderNode.addNode(fileName, NodeType.NT_FILE); - contentNode = fileNode.addNode(Node.JCR_CONTENT, NodeType.NT_UNSTRUCTURED); - } - binary = contentNode.getSession().getValueFactory().createBinary(in); - contentNode.setProperty(Property.JCR_DATA, binary); - updateLastModified(contentNode); - return fileNode; - } catch (RepositoryException e) { - throw new JcrException("Cannot create file node " + fileName + " under " + folderNode, e); - } finally { - closeQuietly(binary); - } - } - - /** Read an an nt:file as an {@link InputStream}. */ - public static InputStream getFileAsStream(Node fileNode) throws RepositoryException { - return fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary().getStream(); - } - - /** - * Set the properties of {@link NodeType#MIX_MIMETYPE} on the content of this - * file node. - */ - public static void setFileMimeType(Node fileNode, String mimeType, String encoding) throws RepositoryException { - Node contentNode = fileNode.getNode(Node.JCR_CONTENT); - if (mimeType != null) - contentNode.setProperty(Property.JCR_MIMETYPE, mimeType); - if (encoding != null) - contentNode.setProperty(Property.JCR_ENCODING, encoding); - // TODO remove properties if args are null? - } - - public static void copyFilesToFs(Node baseNode, Path targetDir, boolean recursive) { - try { - Files.createDirectories(targetDir); - for (NodeIterator nit = baseNode.getNodes(); nit.hasNext();) { - Node node = nit.nextNode(); - if (node.isNodeType(NodeType.NT_FILE)) { - Path filePath = targetDir.resolve(node.getName()); - try (OutputStream out = Files.newOutputStream(filePath); InputStream in = getFileAsStream(node)) { - IOUtils.copy(in, out); - } - } else if (recursive && node.isNodeType(NodeType.NT_FOLDER)) { - Path dirPath = targetDir.resolve(node.getName()); - copyFilesToFs(node, dirPath, true); - } - } - } catch (RepositoryException e) { - throw new JcrException("Cannot copy " + baseNode + " to " + targetDir, e); - } catch (IOException e) { - throw new RuntimeException("Cannot copy " + baseNode + " to " + targetDir, e); - } - } - - /** - * Computes the checksum of an nt:file. - * - * @deprecated use separate digest utilities - */ - @Deprecated - public static String checksumFile(Node fileNode, String algorithm) { - try (InputStream in = fileNode.getNode(Node.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary() - .getStream()) { - return digest(algorithm, in); - } catch (IOException e) { - throw new RuntimeException("Cannot checksum file " + fileNode + " with algorithm " + algorithm, e); - } catch (RepositoryException e) { - throw new JcrException("Cannot checksum file " + fileNode + " with algorithm " + algorithm, e); - } - } - - @Deprecated - private static String digest(String algorithm, InputStream in) { - final Integer byteBufferCapacity = 100 * 1024;// 100 KB - try { - MessageDigest digest = MessageDigest.getInstance(algorithm); - byte[] buffer = new byte[byteBufferCapacity]; - int read = 0; - while ((read = in.read(buffer)) > 0) { - digest.update(buffer, 0, read); - } - - byte[] checksum = digest.digest(); - String res = encodeHexString(checksum); - return res; - } catch (IOException e) { - throw new RuntimeException("Cannot digest with algorithm " + algorithm, e); - } catch (NoSuchAlgorithmException e) { - throw new IllegalArgumentException("Cannot digest with algorithm " + algorithm, e); - } - } - - /** - * From - * http://stackoverflow.com/questions/9655181/how-to-convert-a-byte-array-to - * -a-hex-string-in-java - */ - @Deprecated - private static String encodeHexString(byte[] bytes) { - final char[] hexArray = "0123456789abcdef".toCharArray(); - char[] hexChars = new char[bytes.length * 2]; - for (int j = 0; j < bytes.length; j++) { - int v = bytes[j] & 0xFF; - hexChars[j * 2] = hexArray[v >>> 4]; - hexChars[j * 2 + 1] = hexArray[v & 0x0F]; - } - return new String(hexChars); - } - - /** Export a subtree as a compact XML without namespaces. */ - public static void toSimpleXml(Node node, StringBuilder sb) throws RepositoryException { - sb.append('<'); - String nodeName = node.getName(); - int colIndex = nodeName.indexOf(':'); - if (colIndex > 0) { - nodeName = nodeName.substring(colIndex + 1); - } - sb.append(nodeName); - PropertyIterator pit = node.getProperties(); - properties: while (pit.hasNext()) { - Property p = pit.nextProperty(); - // skip multiple properties - if (p.isMultiple()) - continue properties; - String propertyName = p.getName(); - int pcolIndex = propertyName.indexOf(':'); - // skip properties with namespaces - if (pcolIndex > 0) - continue properties; - // skip binaries - if (p.getType() == PropertyType.BINARY) { - continue properties; - // TODO retrieve identifier? - } - sb.append(' '); - sb.append(propertyName); - sb.append('='); - sb.append('\"').append(p.getString()).append('\"'); - } - - if (node.hasNodes()) { - sb.append('>'); - NodeIterator children = node.getNodes(); - while (children.hasNext()) { - toSimpleXml(children.nextNode(), sb); - } - sb.append("'); - } else { - sb.append("/>"); - } - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxApi.java b/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxApi.java deleted file mode 100644 index 666b2593e..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxApi.java +++ /dev/null @@ -1,190 +0,0 @@ -package org.argeo.jcr; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.RepositoryException; -import javax.jcr.Value; - -/** Uilities around the JCR extensions. */ -public class JcrxApi { - public final static String MD5 = "MD5"; - public final static String SHA1 = "SHA1"; - public final static String SHA256 = "SHA-256"; - public final static String SHA512 = "SHA-512"; - - public final static String EMPTY_MD5 = "d41d8cd98f00b204e9800998ecf8427e"; - public final static String EMPTY_SHA1 = "da39a3ee5e6b4b0d3255bfef95601890afd80709"; - public final static String EMPTY_SHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"; - public final static String EMPTY_SHA512 = "cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"; - - public final static int LENGTH_MD5 = EMPTY_MD5.length(); - public final static int LENGTH_SHA1 = EMPTY_SHA1.length(); - public final static int LENGTH_SHA256 = EMPTY_SHA256.length(); - public final static int LENGTH_SHA512 = EMPTY_SHA512.length(); - - /* - * XML - */ - /** - * Get the XML text of this child node. - */ - public static String getXmlValue(Node node, String name) { - try { - if (!node.hasNode(name)) - return null; - Node child = node.getNode(name); - return getXmlValue(child); - } catch (RepositoryException e) { - throw new IllegalStateException("Cannot get " + name + " as XML text", e); - } - } - - /** - * Get the XML text of this node. - */ - public static String getXmlValue(Node node) { - try { - if (!node.hasNode(Jcr.JCR_XMLTEXT)) - return null; - Node xmlText = node.getNode(Jcr.JCR_XMLTEXT); - if (!xmlText.hasProperty(Jcr.JCR_XMLCHARACTERS)) - throw new IllegalArgumentException( - "Node " + xmlText + " has no " + Jcr.JCR_XMLCHARACTERS + " property"); - return xmlText.getProperty(Jcr.JCR_XMLCHARACTERS).getString(); - } catch (RepositoryException e) { - throw new IllegalStateException("Cannot get " + node + " as XML text", e); - } - } - - /** - * Set as a subnode which will be exported as an XML element. - */ - public static void setXmlValue(Node node, String name, String value) { - try { - if (node.hasNode(name)) { - Node child = node.getNode(name); - setXmlValue(node, child, value); - } else - node.addNode(name, JcrxType.JCRX_XMLVALUE).addNode(Jcr.JCR_XMLTEXT, JcrxType.JCRX_XMLTEXT) - .setProperty(Jcr.JCR_XMLCHARACTERS, value); - } catch (RepositoryException e) { - throw new JcrException("Cannot set " + name + " as XML text", e); - } - } - - public static void setXmlValue(Node node, Node child, String value) { - try { - if (!child.hasNode(Jcr.JCR_XMLTEXT)) - child.addNode(Jcr.JCR_XMLTEXT, JcrxType.JCRX_XMLTEXT); - child.getNode(Jcr.JCR_XMLTEXT).setProperty(Jcr.JCR_XMLCHARACTERS, value); - } catch (RepositoryException e) { - throw new JcrException("Cannot set " + child + " as XML text", e); - } - } - - /** - * Add a checksum replacing the one which was previously set with the same - * length. - */ - public static void addChecksum(Node node, String checksum) { - try { - if (!node.hasProperty(JcrxName.JCRX_SUM)) { - node.setProperty(JcrxName.JCRX_SUM, new String[] { checksum }); - return; - } else { - int stringLength = checksum.length(); - Property property = node.getProperty(JcrxName.JCRX_SUM); - List values = Arrays.asList(property.getValues()); - Integer indexToRemove = null; - values: for (int i = 0; i < values.size(); i++) { - Value value = values.get(i); - if (value.getString().length() == stringLength) { - indexToRemove = i; - break values; - } - } - if (indexToRemove != null) - values.set(indexToRemove, node.getSession().getValueFactory().createValue(checksum)); - else - values.add(0, node.getSession().getValueFactory().createValue(checksum)); - property.setValue(values.toArray(new Value[values.size()])); - } - } catch (RepositoryException e) { - throw new JcrException("Cannot set checksum on " + node, e); - } - } - - /** Replace all checksums. */ - public static void setChecksums(Node node, List checksums) { - try { - node.setProperty(JcrxName.JCRX_SUM, checksums.toArray(new String[checksums.size()])); - } catch (RepositoryException e) { - throw new JcrException("Cannot set checksums on " + node, e); - } - } - - /** Replace all checksums. */ - public static List getChecksums(Node node) { - try { - List res = new ArrayList<>(); - if (!node.hasProperty(JcrxName.JCRX_SUM)) - return res; - Property property = node.getProperty(JcrxName.JCRX_SUM); - for (Value value : property.getValues()) { - res.add(value.getString()); - } - return res; - } catch (RepositoryException e) { - throw new JcrException("Cannot get checksums from " + node, e); - } - } - -// /** Replace all checksums with this single one. */ -// public static void setChecksum(Node node, String checksum) { -// setChecksums(node, Collections.singletonList(checksum)); -// } - - /** Retrieves the checksum with this algorithm, or null if not found. */ - public static String getChecksum(Node node, String algorithm) { - int stringLength; - switch (algorithm) { - case MD5: - stringLength = LENGTH_MD5; - break; - case SHA1: - stringLength = LENGTH_SHA1; - break; - case SHA256: - stringLength = LENGTH_SHA256; - break; - case SHA512: - stringLength = LENGTH_SHA512; - break; - default: - throw new IllegalArgumentException("Unkown algorithm " + algorithm); - } - return getChecksum(node, stringLength); - } - - /** Retrieves the checksum with this string length, or null if not found. */ - public static String getChecksum(Node node, int stringLength) { - try { - if (!node.hasProperty(JcrxName.JCRX_SUM)) - return null; - Property property = node.getProperty(JcrxName.JCRX_SUM); - for (Value value : property.getValues()) { - String str = value.getString(); - if (str.length() == stringLength) - return str; - } - return null; - } catch (RepositoryException e) { - throw new IllegalStateException("Cannot get checksum for " + node, e); - } - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxName.java b/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxName.java deleted file mode 100644 index 9dd43adce..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxName.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.argeo.jcr; - -/** Names declared by the JCR extensions. */ -public interface JcrxName { - /** The multiple property holding various coherent checksums. */ - public final static String JCRX_SUM = "{http://www.argeo.org/ns/jcrx}sum"; -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxType.java b/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxType.java deleted file mode 100644 index 0cbad3341..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/JcrxType.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.argeo.jcr; - -/** Node types declared by the JCR extensions. */ -public interface JcrxType { - /** - * Node type for an XML value, which will be serialized in XML as an element - * containing text. - */ - public final static String JCRX_XMLVALUE = "{http://www.argeo.org/ns/jcrx}xmlvalue"; - - /** Node type for the node containing the text. */ - public final static String JCRX_XMLTEXT = "{http://www.argeo.org/ns/jcrx}xmltext"; - - /** Mixin node type for a set of checksums. */ - public final static String JCRX_CSUM = "{http://www.argeo.org/ns/jcrx}csum"; - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/PropertyDiff.java b/org.argeo.cms.jcr/src/org/argeo/jcr/PropertyDiff.java deleted file mode 100644 index 71e76fe9b..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/PropertyDiff.java +++ /dev/null @@ -1,57 +0,0 @@ -package org.argeo.jcr; - -import javax.jcr.Value; - -/** The result of the comparison of two JCR properties. */ -public class PropertyDiff { - public final static Integer MODIFIED = 0; - public final static Integer ADDED = 1; - public final static Integer REMOVED = 2; - - private final Integer type; - private final String relPath; - private final Value referenceValue; - private final Value newValue; - - public PropertyDiff(Integer type, String relPath, Value referenceValue, Value newValue) { - super(); - - if (type == MODIFIED) { - if (referenceValue == null || newValue == null) - throw new IllegalArgumentException("Reference and new values must be specified."); - } else if (type == ADDED) { - if (referenceValue != null || newValue == null) - throw new IllegalArgumentException("New value and only it must be specified."); - } else if (type == REMOVED) { - if (referenceValue == null || newValue != null) - throw new IllegalArgumentException("Reference value and only it must be specified."); - } else { - throw new IllegalArgumentException("Unkown diff type " + type); - } - - if (relPath == null) - throw new IllegalArgumentException("Relative path must be specified"); - - this.type = type; - this.relPath = relPath; - this.referenceValue = referenceValue; - this.newValue = newValue; - } - - public Integer getType() { - return type; - } - - public String getRelPath() { - return relPath; - } - - public Value getReferenceValue() { - return referenceValue; - } - - public Value getNewValue() { - return newValue; - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/SimplePrincipal.java b/org.argeo.cms.jcr/src/org/argeo/jcr/SimplePrincipal.java deleted file mode 100644 index 4f42f2d9c..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/SimplePrincipal.java +++ /dev/null @@ -1,43 +0,0 @@ -package org.argeo.jcr; - -import java.security.Principal; - -/** Canonical implementation of a {@link Principal} */ -class SimplePrincipal implements Principal { - private final String name; - - public SimplePrincipal(String name) { - if (name == null) - throw new IllegalArgumentException("Principal name cannot be null"); - this.name = name; - } - - public String getName() { - return name; - } - - @Override - public int hashCode() { - return name.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj == null) - return false; - if (obj instanceof Principal) - return name.equals((((Principal) obj).getName())); - return name.equals(obj.toString()); - } - - @Override - protected Object clone() throws CloneNotSupportedException { - return new SimplePrincipal(name); - } - - @Override - public String toString() { - return name; - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/ThreadBoundJcrSessionFactory.java b/org.argeo.cms.jcr/src/org/argeo/jcr/ThreadBoundJcrSessionFactory.java deleted file mode 100644 index 2208627ab..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/ThreadBoundJcrSessionFactory.java +++ /dev/null @@ -1,279 +0,0 @@ -package org.argeo.jcr; - -import java.lang.reflect.InvocationHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import javax.jcr.LoginException; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.SimpleCredentials; - -import org.argeo.api.cms.CmsLog; - -/** Proxy JCR sessions and attach them to calling threads. */ -@Deprecated -public abstract class ThreadBoundJcrSessionFactory { - private final static CmsLog log = CmsLog.getLog(ThreadBoundJcrSessionFactory.class); - - private Repository repository; - /** can be injected as list, only used if repository is null */ - private List repositories; - - private ThreadLocal session = new ThreadLocal(); - private final Session proxiedSession; - /** If workspace is null, default will be used. */ - private String workspace = null; - - private String defaultUsername = "demo"; - private String defaultPassword = "demo"; - private Boolean forceDefaultCredentials = false; - - private boolean active = true; - - // monitoring - private final List threads = Collections.synchronizedList(new ArrayList()); - private final Map activeSessions = Collections.synchronizedMap(new HashMap()); - private MonitoringThread monitoringThread; - - public ThreadBoundJcrSessionFactory() { - Class[] interfaces = { Session.class }; - proxiedSession = (Session) Proxy.newProxyInstance(ThreadBoundJcrSessionFactory.class.getClassLoader(), - interfaces, new JcrSessionInvocationHandler()); - } - - /** Logs in to the repository using various strategies. */ - protected synchronized Session login() { - if (!isActive()) - throw new IllegalStateException("Thread bound session factory inactive"); - - // discard session previously attached to this thread - Thread thread = Thread.currentThread(); - if (activeSessions.containsKey(thread.getId())) { - Session oldSession = activeSessions.remove(thread.getId()); - oldSession.logout(); - session.remove(); - } - - Session newSession = null; - // first try to login without credentials, assuming the underlying login - // module will have dealt with authentication (typically using Spring - // Security) - if (!forceDefaultCredentials) - try { - newSession = repository().login(workspace); - } catch (LoginException e1) { - log.warn("Cannot login without credentials: " + e1.getMessage()); - // invalid credentials, go to the next step - } catch (RepositoryException e1) { - // other kind of exception, fail - throw new JcrException("Cannot log in to repository", e1); - } - - // log using default username / password (useful for testing purposes) - if (newSession == null) - try { - SimpleCredentials sc = new SimpleCredentials(defaultUsername, defaultPassword.toCharArray()); - newSession = repository().login(sc, workspace); - } catch (RepositoryException e) { - throw new JcrException("Cannot log in to repository", e); - } - - session.set(newSession); - // Log and monitor new session - if (log.isTraceEnabled()) - log.trace("Logged in to JCR session " + newSession + "; userId=" + newSession.getUserID()); - - // monitoring - activeSessions.put(thread.getId(), newSession); - threads.add(thread); - return newSession; - } - - public Object getObject() { - return proxiedSession; - } - - public void init() throws Exception { - // log.error("SHOULD NOT BE USED ANYMORE"); - monitoringThread = new MonitoringThread(); - monitoringThread.start(); - } - - public void dispose() throws Exception { - // if (activeSessions.size() == 0) - // return; - - if (log.isTraceEnabled()) - log.trace("Cleaning up " + activeSessions.size() + " active JCR sessions..."); - - deactivate(); - for (Session sess : activeSessions.values()) { - JcrUtils.logoutQuietly(sess); - } - activeSessions.clear(); - } - - protected Boolean isActive() { - return active; - } - - protected synchronized void deactivate() { - active = false; - notifyAll(); - } - - protected synchronized void removeSession(Thread thread) { - if (!isActive()) - return; - activeSessions.remove(thread.getId()); - threads.remove(thread); - } - - protected synchronized void cleanDeadThreads() { - if (!isActive()) - return; - Iterator it = threads.iterator(); - while (it.hasNext()) { - Thread thread = it.next(); - if (!thread.isAlive() && isActive()) { - if (activeSessions.containsKey(thread.getId())) { - Session session = activeSessions.get(thread.getId()); - activeSessions.remove(thread.getId()); - session.logout(); - if (log.isTraceEnabled()) - log.trace("Cleaned up JCR session (userID=" + session.getUserID() + ") from dead thread " - + thread.getId()); - } - it.remove(); - } - } - try { - wait(1000); - } catch (InterruptedException e) { - // silent - } - } - - public Class getObjectType() { - return Session.class; - } - - public boolean isSingleton() { - return true; - } - - /** - * Called before a method is actually called, allowing to check the session or - * re-login it (e.g. if authentication has changed). The default implementation - * returns the session. - */ - protected Session preCall(Session session) { - return session; - } - - protected Repository repository() { - if (repository != null) - return repository; - if (repositories != null) { - // hardened for OSGi dynamic services - Iterator it = repositories.iterator(); - if (it.hasNext()) - return it.next(); - } - throw new IllegalStateException("No repository injected"); - } - - // /** Useful for declarative registration of OSGi services (blueprint) */ - // public void register(Repository repository, Map params) { - // this.repository = repository; - // } - // - // /** Useful for declarative registration of OSGi services (blueprint) */ - // public void unregister(Repository repository, Map params) { - // this.repository = null; - // } - - public void setRepository(Repository repository) { - this.repository = repository; - } - - public void setRepositories(List repositories) { - this.repositories = repositories; - } - - public void setDefaultUsername(String defaultUsername) { - this.defaultUsername = defaultUsername; - } - - public void setDefaultPassword(String defaultPassword) { - this.defaultPassword = defaultPassword; - } - - public void setForceDefaultCredentials(Boolean forceDefaultCredentials) { - this.forceDefaultCredentials = forceDefaultCredentials; - } - - public void setWorkspace(String workspace) { - this.workspace = workspace; - } - - protected class JcrSessionInvocationHandler implements InvocationHandler { - - public Object invoke(Object proxy, Method method, Object[] args) throws Throwable, RepositoryException { - Session threadSession = session.get(); - if (threadSession == null) { - if ("logout".equals(method.getName()))// no need to login - return Void.TYPE; - else if ("toString".equals(method.getName()))// maybe logging - return "Uninitialized Argeo thread bound JCR session"; - threadSession = login(); - } - - preCall(threadSession); - Object ret; - try { - ret = method.invoke(threadSession, args); - } catch (InvocationTargetException e) { - Throwable cause = e.getCause(); - if (cause instanceof RepositoryException) - throw (RepositoryException) cause; - else - throw cause; - } - if ("logout".equals(method.getName())) { - session.remove(); - Thread thread = Thread.currentThread(); - removeSession(thread); - if (log.isTraceEnabled()) - log.trace("Logged out JCR session (userId=" + threadSession.getUserID() + ") on thread " - + thread.getId()); - } - return ret; - } - } - - /** Monitors registered thread in order to clean up dead ones. */ - private class MonitoringThread extends Thread { - - public MonitoringThread() { - super("ThreadBound JCR Session Monitor"); - } - - @Override - public void run() { - while (isActive()) { - cleanDeadThreads(); - } - } - - } -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/VersionDiff.java b/org.argeo.cms.jcr/src/org/argeo/jcr/VersionDiff.java deleted file mode 100644 index dab55548b..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/VersionDiff.java +++ /dev/null @@ -1,38 +0,0 @@ -package org.argeo.jcr; - -import java.util.Calendar; -import java.util.Map; - -/** - * Generic Object that enables the creation of history reports based on a JCR - * versionable node. userId and creation date are added to the map of - * PropertyDiff. - * - * These two fields might be null - * - */ -public class VersionDiff { - - private String userId; - private Map diffs; - private Calendar updateTime; - - public VersionDiff(String userId, Calendar updateTime, - Map diffs) { - this.userId = userId; - this.updateTime = updateTime; - this.diffs = diffs; - } - - public String getUserId() { - return userId; - } - - public Map getDiffs() { - return diffs; - } - - public Calendar getUpdateTime() { - return updateTime; - } -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/fs/BinaryChannel.java b/org.argeo.cms.jcr/src/org/argeo/jcr/fs/BinaryChannel.java deleted file mode 100644 index d6550feee..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/fs/BinaryChannel.java +++ /dev/null @@ -1,190 +0,0 @@ -package org.argeo.jcr.fs; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.ByteBuffer; -import java.nio.channels.Channels; -import java.nio.channels.FileChannel; -import java.nio.channels.ReadableByteChannel; -import java.nio.channels.SeekableByteChannel; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; - -import javax.jcr.Binary; -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.nodetype.NodeType; - -import org.argeo.jcr.JcrUtils; - -/** A read/write {@link SeekableByteChannel} based on a {@link Binary}. */ -public class BinaryChannel implements SeekableByteChannel { - private final Node file; - private Binary binary; - private boolean open = true; - - private long position = 0; - - private FileChannel fc = null; - - public BinaryChannel(Node file, Path path) throws RepositoryException, IOException { - this.file = file; - Session session = file.getSession(); - synchronized (session) { - if (file.isNodeType(NodeType.NT_FILE)) { - if (file.hasNode(Node.JCR_CONTENT)) { - Node data = file.getNode(Property.JCR_CONTENT); - this.binary = data.getProperty(Property.JCR_DATA).getBinary(); - } else { - Node data = file.addNode(Node.JCR_CONTENT, NodeType.NT_UNSTRUCTURED); - data.addMixin(NodeType.MIX_LAST_MODIFIED); - try (InputStream in = new ByteArrayInputStream(new byte[0])) { - this.binary = data.getSession().getValueFactory().createBinary(in); - } - data.setProperty(Property.JCR_DATA, this.binary); - - // MIME type - String mime = Files.probeContentType(path); - // String mime = fileTypeMap.getContentType(file.getName()); - data.setProperty(Property.JCR_MIMETYPE, mime); - - session.refresh(true); - session.save(); - session.notifyAll(); - } - } else { - throw new IllegalArgumentException( - "Unsupported file node " + file + " (" + file.getPrimaryNodeType() + ")"); - } - } - } - - @Override - public synchronized boolean isOpen() { - return open; - } - - @Override - public synchronized void close() throws IOException { - if (isModified()) { - Binary newBinary = null; - try { - Session session = file.getSession(); - synchronized (session) { - fc.position(0); - InputStream in = Channels.newInputStream(fc); - newBinary = session.getValueFactory().createBinary(in); - file.getNode(Property.JCR_CONTENT).setProperty(Property.JCR_DATA, newBinary); - session.refresh(true); - session.save(); - open = false; - session.notifyAll(); - } - } catch (RepositoryException e) { - throw new IOException("Cannot close " + file, e); - } finally { - JcrUtils.closeQuietly(newBinary); - // IOUtils.closeQuietly(fc); - if (fc != null) { - fc.close(); - } - } - } else { - clearReadState(); - open = false; - } - } - - @Override - public int read(ByteBuffer dst) throws IOException { - if (isModified()) { - return fc.read(dst); - } else { - - try { - int read; - byte[] arr = dst.array(); - read = binary.read(arr, position); - - if (read != -1) - position = position + read; - return read; - } catch (RepositoryException e) { - throw new IOException("Cannot read into buffer", e); - } - } - } - - @Override - public int write(ByteBuffer src) throws IOException { - int written = getFileChannel().write(src); - return written; - } - - @Override - public long position() throws IOException { - if (isModified()) - return getFileChannel().position(); - else - return position; - } - - @Override - public SeekableByteChannel position(long newPosition) throws IOException { - if (isModified()) { - getFileChannel().position(position); - } else { - this.position = newPosition; - } - return this; - } - - @Override - public long size() throws IOException { - if (isModified()) { - return getFileChannel().size(); - } else { - try { - return binary.getSize(); - } catch (RepositoryException e) { - throw new IOException("Cannot get size", e); - } - } - } - - @Override - public SeekableByteChannel truncate(long size) throws IOException { - getFileChannel().truncate(size); - return this; - } - - private FileChannel getFileChannel() throws IOException { - try { - if (fc == null) { - Path tempPath = Files.createTempFile(getClass().getSimpleName(), null); - fc = FileChannel.open(tempPath, StandardOpenOption.WRITE, StandardOpenOption.READ, - StandardOpenOption.DELETE_ON_CLOSE, StandardOpenOption.SPARSE); - ReadableByteChannel readChannel = Channels.newChannel(binary.getStream()); - fc.transferFrom(readChannel, 0, binary.getSize()); - clearReadState(); - } - return fc; - } catch (RepositoryException e) { - throw new IOException("Cannot get temp file channel", e); - } - } - - private boolean isModified() { - return fc != null; - } - - private void clearReadState() { - position = -1; - JcrUtils.closeQuietly(binary); - binary = null; - } -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrBasicfileAttributes.java b/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrBasicfileAttributes.java deleted file mode 100644 index 7c9711bf0..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrBasicfileAttributes.java +++ /dev/null @@ -1,138 +0,0 @@ -package org.argeo.jcr.fs; - -import static javax.jcr.Property.JCR_CREATED; -import static javax.jcr.Property.JCR_LAST_MODIFIED; - -import java.nio.file.attribute.FileTime; -import java.time.Instant; - -import javax.jcr.Binary; -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.RepositoryException; -import javax.jcr.nodetype.NodeType; - -import org.argeo.jcr.JcrUtils; - -public class JcrBasicfileAttributes implements NodeFileAttributes { - private final Node node; - - private final static FileTime EPOCH = FileTime.fromMillis(0); - - public JcrBasicfileAttributes(Node node) { - if (node == null) - throw new JcrFsException("Node underlying the attributes cannot be null"); - this.node = node; - } - - @Override - public FileTime lastModifiedTime() { - try { - if (node.hasProperty(JCR_LAST_MODIFIED)) { - Instant instant = node.getProperty(JCR_LAST_MODIFIED).getDate().toInstant(); - return FileTime.from(instant); - } else if (node.hasProperty(JCR_CREATED)) { - Instant instant = node.getProperty(JCR_CREATED).getDate().toInstant(); - return FileTime.from(instant); - } -// if (node.isNodeType(NodeType.MIX_LAST_MODIFIED)) { -// Instant instant = node.getProperty(Property.JCR_LAST_MODIFIED).getDate().toInstant(); -// return FileTime.from(instant); -// } - return EPOCH; - } catch (RepositoryException e) { - throw new JcrFsException("Cannot get last modified time", e); - } - } - - @Override - public FileTime lastAccessTime() { - return lastModifiedTime(); - } - - @Override - public FileTime creationTime() { - try { - if (node.hasProperty(JCR_CREATED)) { - Instant instant = node.getProperty(JCR_CREATED).getDate().toInstant(); - return FileTime.from(instant); - } else if (node.hasProperty(JCR_LAST_MODIFIED)) { - Instant instant = node.getProperty(JCR_LAST_MODIFIED).getDate().toInstant(); - return FileTime.from(instant); - } -// if (node.isNodeType(NodeType.MIX_CREATED)) { -// Instant instant = node.getProperty(JCR_CREATED).getDate().toInstant(); -// return FileTime.from(instant); -// } - return EPOCH; - } catch (RepositoryException e) { - throw new JcrFsException("Cannot get creation time", e); - } - } - - @Override - public boolean isRegularFile() { - try { - return node.isNodeType(NodeType.NT_FILE); - } catch (RepositoryException e) { - throw new JcrFsException("Cannot check if regular file", e); - } - } - - @Override - public boolean isDirectory() { - try { - if (node.isNodeType(NodeType.NT_FOLDER)) - return true; - // all other non file nodes - return !(node.isNodeType(NodeType.NT_FILE) || node.isNodeType(NodeType.NT_LINKED_FILE)); - } catch (RepositoryException e) { - throw new JcrFsException("Cannot check if directory", e); - } - } - - @Override - public boolean isSymbolicLink() { - try { - return node.isNodeType(NodeType.NT_LINKED_FILE); - } catch (RepositoryException e) { - throw new JcrFsException("Cannot check if linked file", e); - } - } - - @Override - public boolean isOther() { - return !(isDirectory() || isRegularFile() || isSymbolicLink()); - } - - @Override - public long size() { - if (isRegularFile()) { - Binary binary = null; - try { - binary = node.getNode(Property.JCR_CONTENT).getProperty(Property.JCR_DATA).getBinary(); - return binary.getSize(); - } catch (RepositoryException e) { - throw new JcrFsException("Cannot check size", e); - } finally { - JcrUtils.closeQuietly(binary); - } - } - return -1; - } - - @Override - public Object fileKey() { - try { - return node.getIdentifier(); - } catch (RepositoryException e) { - throw new JcrFsException("Cannot get identifier", e); - } - } - - @Override - public Node getNode() { - return node; - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystem.java b/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystem.java deleted file mode 100644 index 4b329810f..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystem.java +++ /dev/null @@ -1,252 +0,0 @@ -package org.argeo.jcr.fs; - -import java.io.IOException; -import java.nio.file.FileStore; -import java.nio.file.FileSystem; -import java.nio.file.Path; -import java.nio.file.PathMatcher; -import java.nio.file.WatchService; -import java.nio.file.attribute.UserPrincipalLookupService; -import java.nio.file.spi.FileSystemProvider; -import java.util.ArrayList; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; - -import javax.jcr.Credentials; -import javax.jcr.Node; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.nodetype.NodeType; - -import org.argeo.api.acr.fs.AbstractFsStore; -import org.argeo.api.acr.fs.AbstractFsSystem; -import org.argeo.jcr.Jcr; -import org.argeo.jcr.JcrUtils; - -public class JcrFileSystem extends AbstractFsSystem { - private final JcrFileSystemProvider provider; - - private final Repository repository; - private Session session; - private WorkspaceFileStore baseFileStore; - - private Map mounts = new TreeMap<>(); - - private String userHomePath = null; - - @Deprecated - public JcrFileSystem(JcrFileSystemProvider provider, Session session) throws IOException { - super(); - this.provider = provider; - baseFileStore = new WorkspaceFileStore(null, session.getWorkspace()); - this.session = session; -// Node userHome = provider.getUserHome(session); -// if (userHome != null) -// try { -// userHomePath = userHome.getPath(); -// } catch (RepositoryException e) { -// throw new IOException("Cannot retrieve user home path", e); -// } - this.repository = null; - } - - public JcrFileSystem(JcrFileSystemProvider provider, Repository repository) throws IOException { - this(provider, repository, null); - } - - public JcrFileSystem(JcrFileSystemProvider provider, Repository repository, Credentials credentials) - throws IOException { - super(); - this.provider = provider; - this.repository = repository; - try { - this.session = credentials == null ? repository.login() : repository.login(credentials); - baseFileStore = new WorkspaceFileStore(null, session.getWorkspace()); - workspaces: for (String workspaceName : baseFileStore.getWorkspace().getAccessibleWorkspaceNames()) { - if (workspaceName.equals(baseFileStore.getWorkspace().getName())) - continue workspaces;// do not mount base - if (workspaceName.equals("security")) { - continue workspaces;// do not mount security workspace - // TODO make it configurable - } - Session mountSession = credentials == null ? repository.login(workspaceName) - : repository.login(credentials, workspaceName); - String mountPath = JcrPath.separator + workspaceName; - mounts.put(mountPath, new WorkspaceFileStore(mountPath, mountSession.getWorkspace())); - } - } catch (RepositoryException e) { - throw new IOException("Cannot initialise file system", e); - } - - Node userHome = provider.getUserHome(repository); - if (userHome != null) - try { - userHomePath = toFsPath(userHome); - } catch (RepositoryException e) { - throw new IOException("Cannot retrieve user home path", e); - } finally { - JcrUtils.logoutQuietly(Jcr.session(userHome)); - } - } - - public String toFsPath(Node node) throws RepositoryException { - return getFileStore(node).toFsPath(node); - } - - /** Whether this node should be skipped in directory listings */ - public boolean skipNode(Node node) throws RepositoryException { - if (node.isNodeType(NodeType.NT_HIERARCHY_NODE)) - return false; - return true; - } - - public String getUserHomePath() { - return userHomePath; - } - - public WorkspaceFileStore getFileStore(String path) { - WorkspaceFileStore res = baseFileStore; - for (String mountPath : mounts.keySet()) { - if (path.equals(mountPath)) - return mounts.get(mountPath); - if (path.startsWith(mountPath + JcrPath.separator)) { - res = mounts.get(mountPath); - // we keep the last one - } - } - assert res != null; - return res; - } - - public WorkspaceFileStore getFileStore(Node node) throws RepositoryException { - String workspaceName = node.getSession().getWorkspace().getName(); - if (workspaceName.equals(baseFileStore.getWorkspace().getName())) - return baseFileStore; - for (String mountPath : mounts.keySet()) { - WorkspaceFileStore fileStore = mounts.get(mountPath); - if (workspaceName.equals(fileStore.getWorkspace().getName())) - return fileStore; - } - throw new IllegalStateException("No workspace mount found for " + node + " in workspace " + workspaceName); - } - - public Iterator listDirectMounts(Path base) { - String baseStr = base.toString(); - Set res = new HashSet<>(); - mounts: for (String mountPath : mounts.keySet()) { - if (mountPath.equals(baseStr)) - continue mounts; - if (mountPath.startsWith(baseStr)) { - JcrPath path = new JcrPath(this, mountPath); - Path relPath = base.relativize(path); - if (relPath.getNameCount() == 1) - res.add(path); - } - } - return res.iterator(); - } - - public WorkspaceFileStore getBaseFileStore() { - return baseFileStore; - } - - @Override - public FileSystemProvider provider() { - return provider; - } - - @Override - public void close() throws IOException { - JcrUtils.logoutQuietly(session); - for (String mountPath : mounts.keySet()) { - WorkspaceFileStore fileStore = mounts.get(mountPath); - try { - fileStore.close(); - } catch (Exception e) { - e.printStackTrace(); - } - } - } - - @Override - public boolean isOpen() { - return session.isLive(); - } - - @Override - public boolean isReadOnly() { - return false; - } - - @Override - public String getSeparator() { - return JcrPath.separator; - } - - @Override - public Iterable getRootDirectories() { - Set single = new HashSet<>(); - single.add(new JcrPath(this, JcrPath.separator)); - return single; - } - - @Override - public Iterable getFileStores() { - List stores = new ArrayList<>(); - stores.add(baseFileStore); - stores.addAll(mounts.values()); - return stores; - } - - @Override - public Set supportedFileAttributeViews() { - try { - String[] prefixes = session.getNamespacePrefixes(); - Set res = new HashSet<>(); - for (String prefix : prefixes) - res.add(prefix); - res.add("basic"); - return res; - } catch (RepositoryException e) { - throw new JcrFsException("Cannot get supported file attributes views", e); - } - } - - @Override - public Path getPath(String first, String... more) { - StringBuilder sb = new StringBuilder(first); - // TODO Make it more robust - for (String part : more) - sb.append('/').append(part); - return new JcrPath(this, sb.toString()); - } - - @Override - public PathMatcher getPathMatcher(String syntaxAndPattern) { - throw new UnsupportedOperationException(); - } - - @Override - public UserPrincipalLookupService getUserPrincipalLookupService() { - throw new UnsupportedOperationException(); - } - - @Override - public WatchService newWatchService() throws IOException { - throw new UnsupportedOperationException(); - } - -// public Session getSession() { -// return session; -// } - - public Repository getRepository() { - return repository; - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystemProvider.java b/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystemProvider.java deleted file mode 100644 index 74d9a198e..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFileSystemProvider.java +++ /dev/null @@ -1,337 +0,0 @@ -package org.argeo.jcr.fs; - -import java.io.IOException; -import java.nio.channels.SeekableByteChannel; -import java.nio.file.AccessMode; -import java.nio.file.CopyOption; -import java.nio.file.DirectoryNotEmptyException; -import java.nio.file.DirectoryStream; -import java.nio.file.DirectoryStream.Filter; -import java.nio.file.FileStore; -import java.nio.file.LinkOption; -import java.nio.file.NoSuchFileException; -import java.nio.file.OpenOption; -import java.nio.file.Path; -import java.nio.file.attribute.BasicFileAttributes; -import java.nio.file.attribute.FileAttribute; -import java.nio.file.attribute.FileAttributeView; -import java.nio.file.spi.FileSystemProvider; -import java.util.Calendar; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; - -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.PropertyIterator; -import javax.jcr.PropertyType; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.nodetype.NodeType; -import javax.jcr.nodetype.PropertyDefinition; - -import org.argeo.jcr.JcrUtils; - -/** Operations on a {@link JcrFileSystem}. */ -public abstract class JcrFileSystemProvider extends FileSystemProvider { - - @Override - public SeekableByteChannel newByteChannel(Path path, Set options, FileAttribute... attrs) - throws IOException { - Node node = toNode(path); - try { - if (node == null) { - Node parent = toNode(path.getParent()); - if (parent == null) - throw new IOException("No parent directory for " + path); - if (parent.getPrimaryNodeType().isNodeType(NodeType.NT_FILE) - || parent.getPrimaryNodeType().isNodeType(NodeType.NT_LINKED_FILE)) - throw new IOException(path + " parent is a file"); - - String fileName = path.getFileName().toString(); - fileName = Text.escapeIllegalJcrChars(fileName); - node = parent.addNode(fileName, NodeType.NT_FILE); - node.addMixin(NodeType.MIX_CREATED); -// node.addMixin(NodeType.MIX_LAST_MODIFIED); - } - if (!node.isNodeType(NodeType.NT_FILE)) - throw new UnsupportedOperationException(node + " must be a file"); - return new BinaryChannel(node, path); - } catch (RepositoryException e) { - discardChanges(node); - throw new IOException("Cannot read file", e); - } - } - - @Override - public DirectoryStream newDirectoryStream(Path dir, Filter filter) throws IOException { - try { - Node base = toNode(dir); - if (base == null) - throw new IOException(dir + " is not a JCR node"); - JcrFileSystem fileSystem = (JcrFileSystem) dir.getFileSystem(); - return new NodeDirectoryStream(fileSystem, base.getNodes(), fileSystem.listDirectMounts(dir), filter); - } catch (RepositoryException e) { - throw new IOException("Cannot list directory", e); - } - } - - @Override - public void createDirectory(Path dir, FileAttribute... attrs) throws IOException { - Node node = toNode(dir); - try { - if (node == null) { - Node parent = toNode(dir.getParent()); - if (parent == null) - throw new IOException("Parent of " + dir + " does not exist"); - Session session = parent.getSession(); - synchronized (session) { - if (parent.getPrimaryNodeType().isNodeType(NodeType.NT_FILE) - || parent.getPrimaryNodeType().isNodeType(NodeType.NT_LINKED_FILE)) - throw new IOException(dir + " parent is a file"); - String fileName = dir.getFileName().toString(); - fileName = Text.escapeIllegalJcrChars(fileName); - node = parent.addNode(fileName, NodeType.NT_FOLDER); - node.addMixin(NodeType.MIX_CREATED); - node.addMixin(NodeType.MIX_LAST_MODIFIED); - save(session); - } - } else { - // if (!node.getPrimaryNodeType().isNodeType(NodeType.NT_FOLDER)) - // throw new FileExistsException(dir + " exists and is not a directory"); - } - } catch (RepositoryException e) { - discardChanges(node); - throw new IOException("Cannot create directory " + dir, e); - } - } - - @Override - public void delete(Path path) throws IOException { - Node node = toNode(path); - try { - if (node == null) - throw new NoSuchFileException(path + " does not exist"); - Session session = node.getSession(); - synchronized (session) { - session.refresh(false); - if (node.getPrimaryNodeType().isNodeType(NodeType.NT_FILE)) - node.remove(); - else if (node.getPrimaryNodeType().isNodeType(NodeType.NT_FOLDER)) { - if (node.hasNodes())// TODO check only files - throw new DirectoryNotEmptyException(path.toString()); - node.remove(); - } - save(session); - } - } catch (RepositoryException e) { - discardChanges(node); - throw new IOException("Cannot delete " + path, e); - } - - } - - @Override - public void copy(Path source, Path target, CopyOption... options) throws IOException { - Node sourceNode = toNode(source); - Node targetNode = toNode(target); - try { - Session targetSession = targetNode.getSession(); - synchronized (targetSession) { - JcrUtils.copy(sourceNode, targetNode); - save(targetSession); - } - } catch (RepositoryException e) { - discardChanges(sourceNode); - discardChanges(targetNode); - throw new IOException("Cannot copy from " + source + " to " + target, e); - } - } - - @Override - public void move(Path source, Path target, CopyOption... options) throws IOException { - JcrFileSystem sourceFileSystem = (JcrFileSystem) source.getFileSystem(); - WorkspaceFileStore sourceStore = sourceFileSystem.getFileStore(source.toString()); - WorkspaceFileStore targetStore = sourceFileSystem.getFileStore(target.toString()); - try { - if (sourceStore.equals(targetStore)) { - sourceStore.getWorkspace().move(sourceStore.toJcrPath(source.toString()), - targetStore.toJcrPath(target.toString())); - } else { - // TODO implement it - throw new UnsupportedOperationException("Can only move paths within the same workspace."); - } - } catch (RepositoryException e) { - throw new IOException("Cannot move from " + source + " to " + target, e); - } - -// Node sourceNode = toNode(source); -// try { -// Session session = sourceNode.getSession(); -// synchronized (session) { -// session.move(sourceNode.getPath(), target.toString()); -// save(session); -// } -// } catch (RepositoryException e) { -// discardChanges(sourceNode); -// throw new IOException("Cannot move from " + source + " to " + target, e); -// } - } - - @Override - public boolean isSameFile(Path path, Path path2) throws IOException { - if (path.getFileSystem() != path2.getFileSystem()) - return false; - boolean equ = path.equals(path2); - if (equ) - return true; - else { - try { - Node node = toNode(path); - Node node2 = toNode(path2); - return node.isSame(node2); - } catch (RepositoryException e) { - throw new IOException("Cannot check whether " + path + " and " + path2 + " are same", e); - } - } - - } - - @Override - public boolean isHidden(Path path) throws IOException { - return path.getFileName().toString().charAt(0) == '.'; - } - - @Override - public FileStore getFileStore(Path path) throws IOException { - JcrFileSystem fileSystem = (JcrFileSystem) path.getFileSystem(); - return fileSystem.getFileStore(path.toString()); - } - - @Override - public void checkAccess(Path path, AccessMode... modes) throws IOException { - Node node = toNode(path); - if (node == null) - throw new NoSuchFileException(path + " does not exist"); - // TODO check access via JCR api - } - - @Override - public V getFileAttributeView(Path path, Class type, LinkOption... options) { - throw new UnsupportedOperationException(); - } - - @SuppressWarnings("unchecked") - @Override - public A readAttributes(Path path, Class type, LinkOption... options) - throws IOException { - // TODO check if assignable - Node node = toNode(path); - if (node == null) { - throw new IOException("JCR node not found for " + path); - } - return (A) new JcrBasicfileAttributes(node); - } - - @Override - public Map readAttributes(Path path, String attributes, LinkOption... options) throws IOException { - try { - Node node = toNode(path); - String pattern = attributes.replace(',', '|'); - Map res = new HashMap(); - PropertyIterator it = node.getProperties(pattern); - props: while (it.hasNext()) { - Property prop = it.nextProperty(); - PropertyDefinition pd = prop.getDefinition(); - if (pd.isMultiple()) - continue props; - int requiredType = pd.getRequiredType(); - switch (requiredType) { - case PropertyType.LONG: - res.put(prop.getName(), prop.getLong()); - break; - case PropertyType.DOUBLE: - res.put(prop.getName(), prop.getDouble()); - break; - case PropertyType.BOOLEAN: - res.put(prop.getName(), prop.getBoolean()); - break; - case PropertyType.DATE: - res.put(prop.getName(), prop.getDate()); - break; - case PropertyType.BINARY: - byte[] arr = JcrUtils.getBinaryAsBytes(prop); - res.put(prop.getName(), arr); - break; - default: - res.put(prop.getName(), prop.getString()); - } - } - return res; - } catch (RepositoryException e) { - throw new IOException("Cannot read attributes of " + path, e); - } - } - - @Override - public void setAttribute(Path path, String attribute, Object value, LinkOption... options) throws IOException { - Node node = toNode(path); - try { - Session session = node.getSession(); - synchronized (session) { - if (value instanceof byte[]) { - JcrUtils.setBinaryAsBytes(node, attribute, (byte[]) value); - } else if (value instanceof Calendar) { - node.setProperty(attribute, (Calendar) value); - } else { - node.setProperty(attribute, value.toString()); - } - save(session); - } - } catch (RepositoryException e) { - discardChanges(node); - throw new IOException("Cannot set attribute " + attribute + " on " + path, e); - } - } - - protected Node toNode(Path path) { - try { - return ((JcrPath) path).getNode(); - } catch (RepositoryException e) { - throw new JcrFsException("Cannot convert path " + path + " to JCR Node", e); - } - } - - /** Discard changes in the underlying session */ - protected void discardChanges(Node node) { - if (node == null) - return; - try { - // discard changes - node.getSession().refresh(false); - } catch (RepositoryException e) { - e.printStackTrace(); - // TODO log out session? - // TODO use Commons logging? - } - } - - /** Make sure save is robust. */ - protected void save(Session session) throws RepositoryException { - session.refresh(true); - session.save(); - session.notifyAll(); - } - - /** - * To be overriden in order to support the ~ path, with an implementation - * specific concept of user home. - * - * @return null by default - */ - public Node getUserHome(Repository session) { - return null; - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFsException.java b/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFsException.java deleted file mode 100644 index f214fdc44..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrFsException.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.argeo.jcr.fs; - - -/** Exception related to the JCR FS */ -public class JcrFsException extends RuntimeException { - private static final long serialVersionUID = -7973896038244922980L; - - public JcrFsException(String message, Throwable e) { - super(message, e); - } - - public JcrFsException(String message) { - super(message); - } -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrPath.java b/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrPath.java deleted file mode 100644 index 7318b7096..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/fs/JcrPath.java +++ /dev/null @@ -1,384 +0,0 @@ -package org.argeo.jcr.fs; - -import java.nio.file.Path; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.api.acr.fs.AbstractFsPath; - -/** A {@link Path} which contains a reference to a JCR {@link Node}. */ -public class JcrPath extends AbstractFsPath { - final static String separator = "/"; - final static char separatorChar = '/'; - -// private final JcrFileSystem fs; -// /** null for non absolute paths */ -// private final WorkspaceFileStore fileStore; -// private final String[] path;// null means root -// private final boolean absolute; -// -// // optim -// private final int hashCode; - - public JcrPath(JcrFileSystem filesSystem, String path) { - super(filesSystem, path); -// this.fs = filesSystem; -// if (path == null) -// throw new JcrFsException("Path cannot be null"); -// if (path.equals(separator)) {// root -// this.path = null; -// this.absolute = true; -// this.hashCode = 0; -// this.fileStore = fs.getBaseFileStore(); -// return; -// } else if (path.equals("")) {// empty path -// this.path = new String[] { "" }; -// this.absolute = false; -// this.fileStore = null; -// this.hashCode = "".hashCode(); -// return; -// } -// -// if (path.equals("~")) {// home -// path = filesSystem.getUserHomePath(); -// if (path == null) -// throw new JcrFsException("No home directory available"); -// } -// -// this.absolute = path.charAt(0) == separatorChar ? true : false; -// -// this.fileStore = absolute ? fs.getFileStore(path) : null; -// -// String trimmedPath = path.substring(absolute ? 1 : 0, -// path.charAt(path.length() - 1) == separatorChar ? path.length() - 1 : path.length()); -// this.path = trimmedPath.split(separator); -// for (int i = 0; i < this.path.length; i++) { -// this.path[i] = Text.unescapeIllegalJcrChars(this.path[i]); -// } -// this.hashCode = this.path[this.path.length - 1].hashCode(); -// assert !(absolute && fileStore == null); - } - - public JcrPath(JcrFileSystem filesSystem, Node node) throws RepositoryException { - this(filesSystem, filesSystem.getFileStore(node).toFsPath(node)); - } - - /** Internal optimisation */ - private JcrPath(JcrFileSystem filesSystem, WorkspaceFileStore fileStore, String[] path, boolean absolute) { - super(filesSystem, fileStore, path, absolute); -// this.fs = filesSystem; -// this.path = path; -// this.absolute = path == null ? true : absolute; -// if (this.absolute && fileStore == null) -// throw new IllegalArgumentException("Absolute path requires a file store"); -// if (!this.absolute && fileStore != null) -// throw new IllegalArgumentException("A file store should not be provided for a relative path"); -// this.fileStore = fileStore; -// this.hashCode = path == null ? 0 : path[path.length - 1].hashCode(); -// assert !(absolute && fileStore == null); - } - - protected String cleanUpSegment(String segment) { - return Text.unescapeIllegalJcrChars(segment); - } - - @Override - protected JcrPath newInstance(String path) { - return new JcrPath(getFileSystem(), path); - } - - @Override - protected JcrPath newInstance(String[] segments, boolean absolute) { - return new JcrPath(getFileSystem(), getFileStore(), segments, absolute); - - } - -// @Override -// public FileSystem getFileSystem() { -// return fs; -// } -// -// @Override -// public boolean isAbsolute() { -// return absolute; -// } -// -// @Override -// public Path getRoot() { -// if (path == null) -// return this; -// return new JcrPath(fs, separator); -// } -// -// @Override -// public String toString() { -// return toFsPath(path); -// } -// -// private String toFsPath(String[] path) { -// if (path == null) -// return "/"; -// StringBuilder sb = new StringBuilder(); -// if (isAbsolute()) -// sb.append('/'); -// for (int i = 0; i < path.length; i++) { -// if (i != 0) -// sb.append('/'); -// sb.append(path[i]); -// } -// return sb.toString(); -// } - -// @Deprecated -// private String toJcrPath() { -// return toJcrPath(path); -// } -// -// @Deprecated -// private String toJcrPath(String[] path) { -// if (path == null) -// return "/"; -// StringBuilder sb = new StringBuilder(); -// if (isAbsolute()) -// sb.append('/'); -// for (int i = 0; i < path.length; i++) { -// if (i != 0) -// sb.append('/'); -// sb.append(Text.escapeIllegalJcrChars(path[i])); -// } -// return sb.toString(); -// } - -// @Override -// public Path getFileName() { -// if (path == null) -// return null; -// return new JcrPath(fs, path[path.length - 1]); -// } -// -// @Override -// public Path getParent() { -// if (path == null) -// return null; -// if (path.length == 1)// root -// return new JcrPath(fs, separator); -// String[] parentPath = Arrays.copyOfRange(path, 0, path.length - 1); -// if (!absolute) -// return new JcrPath(fs, null, parentPath, absolute); -// else -// return new JcrPath(fs, toFsPath(parentPath)); -// } -// -// @Override -// public int getNameCount() { -// if (path == null) -// return 0; -// return path.length; -// } -// -// @Override -// public Path getName(int index) { -// if (path == null) -// return null; -// return new JcrPath(fs, path[index]); -// } -// -// @Override -// public Path subpath(int beginIndex, int endIndex) { -// if (path == null) -// return null; -// String[] parentPath = Arrays.copyOfRange(path, beginIndex, endIndex); -// return new JcrPath(fs, null, parentPath, false); -// } -// -// @Override -// public boolean startsWith(Path other) { -// return toString().startsWith(other.toString()); -// } -// -// @Override -// public boolean startsWith(String other) { -// return toString().startsWith(other); -// } -// -// @Override -// public boolean endsWith(Path other) { -// return toString().endsWith(other.toString()); -// } -// -// @Override -// public boolean endsWith(String other) { -// return toString().endsWith(other); -// } - -// @Override -// public Path normalize() { -// // always normalized -// return this; -// } - -// @Override -// public Path resolve(Path other) { -// JcrPath otherPath = (JcrPath) other; -// if (otherPath.isAbsolute()) -// return other; -// String[] newPath; -// if (path == null) { -// newPath = new String[otherPath.path.length]; -// System.arraycopy(otherPath.path, 0, newPath, 0, otherPath.path.length); -// } else { -// newPath = new String[path.length + otherPath.path.length]; -// System.arraycopy(path, 0, newPath, 0, path.length); -// System.arraycopy(otherPath.path, 0, newPath, path.length, otherPath.path.length); -// } -// if (!absolute) -// return new JcrPath(fs, null, newPath, absolute); -// else { -// return new JcrPath(fs, toFsPath(newPath)); -// } -// } -// -// @Override -// public final Path resolve(String other) { -// return resolve(getFileSystem().getPath(other)); -// } -// -// @Override -// public final Path resolveSibling(Path other) { -// if (other == null) -// throw new NullPointerException(); -// Path parent = getParent(); -// return (parent == null) ? other : parent.resolve(other); -// } -// -// @Override -// public final Path resolveSibling(String other) { -// return resolveSibling(getFileSystem().getPath(other)); -// } -// -// @Override -// public final Iterator iterator() { -// return new Iterator() { -// private int i = 0; -// -// @Override -// public boolean hasNext() { -// return (i < getNameCount()); -// } -// -// @Override -// public Path next() { -// if (i < getNameCount()) { -// Path result = getName(i); -// i++; -// return result; -// } else { -// throw new NoSuchElementException(); -// } -// } -// -// @Override -// public void remove() { -// throw new UnsupportedOperationException(); -// } -// }; -// } -// -// @Override -// public Path relativize(Path other) { -// if (equals(other)) -// return new JcrPath(fs, ""); -// if (other.startsWith(this)) { -// String p1 = toString(); -// String p2 = other.toString(); -// String relative = p2.substring(p1.length(), p2.length()); -// if (relative.charAt(0) == '/') -// relative = relative.substring(1); -// return new JcrPath(fs, relative); -// } -// throw new IllegalArgumentException(other + " cannot be relativized against " + this); -// } - -// @Override -// public URI toUri() { -// try { -// return new URI(fs.provider().getScheme(), toString(), null); -// } catch (URISyntaxException e) { -// throw new JcrFsException("Cannot create URI for " + toString(), e); -// } -// } -// -// @Override -// public Path toAbsolutePath() { -// if (isAbsolute()) -// return this; -// return new JcrPath(fs, fileStore, path, true); -// } -// -// @Override -// public Path toRealPath(LinkOption... options) throws IOException { -// return this; -// } -// -// @Override -// public File toFile() { -// throw new UnsupportedOperationException(); -// } - - public Node getNode() throws RepositoryException { - if (!isAbsolute())// TODO default dir - throw new JcrFsException("Cannot get a JCR node from a relative path"); - assert getFileStore() != null; - return getFileStore().toNode(getSegments()); -// String pathStr = toJcrPath(); -// Session session = fs.getSession(); -// // TODO synchronize on the session ? -// if (!session.itemExists(pathStr)) -// return null; -// return session.getNode(pathStr); - } -// -// @Override -// public boolean equals(Object obj) { -// if (!(obj instanceof JcrPath)) -// return false; -// JcrPath other = (JcrPath) obj; -// -// if (path == null) {// root -// if (other.path == null)// root -// return true; -// else -// return false; -// } else { -// if (other.path == null)// root -// return false; -// } -// // non root -// if (path.length != other.path.length) -// return false; -// for (int i = 0; i < path.length; i++) { -// if (!path[i].equals(other.path[i])) -// return false; -// } -// return true; -// } - -// @Override -// public int hashCode() { -// return hashCode; -// } - -// @Override -// protected Object clone() throws CloneNotSupportedException { -// return new JcrPath(fs, toString()); -// } - -// @Override -// protected void finalize() throws Throwable { -// Arrays.fill(path, null); -// } - - - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/fs/NodeDirectoryStream.java b/org.argeo.cms.jcr/src/org/argeo/jcr/fs/NodeDirectoryStream.java deleted file mode 100644 index eda07a548..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/fs/NodeDirectoryStream.java +++ /dev/null @@ -1,77 +0,0 @@ -package org.argeo.jcr.fs; - -import java.io.IOException; -import java.nio.file.DirectoryStream; -import java.nio.file.Path; -import java.util.Iterator; - -import javax.jcr.Node; -import javax.jcr.NodeIterator; - -public class NodeDirectoryStream implements DirectoryStream { - private final JcrFileSystem fs; - private final NodeIterator nodeIterator; - private final Iterator additionalPaths; - private final Filter filter; - - public NodeDirectoryStream(JcrFileSystem fs, NodeIterator nodeIterator, Iterator additionalPaths, - Filter filter) { - this.fs = fs; - this.nodeIterator = nodeIterator; - this.additionalPaths = additionalPaths; - this.filter = filter; - } - - @Override - public void close() throws IOException { - } - - @Override - public Iterator iterator() { - return new Iterator() { - private JcrPath next = null; - - @Override - public synchronized boolean hasNext() { - if (next != null) - return true; - nodes: while (nodeIterator.hasNext()) { - try { - Node node = nodeIterator.nextNode(); - String nodeName = node.getName(); - if (nodeName.startsWith("rep:") || nodeName.startsWith("jcr:")) - continue nodes; - if (fs.skipNode(node)) - continue nodes; - next = new JcrPath(fs, node); - if (filter != null) { - if (filter.accept(next)) - break nodes; - } else - break nodes; - } catch (Exception e) { - throw new JcrFsException("Could not get next path", e); - } - } - - if (next == null) { - if (additionalPaths.hasNext()) - next = additionalPaths.next(); - } - - return next != null; - } - - @Override - public synchronized Path next() { - if (!hasNext())// make sure has next has been called - return null; - JcrPath res = next; - next = null; - return res; - } - - }; - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/fs/NodeFileAttributes.java b/org.argeo.cms.jcr/src/org/argeo/jcr/fs/NodeFileAttributes.java deleted file mode 100644 index 8054d52f8..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/fs/NodeFileAttributes.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.argeo.jcr.fs; - -import java.nio.file.attribute.BasicFileAttributes; - -import javax.jcr.Node; - -public interface NodeFileAttributes extends BasicFileAttributes { - public Node getNode(); -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/fs/Text.java b/org.argeo.cms.jcr/src/org/argeo/jcr/fs/Text.java deleted file mode 100644 index 4643c8c9c..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/fs/Text.java +++ /dev/null @@ -1,877 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one or more - * contributor license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright ownership. - * The ASF licenses this file to You under the Apache License, Version 2.0 - * (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.argeo.jcr.fs; - -import java.io.ByteArrayOutputStream; -import java.io.UnsupportedEncodingException; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.ArrayList; -import java.util.BitSet; -import java.util.Properties; - -/** - * Hacked from org.apache.jackrabbit.util.Text in Jackrabbit JCR Commons - * This Class provides some text related utilities - */ -class Text { - - /** - * Hidden constructor. - */ - private Text() { - } - - /** - * used for the md5 - */ - public static final char[] hexTable = "0123456789abcdef".toCharArray(); - - /** - * Calculate an MD5 hash of the string given. - * - * @param data - * the data to encode - * @param enc - * the character encoding to use - * @return a hex encoded string of the md5 digested input - */ - public static String md5(String data, String enc) throws UnsupportedEncodingException { - try { - return digest("MD5", data.getBytes(enc)); - } catch (NoSuchAlgorithmException e) { - throw new InternalError("MD5 digest not available???"); - } - } - - /** - * Calculate an MD5 hash of the string given using 'utf-8' encoding. - * - * @param data - * the data to encode - * @return a hex encoded string of the md5 digested input - */ - public static String md5(String data) { - try { - return md5(data, "utf-8"); - } catch (UnsupportedEncodingException e) { - throw new InternalError("UTF8 digest not available???"); - } - } - - /** - * Digest the plain string using the given algorithm. - * - * @param algorithm - * The alogrithm for the digest. This algorithm must be supported - * by the MessageDigest class. - * @param data - * The plain text String to be digested. - * @param enc - * The character encoding to use - * @return The digested plain text String represented as Hex digits. - * @throws java.security.NoSuchAlgorithmException - * if the desired algorithm is not supported by the - * MessageDigest class. - * @throws java.io.UnsupportedEncodingException - * if the encoding is not supported - */ - public static String digest(String algorithm, String data, String enc) - throws NoSuchAlgorithmException, UnsupportedEncodingException { - - return digest(algorithm, data.getBytes(enc)); - } - - /** - * Digest the plain string using the given algorithm. - * - * @param algorithm - * The algorithm for the digest. This algorithm must be supported - * by the MessageDigest class. - * @param data - * the data to digest with the given algorithm - * @return The digested plain text String represented as Hex digits. - * @throws java.security.NoSuchAlgorithmException - * if the desired algorithm is not supported by the - * MessageDigest class. - */ - public static String digest(String algorithm, byte[] data) throws NoSuchAlgorithmException { - - MessageDigest md = MessageDigest.getInstance(algorithm); - byte[] digest = md.digest(data); - StringBuilder res = new StringBuilder(digest.length * 2); - for (byte b : digest) { - res.append(hexTable[(b >> 4) & 15]); - res.append(hexTable[b & 15]); - } - return res.toString(); - } - - /** - * returns an array of strings decomposed of the original string, split at - * every occurrence of 'ch'. if 2 'ch' follow each other with no - * intermediate characters, empty "" entries are avoided. - * - * @param str - * the string to decompose - * @param ch - * the character to use a split pattern - * @return an array of strings - */ - public static String[] explode(String str, int ch) { - return explode(str, ch, false); - } - - /** - * returns an array of strings decomposed of the original string, split at - * every occurrence of 'ch'. - * - * @param str - * the string to decompose - * @param ch - * the character to use a split pattern - * @param respectEmpty - * if true, empty elements are generated - * @return an array of strings - */ - public static String[] explode(String str, int ch, boolean respectEmpty) { - if (str == null || str.length() == 0) { - return new String[0]; - } - - ArrayList strings = new ArrayList(); - int pos; - int lastpos = 0; - - // add snipples - while ((pos = str.indexOf(ch, lastpos)) >= 0) { - if (pos - lastpos > 0 || respectEmpty) { - strings.add(str.substring(lastpos, pos)); - } - lastpos = pos + 1; - } - // add rest - if (lastpos < str.length()) { - strings.add(str.substring(lastpos)); - } else if (respectEmpty && lastpos == str.length()) { - strings.add(""); - } - - // return string array - return strings.toArray(new String[strings.size()]); - } - - /** - * Concatenates all strings in the string array using the specified - * delimiter. - * - * @param arr - * @param delim - * @return the concatenated string - */ - public static String implode(String[] arr, String delim) { - StringBuilder buf = new StringBuilder(); - for (int i = 0; i < arr.length; i++) { - if (i > 0) { - buf.append(delim); - } - buf.append(arr[i]); - } - return buf.toString(); - } - - /** - * Replaces all occurrences of oldString in text - * with newString. - * - * @param text - * @param oldString - * old substring to be replaced with newString - * @param newString - * new substring to replace occurrences of oldString - * @return a string - */ - public static String replace(String text, String oldString, String newString) { - if (text == null || oldString == null || newString == null) { - throw new IllegalArgumentException("null argument"); - } - int pos = text.indexOf(oldString); - if (pos == -1) { - return text; - } - int lastPos = 0; - StringBuilder sb = new StringBuilder(text.length()); - while (pos != -1) { - sb.append(text.substring(lastPos, pos)); - sb.append(newString); - lastPos = pos + oldString.length(); - pos = text.indexOf(oldString, lastPos); - } - if (lastPos < text.length()) { - sb.append(text.substring(lastPos)); - } - return sb.toString(); - } - - /** - * Replaces XML characters in the given string that might need escaping as - * XML text or attribute - * - * @param text - * text to be escaped - * @return a string - */ - public static String encodeIllegalXMLCharacters(String text) { - return encodeMarkupCharacters(text, false); - } - - /** - * Replaces HTML characters in the given string that might need escaping as - * HTML text or attribute - * - * @param text - * text to be escaped - * @return a string - */ - public static String encodeIllegalHTMLCharacters(String text) { - return encodeMarkupCharacters(text, true); - } - - private static String encodeMarkupCharacters(String text, boolean isHtml) { - if (text == null) { - throw new IllegalArgumentException("null argument"); - } - StringBuilder buf = null; - int length = text.length(); - int pos = 0; - for (int i = 0; i < length; i++) { - int ch = text.charAt(i); - switch (ch) { - case '<': - case '>': - case '&': - case '"': - case '\'': - if (buf == null) { - buf = new StringBuilder(); - } - if (i > 0) { - buf.append(text.substring(pos, i)); - } - pos = i + 1; - break; - default: - continue; - } - if (ch == '<') { - buf.append("<"); - } else if (ch == '>') { - buf.append(">"); - } else if (ch == '&') { - buf.append("&"); - } else if (ch == '"') { - buf.append("""); - } else if (ch == '\'') { - buf.append(isHtml ? "'" : "'"); - } - } - if (buf == null) { - return text; - } else { - if (pos < length) { - buf.append(text.substring(pos)); - } - return buf.toString(); - } - } - - /** - * The list of characters that are not encoded by the escape() - * and unescape() METHODS. They contains the characters as - * defined 'unreserved' in section 2.3 of the RFC 2396 'URI generic syntax': - *

- * - *

-	 * unreserved  = alphanum | mark
-	 * mark        = "-" | "_" | "." | "!" | "~" | "*" | "'" | "(" | ")"
-	 * 
- */ - public static BitSet URISave; - - /** - * Same as {@link #URISave} but also contains the '/' - */ - public static BitSet URISaveEx; - - static { - URISave = new BitSet(256); - int i; - for (i = 'a'; i <= 'z'; i++) { - URISave.set(i); - } - for (i = 'A'; i <= 'Z'; i++) { - URISave.set(i); - } - for (i = '0'; i <= '9'; i++) { - URISave.set(i); - } - URISave.set('-'); - URISave.set('_'); - URISave.set('.'); - URISave.set('!'); - URISave.set('~'); - URISave.set('*'); - URISave.set('\''); - URISave.set('('); - URISave.set(')'); - - URISaveEx = (BitSet) URISave.clone(); - URISaveEx.set('/'); - } - - /** - * Does an URL encoding of the string using the - * escape character. The characters that don't need encoding - * are those defined 'unreserved' in section 2.3 of the 'URI generic syntax' - * RFC 2396, but without the escape character. - * - * @param string - * the string to encode. - * @param escape - * the escape character. - * @return the escaped string - * @throws NullPointerException - * if string is null. - */ - public static String escape(String string, char escape) { - return escape(string, escape, false); - } - - /** - * Does an URL encoding of the string using the - * escape character. The characters that don't need encoding - * are those defined 'unreserved' in section 2.3 of the 'URI generic syntax' - * RFC 2396, but without the escape character. If isPath is - * true, additionally the slash '/' is ignored, too. - * - * @param string - * the string to encode. - * @param escape - * the escape character. - * @param isPath - * if true, the string is treated as path - * @return the escaped string - * @throws NullPointerException - * if string is null. - */ - public static String escape(String string, char escape, boolean isPath) { - try { - BitSet validChars = isPath ? URISaveEx : URISave; - byte[] bytes = string.getBytes("utf-8"); - StringBuilder out = new StringBuilder(bytes.length); - for (byte aByte : bytes) { - int c = aByte & 0xff; - if (validChars.get(c) && c != escape) { - out.append((char) c); - } else { - out.append(escape); - out.append(hexTable[(c >> 4) & 0x0f]); - out.append(hexTable[(c) & 0x0f]); - } - } - return out.toString(); - } catch (UnsupportedEncodingException e) { - throw new InternalError(e.toString()); - } - } - - /** - * Does a URL encoding of the string. The characters that don't - * need encoding are those defined 'unreserved' in section 2.3 of the 'URI - * generic syntax' RFC 2396. - * - * @param string - * the string to encode - * @return the escaped string - * @throws NullPointerException - * if string is null. - */ - public static String escape(String string) { - return escape(string, '%'); - } - - /** - * Does a URL encoding of the path. The characters that don't - * need encoding are those defined 'unreserved' in section 2.3 of the 'URI - * generic syntax' RFC 2396. In contrast to the {@link #escape(String)} - * method, not the entire path string is escaped, but every individual part - * (i.e. the slashes are not escaped). - * - * @param path - * the path to encode - * @return the escaped path - * @throws NullPointerException - * if path is null. - */ - public static String escapePath(String path) { - return escape(path, '%', true); - } - - /** - * Does a URL decoding of the string using the - * escape character. Please note that in opposite to the - * {@link java.net.URLDecoder} it does not transform the + into spaces. - * - * @param string - * the string to decode - * @param escape - * the escape character - * @return the decoded string - * @throws NullPointerException - * if string is null. - * @throws IllegalArgumentException - * if the 2 characters following the escape character do not - * represent a hex-number or if not enough characters follow an - * escape character - */ - public static String unescape(String string, char escape) { - try { - byte[] utf8 = string.getBytes("utf-8"); - - // Check whether escape occurs at invalid position - if ((utf8.length >= 1 && utf8[utf8.length - 1] == escape) - || (utf8.length >= 2 && utf8[utf8.length - 2] == escape)) { - throw new IllegalArgumentException("Premature end of escape sequence at end of input"); - } - - ByteArrayOutputStream out = new ByteArrayOutputStream(utf8.length); - for (int k = 0; k < utf8.length; k++) { - byte b = utf8[k]; - if (b == escape) { - out.write((decodeDigit(utf8[++k]) << 4) + decodeDigit(utf8[++k])); - } else { - out.write(b); - } - } - - return new String(out.toByteArray(), "utf-8"); - } catch (UnsupportedEncodingException e) { - throw new InternalError(e.toString()); - } - } - - /** - * Does a URL decoding of the string. Please note that in - * opposite to the {@link java.net.URLDecoder} it does not transform the + - * into spaces. - * - * @param string - * the string to decode - * @return the decoded string - * @throws NullPointerException - * if string is null. - * @throws ArrayIndexOutOfBoundsException - * if not enough character follow an escape character - * @throws IllegalArgumentException - * if the 2 characters following the escape character do not - * represent a hex-number. - */ - public static String unescape(String string) { - return unescape(string, '%'); - } - - /** - * Escapes all illegal JCR name characters of a string. The encoding is - * loosely modeled after URI encoding, but only encodes the characters it - * absolutely needs to in order to make the resulting string a valid JCR - * name. Use {@link #unescapeIllegalJcrChars(String)} for decoding. - *

- * QName EBNF:
- *

simplename ::= onecharsimplename | twocharsimplename | - * threeormorecharname onecharsimplename ::= (* Any Unicode character - * except: '.', '/', ':', '[', ']', '*', '|' or any whitespace character *) - * twocharsimplename ::= '.' onecharsimplename | onecharsimplename '.' | - * onecharsimplename onecharsimplename threeormorecharname ::= nonspace - * string nonspace string ::= char | string char char ::= nonspace | ' ' - * nonspace ::= (* Any Unicode character except: '/', ':', '[', ']', '*', - * '|' or any whitespace character *) - * - * @param name - * the name to escape - * @return the escaped name - */ - public static String escapeIllegalJcrChars(String name) { - return escapeIllegalChars(name, "%/:[]*|\t\r\n"); - } - - /** - * Escapes all illegal JCR 1.0 name characters of a string. Use - * {@link #unescapeIllegalJcrChars(String)} for decoding. - *

- * QName EBNF:
- *

simplename ::= onecharsimplename | twocharsimplename | - * threeormorecharname onecharsimplename ::= (* Any Unicode character - * except: '.', '/', ':', '[', ']', '*', ''', '"', '|' or any whitespace - * character *) twocharsimplename ::= '.' onecharsimplename | - * onecharsimplename '.' | onecharsimplename onecharsimplename - * threeormorecharname ::= nonspace string nonspace string ::= char | string - * char char ::= nonspace | ' ' nonspace ::= (* Any Unicode character - * except: '/', ':', '[', ']', '*', ''', '"', '|' or any whitespace - * character *) - * - * @since Apache Jackrabbit 2.3.2 and 2.2.10 - * @see
JCR-3128 - * @param name - * the name to escape - * @return the escaped name - */ - public static String escapeIllegalJcr10Chars(String name) { - return escapeIllegalChars(name, "%/:[]*'\"|\t\r\n"); - } - - private static String escapeIllegalChars(String name, String illegal) { - StringBuilder buffer = new StringBuilder(name.length() * 2); - for (int i = 0; i < name.length(); i++) { - char ch = name.charAt(i); - if (illegal.indexOf(ch) != -1 || (ch == '.' && name.length() < 3) - || (ch == ' ' && (i == 0 || i == name.length() - 1))) { - buffer.append('%'); - buffer.append(Character.toUpperCase(Character.forDigit(ch / 16, 16))); - buffer.append(Character.toUpperCase(Character.forDigit(ch % 16, 16))); - } else { - buffer.append(ch); - } - } - return buffer.toString(); - } - - /** - * Escapes illegal XPath search characters at the end of a string. - *

- * Example:
- * A search string like 'test?' will run into a ParseException documented in - * http://issues.apache.org/jira/browse/JCR-1248 - * - * @param s - * the string to encode - * @return the escaped string - */ - public static String escapeIllegalXpathSearchChars(String s) { - StringBuilder sb = new StringBuilder(); - sb.append(s.substring(0, (s.length() - 1))); - char c = s.charAt(s.length() - 1); - // NOTE: keep this in sync with _ESCAPED_CHAR below! - if (c == '!' || c == '(' || c == ':' || c == '^' || c == '[' || c == ']' || c == '{' || c == '}' || c == '?') { - sb.append('\\'); - } - sb.append(c); - return sb.toString(); - } - - /** - * Unescapes previously escaped jcr chars. - *

- * Please note, that this does not exactly the same as the url related - * {@link #unescape(String)}, since it handles the byte-encoding - * differently. - * - * @param name - * the name to unescape - * @return the unescaped name - */ - public static String unescapeIllegalJcrChars(String name) { - StringBuilder buffer = new StringBuilder(name.length()); - int i = name.indexOf('%'); - while (i > -1 && i + 2 < name.length()) { - buffer.append(name.toCharArray(), 0, i); - int a = Character.digit(name.charAt(i + 1), 16); - int b = Character.digit(name.charAt(i + 2), 16); - if (a > -1 && b > -1) { - buffer.append((char) (a * 16 + b)); - name = name.substring(i + 3); - } else { - buffer.append('%'); - name = name.substring(i + 1); - } - i = name.indexOf('%'); - } - buffer.append(name); - return buffer.toString(); - } - - /** - * Returns the name part of the path. If the given path is already a name - * (i.e. contains no slashes) it is returned. - * - * @param path - * the path - * @return the name part or null if path is - * null. - */ - public static String getName(String path) { - return getName(path, '/'); - } - - /** - * Returns the name part of the path, delimited by the given - * delim. If the given path is already a name (i.e. contains no - * delim characters) it is returned. - * - * @param path - * the path - * @param delim - * the delimiter - * @return the name part or null if path is - * null. - */ - public static String getName(String path, char delim) { - return path == null ? null : path.substring(path.lastIndexOf(delim) + 1); - } - - /** - * Same as {@link #getName(String)} but adding the possibility to pass paths - * that end with a trailing '/' - * - * @see #getName(String) - */ - public static String getName(String path, boolean ignoreTrailingSlash) { - if (ignoreTrailingSlash && path != null && path.endsWith("/") && path.length() > 1) { - path = path.substring(0, path.length() - 1); - } - return getName(path); - } - - /** - * Returns the namespace prefix of the given qname. If the - * prefix is missing, an empty string is returned. Please note, that this - * method does not validate the name or prefix. - *

- * the qname has the format: qname := [prefix ':'] local; - * - * @param qname - * a qualified name - * @return the prefix of the name or "". - * - * @see #getLocalName(String) - * - * @throws NullPointerException - * if qname is null - */ - public static String getNamespacePrefix(String qname) { - int pos = qname.indexOf(':'); - return pos >= 0 ? qname.substring(0, pos) : ""; - } - - /** - * Returns the local name of the given qname. Please note, that - * this method does not validate the name. - *

- * the qname has the format: qname := [prefix ':'] local; - * - * @param qname - * a qualified name - * @return the localname - * - * @see #getNamespacePrefix(String) - * - * @throws NullPointerException - * if qname is null - */ - public static String getLocalName(String qname) { - int pos = qname.indexOf(':'); - return pos >= 0 ? qname.substring(pos + 1) : qname; - } - - /** - * Determines, if two paths denote hierarchical siblins. - * - * @param p1 - * first path - * @param p2 - * second path - * @return true if on same level, false otherwise - */ - public static boolean isSibling(String p1, String p2) { - int pos1 = p1.lastIndexOf('/'); - int pos2 = p2.lastIndexOf('/'); - return (pos1 == pos2 && pos1 >= 0 && p1.regionMatches(0, p2, 0, pos1)); - } - - /** - * Determines if the descendant path is hierarchical a - * descendant of path. - * - * @param path - * the current path - * @param descendant - * the potential descendant - * @return true if the descendant is a descendant; - * false otherwise. - */ - public static boolean isDescendant(String path, String descendant) { - String pattern = path.endsWith("/") ? path : path + "/"; - return !pattern.equals(descendant) && descendant.startsWith(pattern); - } - - /** - * Determines if the descendant path is hierarchical a - * descendant of path or equal to it. - * - * @param path - * the path to check - * @param descendant - * the potential descendant - * @return true if the descendant is a descendant - * or equal; false otherwise. - */ - public static boolean isDescendantOrEqual(String path, String descendant) { - if (path.equals(descendant)) { - return true; - } else { - String pattern = path.endsWith("/") ? path : path + "/"; - return descendant.startsWith(pattern); - } - } - - /** - * Returns the nth relative parent of the path, where n=level. - *

- * Example:
- * - * Text.getRelativeParent("/foo/bar/test", 1) == "/foo/bar" - * - * - * @param path - * the path of the page - * @param level - * the level of the parent - */ - public static String getRelativeParent(String path, int level) { - int idx = path.length(); - while (level > 0) { - idx = path.lastIndexOf('/', idx - 1); - if (idx < 0) { - return ""; - } - level--; - } - return (idx == 0) ? "/" : path.substring(0, idx); - } - - /** - * Same as {@link #getRelativeParent(String, int)} but adding the - * possibility to pass paths that end with a trailing '/' - * - * @see #getRelativeParent(String, int) - */ - public static String getRelativeParent(String path, int level, boolean ignoreTrailingSlash) { - if (ignoreTrailingSlash && path.endsWith("/") && path.length() > 1) { - path = path.substring(0, path.length() - 1); - } - return getRelativeParent(path, level); - } - - /** - * Returns the nth absolute parent of the path, where n=level. - *

- * Example:
- * - * Text.getAbsoluteParent("/foo/bar/test", 1) == "/foo/bar" - * - * - * @param path - * the path of the page - * @param level - * the level of the parent - */ - public static String getAbsoluteParent(String path, int level) { - int idx = 0; - int len = path.length(); - while (level >= 0 && idx < len) { - idx = path.indexOf('/', idx + 1); - if (idx < 0) { - idx = len; - } - level--; - } - return level >= 0 ? "" : path.substring(0, idx); - } - - /** - * Performs variable replacement on the given string value. Each - * ${...} sequence within the given value is replaced with the - * value of the named parser variable. If a variable is not found in the - * properties an IllegalArgumentException is thrown unless - * ignoreMissing is true. In the later case, the - * missing variable is replaced by the empty string. - * - * @param value - * the original value - * @param ignoreMissing - * if true, missing variables are replaced by the - * empty string. - * @return value after variable replacements - * @throws IllegalArgumentException - * if the replacement of a referenced variable is not found - */ - public static String replaceVariables(Properties variables, String value, boolean ignoreMissing) - throws IllegalArgumentException { - StringBuilder result = new StringBuilder(); - - // Value: - // +--+-+--------+-+-----------------+ - // | |p|--> |q|--> | - // +--+-+--------+-+-----------------+ - int p = 0, q = value.indexOf("${"); // Find first ${ - while (q != -1) { - result.append(value.substring(p, q)); // Text before ${ - p = q; - q = value.indexOf("}", q + 2); // Find } - if (q != -1) { - String variable = value.substring(p + 2, q); - String replacement = variables.getProperty(variable); - if (replacement == null) { - if (ignoreMissing) { - replacement = ""; - } else { - throw new IllegalArgumentException("Replacement not found for ${" + variable + "}."); - } - } - result.append(replacement); - p = q + 1; - q = value.indexOf("${", p); // Find next ${ - } - } - result.append(value.substring(p, value.length())); // Trailing text - - return result.toString(); - } - - private static byte decodeDigit(byte b) { - if (b >= 0x30 && b <= 0x39) { - return (byte) (b - 0x30); - } else if (b >= 0x41 && b <= 0x46) { - return (byte) (b - 0x37); - } else if (b >= 0x61 && b <= 0x66) { - return (byte) (b - 0x57); - } else { - throw new IllegalArgumentException("Escape sequence is not hexadecimal: " + (char) b); - } - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/fs/WorkspaceFileStore.java b/org.argeo.cms.jcr/src/org/argeo/jcr/fs/WorkspaceFileStore.java deleted file mode 100644 index ce4205a97..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/fs/WorkspaceFileStore.java +++ /dev/null @@ -1,192 +0,0 @@ -package org.argeo.jcr.fs; - -import java.io.IOException; -import java.nio.file.FileStore; -import java.nio.file.attribute.FileAttributeView; -import java.nio.file.attribute.FileStoreAttributeView; -import java.util.Arrays; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.Workspace; - -import org.argeo.api.acr.fs.AbstractFsStore; -import org.argeo.jcr.JcrUtils; - -/** A {@link FileStore} implementation based on JCR {@link Workspace}. */ -public class WorkspaceFileStore extends AbstractFsStore { - private final String mountPath; - private final Workspace workspace; - private final String workspaceName; - private final int mountDepth; - - public WorkspaceFileStore(String mountPath, Workspace workspace) { - if ("/".equals(mountPath) || "".equals(mountPath)) - throw new IllegalArgumentException( - "Mount path '" + mountPath + "' is unsupported, use null for the base file store"); - if (mountPath != null && !mountPath.startsWith(JcrPath.separator)) - throw new IllegalArgumentException("Mount path '" + mountPath + "' cannot end with /"); - if (mountPath != null && mountPath.endsWith(JcrPath.separator)) - throw new IllegalArgumentException("Mount path '" + mountPath + "' cannot end with /"); - this.mountPath = mountPath; - if (mountPath == null) - mountDepth = 0; - else { - mountDepth = mountPath.split(JcrPath.separator).length - 1; - } - this.workspace = workspace; - this.workspaceName = workspace.getName(); - } - - public void close() { - JcrUtils.logoutQuietly(workspace.getSession()); - } - - @Override - public String name() { - return workspace.getName(); - } - - @Override - public String type() { - return "workspace"; - } - - @Override - public boolean isReadOnly() { - return false; - } - - @Override - public long getTotalSpace() throws IOException { - return 0; - } - - @Override - public long getUsableSpace() throws IOException { - return 0; - } - - @Override - public long getUnallocatedSpace() throws IOException { - return 0; - } - - @Override - public boolean supportsFileAttributeView(Class type) { - return false; - } - - @Override - public boolean supportsFileAttributeView(String name) { - return false; - } - - @Override - public V getFileStoreAttributeView(Class type) { - return null; - } - - @Override - public Object getAttribute(String attribute) throws IOException { - return workspace.getSession().getRepository().getDescriptor(attribute); - } - - public Workspace getWorkspace() { - return workspace; - } - - public String toFsPath(Node node) throws RepositoryException { - String nodeWorkspaceName = node.getSession().getWorkspace().getName(); - if (!nodeWorkspaceName.equals(workspace.getName())) - throw new IllegalArgumentException("Icompatible " + node + " from workspace '" + nodeWorkspaceName - + "' in file store '" + workspace.getName() + "'"); - return mountPath == null ? node.getPath() : mountPath + node.getPath(); - } - - public boolean isBase() { - return mountPath == null; - } - - Node toNode(String[] fullPath) throws RepositoryException { - String jcrPath = toJcrPath(fullPath); - Session session = workspace.getSession(); - if (!session.itemExists(jcrPath)) - return null; - Node node = session.getNode(jcrPath); - return node; - } - - String toJcrPath(String fsPath) { - if (fsPath.length() == 1) - return toJcrPath((String[]) null);// root - String[] arr = fsPath.substring(1).split("/"); -// if (arr.length == 0 || (arr.length == 1 && arr[0].equals(""))) -// return toJcrPath((String[]) null);// root -// else - return toJcrPath(arr); - } - - private String toJcrPath(String[] path) { - if (path == null) - return "/"; - if (path.length < mountDepth) - throw new IllegalArgumentException( - "Path " + Arrays.asList(path) + " is no compatible with mount " + mountPath); - - if (!isBase()) { - // check mount compatibility - StringBuilder mount = new StringBuilder(); - mount.append('/'); - for (int i = 0; i < mountDepth; i++) { - if (i != 0) - mount.append('/'); - mount.append(Text.escapeIllegalJcrChars(path[i])); - } - if (!mountPath.equals(mount.toString())) - throw new IllegalArgumentException( - "Path " + Arrays.asList(path) + " is no compatible with mount " + mountPath); - } - - StringBuilder sb = new StringBuilder(); - sb.append('/'); - for (int i = mountDepth; i < path.length; i++) { - if (i != mountDepth) - sb.append('/'); - sb.append(Text.escapeIllegalJcrChars(path[i])); - } - return sb.toString(); - } - - public String getMountPath() { - return mountPath; - } - - public String getWorkspaceName() { - return workspaceName; - } - - public int getMountDepth() { - return mountDepth; - } - - @Override - public int hashCode() { - return workspaceName.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (!(obj instanceof WorkspaceFileStore)) - return false; - WorkspaceFileStore other = (WorkspaceFileStore) obj; - return workspaceName.equals(other.workspaceName); - } - - @Override - public String toString() { - return "WorkspaceFileStore " + workspaceName; - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/fs/package-info.java b/org.argeo.cms.jcr/src/org/argeo/jcr/fs/package-info.java deleted file mode 100644 index 0cdfdaf43..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/fs/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Java NIO file system implementation based on plain JCR. */ -package org.argeo.jcr.fs; \ No newline at end of file diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/jcrx.cnd b/org.argeo.cms.jcr/src/org/argeo/jcr/jcrx.cnd deleted file mode 100644 index 3eb0e7a3d..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/jcrx.cnd +++ /dev/null @@ -1,16 +0,0 @@ -// -// JCR EXTENSIONS -// - - -[jcrx:xmlvalue] -- * -+ jcr:xmltext (jcrx:xmltext) = jcrx:xmltext - -[jcrx:xmltext] - - jcr:xmlcharacters (STRING) mandatory - -[jcrx:csum] -mixin - - jcrx:sum (STRING) * - \ No newline at end of file diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/package-info.java b/org.argeo.cms.jcr/src/org/argeo/jcr/package-info.java deleted file mode 100644 index 1837749f1..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Generic JCR utilities. */ -package org.argeo.jcr; \ No newline at end of file diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/AbstractUrlProxy.java b/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/AbstractUrlProxy.java deleted file mode 100644 index 0177636f8..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/AbstractUrlProxy.java +++ /dev/null @@ -1,153 +0,0 @@ -package org.argeo.jcr.proxy; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; - -import javax.jcr.Binary; -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.nodetype.NodeType; - -import org.argeo.api.cms.CmsLog; -import org.argeo.jcr.JcrException; -import org.argeo.jcr.JcrUtils; - -/** Base class for URL based proxys. */ -public abstract class AbstractUrlProxy implements ResourceProxy { - private final static CmsLog log = CmsLog.getLog(AbstractUrlProxy.class); - - private Repository jcrRepository; - private Session jcrAdminSession; - private String proxyWorkspace = "proxy"; - - protected abstract Node retrieve(Session session, String path); - - void init() { - try { - jcrAdminSession = JcrUtils.loginOrCreateWorkspace(jcrRepository, proxyWorkspace); - beforeInitSessionSave(jcrAdminSession); - if (jcrAdminSession.hasPendingChanges()) - jcrAdminSession.save(); - } catch (RepositoryException e) { - JcrUtils.discardQuietly(jcrAdminSession); - throw new JcrException("Cannot initialize URL proxy", e); - } - } - - /** - * Called before the (admin) session is saved at the end of the initialization. - * Does nothing by default, to be overridden. - */ - protected void beforeInitSessionSave(Session session) throws RepositoryException { - } - - void destroy() { - JcrUtils.logoutQuietly(jcrAdminSession); - } - - /** - * Called before the (admin) session is logged out when resources are released. - * Does nothing by default, to be overridden. - */ - protected void beforeDestroySessionLogout() throws RepositoryException { - } - - public Node proxy(String path) { - // we open a JCR session with client credentials in order not to use the - // admin session in multiple thread or make it a bottleneck. - Node nodeAdmin = null; - Node nodeClient = null; - Session clientSession = null; - try { - clientSession = jcrRepository.login(proxyWorkspace); - if (!clientSession.itemExists(path) || shouldUpdate(clientSession, path)) { - nodeAdmin = retrieveAndSave(path); - if (nodeAdmin != null) - nodeClient = clientSession.getNode(path); - } else - nodeClient = clientSession.getNode(path); - return nodeClient; - } catch (RepositoryException e) { - throw new JcrException("Cannot proxy " + path, e); - } finally { - if (nodeClient == null) - JcrUtils.logoutQuietly(clientSession); - } - } - - protected synchronized Node retrieveAndSave(String path) { - try { - Node node = retrieve(jcrAdminSession, path); - if (node == null) - return null; - jcrAdminSession.save(); - return node; - } catch (RepositoryException e) { - JcrUtils.discardQuietly(jcrAdminSession); - throw new JcrException("Cannot retrieve and save " + path, e); - } finally { - notifyAll(); - } - } - - /** Session is not saved */ - protected synchronized Node proxyUrl(Session session, String remoteUrl, String path) throws RepositoryException { - Node node = null; - if (session.itemExists(path)) { - // throw new ArgeoJcrException("Node " + path + " already exists"); - } - try (InputStream in = new URL(remoteUrl).openStream()) { - // URL u = new URL(remoteUrl); - // in = u.openStream(); - node = importFile(session, path, in); - } catch (IOException e) { - if (log.isDebugEnabled()) { - log.debug("Cannot read " + remoteUrl + ", skipping... " + e.getMessage()); - // log.trace("Cannot read because of ", e); - } - JcrUtils.discardQuietly(session); - // } finally { - // IOUtils.closeQuietly(in); - } - return node; - } - - protected synchronized Node importFile(Session session, String path, InputStream in) throws RepositoryException { - Binary binary = null; - try { - Node content = null; - Node node = null; - if (!session.itemExists(path)) { - node = JcrUtils.mkdirs(session, path, NodeType.NT_FILE, NodeType.NT_FOLDER, false); - content = node.addNode(Node.JCR_CONTENT, NodeType.NT_UNSTRUCTURED); - } else { - node = session.getNode(path); - content = node.getNode(Node.JCR_CONTENT); - } - binary = session.getValueFactory().createBinary(in); - content.setProperty(Property.JCR_DATA, binary); - JcrUtils.updateLastModifiedAndParents(node, null, true); - return node; - } finally { - JcrUtils.closeQuietly(binary); - } - } - - /** Whether the file should be updated. */ - protected Boolean shouldUpdate(Session clientSession, String nodePath) { - return false; - } - - public void setJcrRepository(Repository jcrRepository) { - this.jcrRepository = jcrRepository; - } - - public void setProxyWorkspace(String localWorkspace) { - this.proxyWorkspace = localWorkspace; - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/ResourceProxy.java b/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/ResourceProxy.java deleted file mode 100644 index 84eea1f31..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/ResourceProxy.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.argeo.jcr.proxy; - -import javax.jcr.Node; - -/** A proxy which nows how to resolve and synchronize relative URLs */ -public interface ResourceProxy { - /** - * Proxy the file referenced by this relative path in the underlying - * repository. A new session is created by each call, so the underlying - * session of the returned node must be closed by the caller. - * - * @return the proxied Node, null if the resource was not found - * (e.g. HTTP 404) - */ - public Node proxy(String relativePath); -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/ResourceProxyServlet.java b/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/ResourceProxyServlet.java deleted file mode 100644 index a8e00df60..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/ResourceProxyServlet.java +++ /dev/null @@ -1,115 +0,0 @@ -package org.argeo.jcr.proxy; - -import java.io.IOException; -import java.io.InputStream; - -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.RepositoryException; -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.commons.io.FilenameUtils; -import org.apache.commons.io.IOUtils; -import org.argeo.jcr.JcrException; -import org.argeo.api.cms.CmsLog; -import org.argeo.jcr.Bin; -import org.argeo.jcr.JcrUtils; - -/** Wraps a proxy via HTTP */ -public class ResourceProxyServlet extends HttpServlet { - private static final long serialVersionUID = -8886549549223155801L; - - private final static CmsLog log = CmsLog - .getLog(ResourceProxyServlet.class); - - private ResourceProxy proxy; - - private String contentTypeCharset = "UTF-8"; - - @Override - protected void doGet(HttpServletRequest request, - HttpServletResponse response) throws ServletException, IOException { - String path = request.getPathInfo(); - - if (log.isTraceEnabled()) { - log.trace("path=" + path); - log.trace("UserPrincipal = " + request.getUserPrincipal().getName()); - log.trace("SessionID = " + request.getSession(false).getId()); - log.trace("ContextPath = " + request.getContextPath()); - log.trace("ServletPath = " + request.getServletPath()); - log.trace("PathInfo = " + request.getPathInfo()); - log.trace("Method = " + request.getMethod()); - log.trace("User-Agent = " + request.getHeader("User-Agent")); - } - - Node node = null; - try { - node = proxy.proxy(path); - if (node == null) - response.sendError(404); - else - processResponse(node, response); - } finally { - if (node != null) - try { - JcrUtils.logoutQuietly(node.getSession()); - } catch (RepositoryException e) { - // silent - } - } - - } - - /** Retrieve the content of the node. */ - protected void processResponse(Node node, HttpServletResponse response) { -// Binary binary = null; -// InputStream in = null; - try(Bin binary = new Bin( node.getNode(Property.JCR_CONTENT) - .getProperty(Property.JCR_DATA));InputStream in = binary.getStream()) { - String fileName = node.getName(); - String ext = FilenameUtils.getExtension(fileName); - - // TODO use a more generic / standard approach - // see http://svn.apache.org/viewvc/tomcat/trunk/conf/web.xml - String contentType; - if ("xml".equals(ext)) - contentType = "text/xml;charset=" + contentTypeCharset; - else if ("jar".equals(ext)) - contentType = "application/java-archive"; - else if ("zip".equals(ext)) - contentType = "application/zip"; - else if ("gz".equals(ext)) - contentType = "application/x-gzip"; - else if ("bz2".equals(ext)) - contentType = "application/x-bzip2"; - else if ("tar".equals(ext)) - contentType = "application/x-tar"; - else if ("rpm".equals(ext)) - contentType = "application/x-redhat-package-manager"; - else - contentType = "application/octet-stream"; - contentType = contentType + ";name=\"" + fileName + "\""; - response.setHeader("Content-Disposition", "attachment; filename=\"" - + fileName + "\""); - response.setHeader("Expires", "0"); - response.setHeader("Cache-Control", "no-cache, must-revalidate"); - response.setHeader("Pragma", "no-cache"); - - response.setContentType(contentType); - - IOUtils.copy(in, response.getOutputStream()); - } catch (RepositoryException e) { - throw new JcrException("Cannot download " + node, e); - } catch (IOException e) { - throw new RuntimeException("Cannot download " + node, e); - } - } - - public void setProxy(ResourceProxy resourceProxy) { - this.proxy = resourceProxy; - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/package-info.java b/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/package-info.java deleted file mode 100644 index a578c456e..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/proxy/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Components to build proxys based on JCR. */ -package org.argeo.jcr.proxy; \ No newline at end of file diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/unit/AbstractJcrTestCase.java b/org.argeo.cms.jcr/src/org/argeo/jcr/unit/AbstractJcrTestCase.java deleted file mode 100644 index 84e8cd31a..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/unit/AbstractJcrTestCase.java +++ /dev/null @@ -1,115 +0,0 @@ -package org.argeo.jcr.unit; - -import java.io.File; -import java.security.AccessController; -import java.security.PrivilegedAction; - -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.SimpleCredentials; -import javax.security.auth.Subject; -import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; - -import org.apache.commons.io.FileUtils; -import org.argeo.api.cms.CmsLog; -import org.argeo.jcr.JcrException; - -import junit.framework.TestCase; - -/** Base for unit tests with a JCR repository. */ -public abstract class AbstractJcrTestCase extends TestCase { - private final static CmsLog log = CmsLog.getLog(AbstractJcrTestCase.class); - - private Repository repository; - private Session session = null; - - public final static String LOGIN_CONTEXT_TEST_SYSTEM = "TEST_JACKRABBIT_ADMIN"; - - // protected abstract File getRepositoryFile() throws Exception; - - protected abstract Repository createRepository() throws Exception; - - protected abstract void clearRepository(Repository repository) throws Exception; - - @Override - protected void setUp() throws Exception { - File homeDir = getHomeDir(); - FileUtils.deleteDirectory(homeDir); - repository = createRepository(); - } - - @Override - protected void tearDown() throws Exception { - if (session != null) { - session.logout(); - if (log.isTraceEnabled()) - log.trace("Logout session"); - } - clearRepository(repository); - } - - protected Session session() { - if (session != null && session.isLive()) - return session; - Session session; - if (getLoginContext() != null) { - LoginContext lc; - try { - lc = new LoginContext(getLoginContext()); - lc.login(); - } catch (LoginException e) { - throw new IllegalStateException("JAAS login failed", e); - } - session = Subject.doAs(lc.getSubject(), new PrivilegedAction() { - - @Override - public Session run() { - return login(); - } - - }); - } else - session = login(); - this.session = session; - return this.session; - } - - protected String getLoginContext() { - return null; - } - - protected Session login() { - try { - if (log.isTraceEnabled()) - log.trace("Login session"); - Subject subject = Subject.getSubject(AccessController.getContext()); - if (subject != null) - return getRepository().login(); - else - return getRepository().login(new SimpleCredentials("demo", "demo".toCharArray())); - } catch (RepositoryException e) { - throw new JcrException("Cannot login to repository", e); - } - } - - protected Repository getRepository() { - return repository; - } - - /** - * enables children class to set an existing repository in case it is not - * deleted on startup, to test migration by instance - */ - public void setRepository(Repository repository) { - this.repository = repository; - } - - protected File getHomeDir() { - File homeDir = new File(System.getProperty("java.io.tmpdir"), - AbstractJcrTestCase.class.getSimpleName() + "-" + System.getProperty("user.name")); - return homeDir; - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/unit/package-info.java b/org.argeo.cms.jcr/src/org/argeo/jcr/unit/package-info.java deleted file mode 100644 index c6e741524..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/unit/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Helpers for unit tests with JCR repositories. */ -package org.argeo.jcr.unit; \ No newline at end of file diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/xml/JcrXmlUtils.java b/org.argeo.cms.jcr/src/org/argeo/jcr/xml/JcrXmlUtils.java deleted file mode 100644 index 2adb6a97e..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/xml/JcrXmlUtils.java +++ /dev/null @@ -1,186 +0,0 @@ -package org.argeo.jcr.xml; - -import java.io.IOException; -import java.io.Writer; -import java.util.Map; -import java.util.TreeMap; - -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.Property; -import javax.jcr.PropertyIterator; -import javax.jcr.RepositoryException; -import javax.jcr.Value; -import javax.jcr.nodetype.NodeType; - -import org.argeo.jcr.Jcr; - -/** Utilities around JCR and XML. */ -public class JcrXmlUtils { - /** - * Convenience method calling {@link #toXmlElements(Writer, Node, boolean)} with - * false. - */ - public static void toXmlElements(Writer writer, Node node) throws RepositoryException, IOException { - toXmlElements(writer, node, null, false, false, false); - } - - /** - * Write JCR properties as XML elements in a tree structure whose elements are - * named by node primary type. - * - * @param writer the writer to use - * @param node the subtree - * @param depth maximal depth, or if null the whole - * subtree. It must be positive, with depth 0 - * describing just the node without its children. - * @param withMetadata whether to write the primary type and mixins as - * elements - * @param withPrefix whether to keep the namespace prefixes - * @param propertiesAsElements whether single properties should be written as - * elements rather than attributes. If - * false, multiple properties will be - * skipped. - */ - public static void toXmlElements(Writer writer, Node node, Integer depth, boolean withMetadata, boolean withPrefix, - boolean propertiesAsElements) throws RepositoryException, IOException { - if (depth != null && depth < 0) - throw new IllegalArgumentException("Depth " + depth + " is negative."); - - if (node.getName().equals(Jcr.JCR_XMLTEXT)) { - writer.write(node.getProperty(Jcr.JCR_XMLCHARACTERS).getString()); - return; - } - - if (!propertiesAsElements) { - Map attrs = new TreeMap<>(); - PropertyIterator pit = node.getProperties(); - properties: while (pit.hasNext()) { - Property p = pit.nextProperty(); - if (!p.isMultiple()) { - String pName = p.getName(); - if (!withMetadata && (pName.equals(Jcr.JCR_PRIMARY_TYPE) || pName.equals(Jcr.JCR_UUID) - || pName.equals(Jcr.JCR_CREATED) || pName.equals(Jcr.JCR_CREATED_BY) - || pName.equals(Jcr.JCR_LAST_MODIFIED) || pName.equals(Jcr.JCR_LAST_MODIFIED_BY))) - continue properties; - attrs.put(withPrefix(p.getName(), withPrefix), p.getString()); - } - } - if (withMetadata && node.hasProperty(Property.JCR_UUID)) - attrs.put("id", "urn:uuid:" + node.getProperty(Property.JCR_UUID).getString()); - attrs.put(withPrefix ? Jcr.JCR_NAME : "name", node.getName()); - writeStart(writer, withPrefix(node.getPrimaryNodeType().getName(), withPrefix), attrs, node.hasNodes()); - } else { - if (withMetadata && node.hasProperty(Property.JCR_UUID)) { - writeStart(writer, withPrefix(node.getPrimaryNodeType().getName(), withPrefix), "id", - "urn:uuid:" + node.getProperty(Property.JCR_UUID).getString()); - } else { - writeStart(writer, withPrefix(node.getPrimaryNodeType().getName(), withPrefix)); - } - // name - writeStart(writer, withPrefix ? Jcr.JCR_NAME : "name"); - writer.append(node.getName()); - writeEnd(writer, withPrefix ? Jcr.JCR_NAME : "name"); - } - - // mixins - if (withMetadata) { - for (NodeType mixin : node.getMixinNodeTypes()) { - writeStart(writer, withPrefix ? Jcr.JCR_MIXIN_TYPES : "mixinTypes"); - writer.append(mixin.getName()); - writeEnd(writer, withPrefix ? Jcr.JCR_MIXIN_TYPES : "mixinTypes"); - } - } - - // properties as elements - if (propertiesAsElements) { - PropertyIterator pit = node.getProperties(); - properties: while (pit.hasNext()) { - Property p = pit.nextProperty(); - if (p.isMultiple()) { - for (Value value : p.getValues()) { - writeStart(writer, withPrefix(p.getName(), withPrefix)); - writer.write(value.getString()); - writeEnd(writer, withPrefix(p.getName(), withPrefix)); - } - } else { - Value value = p.getValue(); - String pName = p.getName(); - if (!withMetadata && (pName.equals(Jcr.JCR_PRIMARY_TYPE) || pName.equals(Jcr.JCR_UUID) - || pName.equals(Jcr.JCR_CREATED) || pName.equals(Jcr.JCR_CREATED_BY) - || pName.equals(Jcr.JCR_LAST_MODIFIED) || pName.equals(Jcr.JCR_LAST_MODIFIED_BY))) - continue properties; - writeStart(writer, withPrefix(p.getName(), withPrefix)); - writer.write(value.getString()); - writeEnd(writer, withPrefix(p.getName(), withPrefix)); - } - } - } - - // children - if (node.hasNodes()) { - if (depth == null || depth > 0) { - NodeIterator nit = node.getNodes(); - while (nit.hasNext()) { - toXmlElements(writer, nit.nextNode(), depth == null ? null : depth - 1, withMetadata, withPrefix, - propertiesAsElements); - } - } - writeEnd(writer, withPrefix(node.getPrimaryNodeType().getName(), withPrefix)); - } - } - - private static String withPrefix(String str, boolean withPrefix) { - if (withPrefix) - return str; - int index = str.indexOf(':'); - if (index < 0) - return str; - return str.substring(index + 1); - } - - private static void writeStart(Writer writer, String tagName) throws IOException { - writer.append('<'); - writer.append(tagName); - writer.append('>'); - } - - private static void writeStart(Writer writer, String tagName, String attr, String value) throws IOException { - writer.append('<'); - writer.append(tagName); - writer.append(' '); - writer.append(attr); - writer.append("=\""); - writer.append(value); - writer.append("\">"); - } - - private static void writeStart(Writer writer, String tagName, Map attrs, boolean hasChildren) - throws IOException { - writer.append('<'); - writer.append(tagName); - for (String attr : attrs.keySet()) { - writer.append(' '); - writer.append(attr); - writer.append("=\""); - writer.append(attrs.get(attr)); - writer.append('\"'); - } - if (hasChildren) - writer.append('>'); - else - writer.append("/>"); - } - - private static void writeEnd(Writer writer, String tagName) throws IOException { - writer.append("'); - } - - /** Singleton. */ - private JcrXmlUtils() { - - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/jcr/xml/removePrefixes.xsl b/org.argeo.cms.jcr/src/org/argeo/jcr/xml/removePrefixes.xsl deleted file mode 100644 index 813d06570..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/jcr/xml/removePrefixes.xsl +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/org.argeo.cms.jcr/src/org/argeo/maintenance/AbstractMaintenanceService.java b/org.argeo.cms.jcr/src/org/argeo/maintenance/AbstractMaintenanceService.java deleted file mode 100644 index 3c8f296c4..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/maintenance/AbstractMaintenanceService.java +++ /dev/null @@ -1,220 +0,0 @@ -package org.argeo.maintenance; - -import java.io.IOException; -import java.util.EnumSet; -import java.util.HashSet; -import java.util.Set; - -import javax.jcr.NoSuchWorkspaceException; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; - -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.jcr.Jcr; -import org.argeo.jcr.JcrUtils; -import org.argeo.osgi.transaction.WorkTransaction; -import org.argeo.util.naming.Distinguished; -import org.osgi.service.useradmin.Group; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.UserAdmin; - -/** Make sure roles and access rights are properly configured. */ -public abstract class AbstractMaintenanceService { - private final static CmsLog log = CmsLog.getLog(AbstractMaintenanceService.class); - - private Repository repository; -// private UserAdminService userAdminService; - private UserAdmin userAdmin; - private WorkTransaction userTransaction; - - public void init() { - makeSureRolesExists(getRequiredRoles()); - configureStandardRoles(); - - Set workspaceNames = getWorkspaceNames(); - if (workspaceNames == null || workspaceNames.isEmpty()) { - configureJcr(repository, null); - } else { - for (String workspaceName : workspaceNames) - configureJcr(repository, workspaceName); - } - } - - /** Configures a workspace. */ - protected void configureJcr(Repository repository, String workspaceName) { - Session adminSession; - try { - adminSession = CmsJcrUtils.openDataAdminSession(repository, workspaceName); - } catch (RuntimeException e1) { - if (e1.getCause() != null && e1.getCause() instanceof NoSuchWorkspaceException) { - Session defaultAdminSession = CmsJcrUtils.openDataAdminSession(repository, null); - try { - defaultAdminSession.getWorkspace().createWorkspace(workspaceName); - log.info("Created JCR workspace " + workspaceName); - } catch (RepositoryException e) { - throw new IllegalStateException("Cannot create workspace " + workspaceName, e); - } finally { - Jcr.logout(defaultAdminSession); - } - adminSession = CmsJcrUtils.openDataAdminSession(repository, workspaceName); - } else - throw e1; - } - try { - if (prepareJcrTree(adminSession)) { - configurePrivileges(adminSession); - } - } catch (RepositoryException | IOException e) { - throw new IllegalStateException("Cannot initialise JCR data layer.", e); - } finally { - JcrUtils.logoutQuietly(adminSession); - } - } - - /** To be overridden. */ - protected Set getWorkspaceNames() { - return null; - } - - /** - * To be overridden in order to programmatically set relationships between - * roles. Does nothing by default. - */ - protected void configureStandardRoles() { - } - - /** - * Creates the base JCR tree structure expected for this app if necessary. - * - * Expects a clean session ({@link Session#hasPendingChanges()} should return - * false) and saves it once the changes have been done. Thus the session can be - * rolled back if an exception occurs. - * - * @return true if something as been updated - */ - public boolean prepareJcrTree(Session adminSession) throws RepositoryException, IOException { - return false; - } - - /** - * Adds app specific default privileges. - * - * Expects a clean session ({@link Session#hasPendingChanges()} should return - * false} and saves it once the changes have been done. Thus the session can be - * rolled back if an exception occurs. - * - * Warning: no check is done and corresponding privileges are always added, so - * only call this when necessary - */ - public void configurePrivileges(Session session) throws RepositoryException { - } - - /** The system roles that must be available in the system. */ - protected Set getRequiredRoles() { - return new HashSet<>(); - } - - public void destroy() { - - } - - /* - * UTILITIES - */ - - /** Create these roles as group if they don't exist. */ - protected void makeSureRolesExists(EnumSet enumSet) { - makeSureRolesExists(Distinguished.enumToDns(enumSet)); - } - - /** Create these roles as group if they don't exist. */ - protected void makeSureRolesExists(Set requiredRoles) { - if (requiredRoles == null) - return; - if (getUserAdmin() == null) { - log.warn("No user admin service available, cannot make sure that role exists"); - return; - } - for (String role : requiredRoles) { - Role systemRole = getUserAdmin().getRole(role); - if (systemRole == null) { - try { - getUserTransaction().begin(); - getUserAdmin().createRole(role, Role.GROUP); - getUserTransaction().commit(); - log.info("Created role " + role); - } catch (Exception e) { - try { - getUserTransaction().rollback(); - } catch (Exception e1) { - // silent - } - throw new IllegalStateException("Cannot create role " + role, e); - } - } - } - } - - /** Add a user or group to a group. */ - protected void addToGroup(String groupToAddDn, String groupDn) { - if (groupToAddDn.contentEquals(groupDn)) { - if (log.isTraceEnabled()) - log.trace("Ignore adding group " + groupDn + " to itself"); - return; - } - - if (getUserAdmin() == null) { - log.warn("No user admin service available, cannot add group " + groupToAddDn + " to " + groupDn); - return; - } - Group groupToAdd = (Group) getUserAdmin().getRole(groupToAddDn); - if (groupToAdd == null) - throw new IllegalArgumentException("Group " + groupToAddDn + " not found"); - Group group = (Group) getUserAdmin().getRole(groupDn); - if (group == null) - throw new IllegalArgumentException("Group " + groupDn + " not found"); - try { - getUserTransaction().begin(); - if (group.addMember(groupToAdd)) - log.info("Added " + groupToAddDn + " to " + group); - getUserTransaction().commit(); - } catch (Exception e) { - try { - getUserTransaction().rollback(); - } catch (Exception e1) { - // silent - } - throw new IllegalStateException("Cannot add " + groupToAddDn + " to " + groupDn); - } - } - - /* - * DEPENDENCY INJECTION - */ - public void setRepository(Repository repository) { - this.repository = repository; - } - -// public void setUserAdminService(UserAdminService userAdminService) { -// this.userAdminService = userAdminService; -// } - - protected WorkTransaction getUserTransaction() { - return userTransaction; - } - - protected UserAdmin getUserAdmin() { - return userAdmin; - } - - public void setUserAdmin(UserAdmin userAdmin) { - this.userAdmin = userAdmin; - } - - public void setUserTransaction(WorkTransaction userTransaction) { - this.userTransaction = userTransaction; - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/maintenance/SimpleRoleRegistration.java b/org.argeo.cms.jcr/src/org/argeo/maintenance/SimpleRoleRegistration.java deleted file mode 100644 index ebb8c534d..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/maintenance/SimpleRoleRegistration.java +++ /dev/null @@ -1,86 +0,0 @@ -package org.argeo.maintenance; - -import java.util.ArrayList; -import java.util.List; -import java.util.Map; - -import javax.naming.InvalidNameException; -import javax.naming.ldap.LdapName; - -import org.argeo.api.cms.CmsLog; -import org.argeo.osgi.transaction.WorkTransaction; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.UserAdmin; - -/** - * Register one or many roles via a user admin service. Does nothing if the role - * is already registered. - */ -public class SimpleRoleRegistration implements Runnable { - private final static CmsLog log = CmsLog.getLog(SimpleRoleRegistration.class); - - private String role; - private List roles = new ArrayList(); - private UserAdmin userAdmin; - private WorkTransaction userTransaction; - - @Override - public void run() { - try { - userTransaction.begin(); - if (role != null && !roleExists(role)) - newRole(toDn(role)); - - for (String r : roles) - if (!roleExists(r)) - newRole(toDn(r)); - userTransaction.commit(); - } catch (Exception e) { - try { - userTransaction.rollback(); - } catch (Exception e1) { - log.error("Cannot rollback", e1); - } - throw new IllegalArgumentException("Cannot add roles", e); - } - } - - private boolean roleExists(String role) { - return userAdmin.getRole(toDn(role).toString()) != null; - } - - protected void newRole(LdapName r) { - userAdmin.createRole(r.toString(), Role.GROUP); - log.info("Added role " + r + " required by application."); - } - - public void register(UserAdmin userAdminService, Map properties) { - this.userAdmin = userAdminService; - run(); - } - - protected LdapName toDn(String name) { - try { - return new LdapName("cn=" + name + ",ou=roles,ou=node"); - } catch (InvalidNameException e) { - throw new IllegalArgumentException("Badly formatted role name " + name, e); - } - } - - public void setRole(String role) { - this.role = role; - } - - public void setRoles(List roles) { - this.roles = roles; - } - - public void setUserAdmin(UserAdmin userAdminService) { - this.userAdmin = userAdminService; - } - - public void setUserTransaction(WorkTransaction userTransaction) { - this.userTransaction = userTransaction; - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/BackupContentHandler.java b/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/BackupContentHandler.java deleted file mode 100644 index ef83c1ff9..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/BackupContentHandler.java +++ /dev/null @@ -1,257 +0,0 @@ -package org.argeo.maintenance.backup; - -import java.io.IOException; -import java.io.InputStream; -import java.io.Writer; -import java.util.Arrays; -import java.util.Base64; -import java.util.Set; -import java.util.TreeSet; - -import javax.jcr.Binary; -import javax.jcr.Node; -import javax.jcr.RepositoryException; -import javax.jcr.Session; - -import org.apache.commons.io.IOUtils; -import org.argeo.jcr.Jcr; -import org.argeo.jcr.JcrException; -import org.xml.sax.Attributes; -import org.xml.sax.SAXException; -import org.xml.sax.helpers.DefaultHandler; - -/** XML handler serialising a JCR system view. */ -public class BackupContentHandler extends DefaultHandler { - final static int MAX_DEPTH = 1024; - final static String SV_NAMESPACE_URI = "http://www.jcp.org/jcr/sv/1.0"; - final static String SV_PREFIX = "sv"; - // elements - final static String NODE = "node"; - final static String PROPERTY = "property"; - final static String VALUE = "value"; - // attributes - final static String NAME = "name"; - final static String MULTIPLE = "multiple"; - final static String TYPE = "type"; - - // values - final static String BINARY = "Binary"; - final static String JCR_CONTENT = "jcr:content"; - - private Writer out; - private Session session; - private Set contentPaths = new TreeSet<>(); - - boolean prettyPrint = true; - - private final String parentPath; - -// private boolean inSystem = false; - - public BackupContentHandler(Writer out, Node node) { - super(); - this.out = out; - this.session = Jcr.getSession(node); - parentPath = Jcr.getParentPath(node); - } - - private int currentDepth = -1; - private String[] currentPath = new String[MAX_DEPTH]; - - private boolean currentPropertyIsMultiple = false; - private String currentEncoded = null; - private Base64.Encoder base64encore = Base64.getEncoder(); - - @Override - public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { - boolean isNode; - boolean isProperty; - switch (localName) { - case NODE: - isNode = true; - isProperty = false; - break; - case PROPERTY: - isNode = false; - isProperty = true; - break; - default: - isNode = false; - isProperty = false; - } - - if (isNode) { - String nodeName = attributes.getValue(SV_NAMESPACE_URI, NAME); - currentDepth = currentDepth + 1; -// if (currentDepth >= 0) - currentPath[currentDepth] = nodeName; -// System.out.println(getCurrentPath() + " , depth=" + currentDepth); -// if ("jcr:system".equals(nodeName)) { -// inSystem = true; -// } - } -// if (inSystem) -// return; - - if (SV_NAMESPACE_URI.equals(uri)) - try { - if (prettyPrint) { - if (isNode) { - out.write(spaces()); - out.write("\n"); - out.write(spaces()); - } else if (isProperty) - out.write(spaces()); - else if (currentPropertyIsMultiple) - out.write(spaces()); - } - - out.write("<"); - out.write(SV_PREFIX + ":" + localName); - if (isProperty) - currentPropertyIsMultiple = false; // always reset - for (int i = 0; i < attributes.getLength(); i++) { - String ns = attributes.getURI(i); - if (SV_NAMESPACE_URI.equals(ns)) { - String attrName = attributes.getLocalName(i); - String attrValue = attributes.getValue(i); - out.write(" "); - out.write(SV_PREFIX + ":" + attrName); - out.write("="); - out.write("\""); - out.write(attrValue); - out.write("\""); - if (isProperty) { - if (MULTIPLE.equals(attrName)) - currentPropertyIsMultiple = Boolean.parseBoolean(attrValue); - else if (TYPE.equals(attrName)) { - if (BINARY.equals(attrValue)) { - if (JCR_CONTENT.equals(getCurrentName())) { - contentPaths.add(getCurrentJcrPath()); - } else { - Binary binary = session.getNode(getCurrentJcrPath()).getProperty(attrName) - .getBinary(); - try (InputStream in = binary.getStream()) { - currentEncoded = base64encore.encodeToString(IOUtils.toByteArray(in)); - } finally { - - } - } - } - } - } - } - } - if (isNode && currentDepth == 0) { - // out.write(" xmlns=\"" + SV_NAMESPACE_URI + "\""); - out.write(" xmlns:" + SV_PREFIX + "=\"" + SV_NAMESPACE_URI + "\""); - } - out.write(">"); - - if (prettyPrint) - if (isNode) - out.write("\n"); - else if (isProperty && currentPropertyIsMultiple) - out.write("\n"); - } catch (IOException e) { - throw new RuntimeException(e); - } catch (RepositoryException e) { - throw new JcrException(e); - } - } - - @Override - public void endElement(String uri, String localName, String qName) throws SAXException { - boolean isNode = localName.equals(NODE); - boolean isValue = localName.equals(VALUE); - if (prettyPrint) - if (!isValue) - try { - if (isNode || currentPropertyIsMultiple) - out.write(spaces()); - } catch (IOException e1) { - throw new RuntimeException(e1); - } - if (isNode) { -// System.out.println("endElement " + getCurrentPath() + " , depth=" + currentDepth); -// if (currentDepth > 0) - currentPath[currentDepth] = null; - currentDepth = currentDepth - 1; -// if (inSystem) { -// // System.out.println("Skip " + getCurrentPath()+" , -// // currentDepth="+currentDepth); -// if (currentDepth == 0) { -// inSystem = false; -// return; -// } -// } - } -// if (inSystem) -// return; - if (SV_NAMESPACE_URI.equals(uri)) - try { - if (isValue && currentEncoded != null) { - out.write(currentEncoded); - } - currentEncoded = null; - out.write(""); - if (prettyPrint) - if (!isValue) - out.write("\n"); - else { - if (currentPropertyIsMultiple) - out.write("\n"); - } - if (currentDepth == 0) - out.flush(); - } catch (IOException e) { - throw new RuntimeException(e); - } - - } - - private char[] spaces() { - char[] arr = new char[currentDepth]; - Arrays.fill(arr, ' '); - return arr; - } - - @Override - public void characters(char[] ch, int start, int length) throws SAXException { -// if (inSystem) -// return; - try { - out.write(ch, start, length); - } catch (IOException e) { - throw new RuntimeException(e); - } - } - - protected String getCurrentName() { - assert currentDepth >= 0; -// if (currentDepth == 0) -// return "jcr:root"; - return currentPath[currentDepth]; - } - - protected String getCurrentJcrPath() { -// if (currentDepth == 0) -// return "/"; - StringBuilder sb = new StringBuilder(parentPath.equals("/") ? "" : parentPath); - for (int i = 0; i <= currentDepth; i++) { -// if (i != 0) - sb.append('/'); - sb.append(currentPath[i]); - } - return sb.toString(); - } - - public Set getContentPaths() { - return contentPaths; - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/LogicalBackup.java b/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/LogicalBackup.java deleted file mode 100644 index 00d4be8b0..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/LogicalBackup.java +++ /dev/null @@ -1,448 +0,0 @@ -package org.argeo.maintenance.backup; - -import java.io.BufferedWriter; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.OutputStreamWriter; -import java.io.Writer; -import java.net.URI; -import java.net.URL; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Dictionary; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.jar.JarOutputStream; -import java.util.jar.Manifest; -import java.util.zip.ZipEntry; -import java.util.zip.ZipException; -import java.util.zip.ZipOutputStream; - -import javax.jcr.Binary; -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.Property; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.RepositoryFactory; -import javax.jcr.Session; - -import org.apache.commons.io.IOUtils; -import org.apache.jackrabbit.api.JackrabbitSession; -import org.apache.jackrabbit.api.JackrabbitValue; -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.jackrabbit.client.ClientDavexRepositoryFactory; -import org.argeo.jcr.Jcr; -import org.argeo.jcr.JcrException; -import org.argeo.jcr.JcrUtils; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; - -/** - * Performs a backup of the data based only on programmatic interfaces. Useful - * for migration or live backup. Physical backups of the underlying file - * systems, databases, LDAP servers, etc. should be performed for disaster - * recovery. - */ -public class LogicalBackup implements Runnable { - private final static CmsLog log = CmsLog.getLog(LogicalBackup.class); - - public final static String WORKSPACES_BASE = "workspaces/"; - public final static String FILES_BASE = "files/"; - public final static String OSGI_BASE = "share/osgi/"; - - public final static String JCR_SYSTEM = "jcr:system"; - public final static String JCR_VERSION_STORAGE_PATH = "/jcr:system/jcr:versionStorage"; - - private final Repository repository; - private String defaultWorkspace; - private final BundleContext bundleContext; - - private final ZipOutputStream zout; - private final Path basePath; - - private ExecutorService executorService; - - private boolean performSoftwareBackup = false; - - private Map checksums = new TreeMap<>(); - - private int threadCount = 5; - - private boolean backupFailed = false; - - public LogicalBackup(BundleContext bundleContext, Repository repository, Path basePath) { - this.repository = repository; - this.zout = null; - this.basePath = basePath; - this.bundleContext = bundleContext; - } - - @Override - public void run() { - try { - log.info("Start logical backup to " + basePath); - perform(); - } catch (Exception e) { - log.error("Unexpected exception when performing logical backup", e); - throw new IllegalStateException("Logical backup failed", e); - } - - } - - public void perform() throws RepositoryException, IOException { - if (executorService != null && !executorService.isTerminated()) - throw new IllegalStateException("Another backup is running"); - executorService = Executors.newFixedThreadPool(threadCount); - long begin = System.currentTimeMillis(); - // software backup - if (bundleContext != null && performSoftwareBackup) - executorService.submit(() -> performSoftwareBackup(bundleContext)); - - // data backup - Session defaultSession = login(null); - defaultWorkspace = defaultSession.getWorkspace().getName(); - try { - String[] workspaceNames = defaultSession.getWorkspace().getAccessibleWorkspaceNames(); - workspaces: for (String workspaceName : workspaceNames) { - if ("security".equals(workspaceName)) - continue workspaces; - performDataBackup(workspaceName); - } - } finally { - JcrUtils.logoutQuietly(defaultSession); - executorService.shutdown(); - try { - executorService.awaitTermination(24, TimeUnit.HOURS); - } catch (InterruptedException e) { - // silent - throw new IllegalStateException("Backup was interrupted before completion", e); - } - } - // versions - executorService = Executors.newFixedThreadPool(threadCount); - try { - performVersionsBackup(); - } finally { - executorService.shutdown(); - try { - executorService.awaitTermination(24, TimeUnit.HOURS); - } catch (InterruptedException e) { - // silent - throw new IllegalStateException("Backup was interrupted before completion", e); - } - } - long duration = System.currentTimeMillis() - begin; - if (isBackupFailed()) - log.info("System logical backup failed after " + (duration / 60000) + "min " + (duration / 1000) + "s"); - else - log.info("System logical backup completed in " + (duration / 60000) + "min " + (duration / 1000) + "s"); - } - - protected void performDataBackup(String workspaceName) throws RepositoryException, IOException { - Session session = login(workspaceName); - try { - nodes: for (NodeIterator nit = session.getRootNode().getNodes(); nit.hasNext();) { - if (isBackupFailed()) - return; - Node nodeToExport = nit.nextNode(); - if (JCR_SYSTEM.equals(nodeToExport.getName())) - continue nodes; - String nodePath = nodeToExport.getPath(); - Future> contentPathsFuture = executorService - .submit(() -> performNodeBackup(workspaceName, nodePath)); - executorService.submit(() -> performFilesBackup(workspaceName, contentPathsFuture)); - } - } finally { - Jcr.logout(session); - } - } - - protected void performVersionsBackup() throws RepositoryException, IOException { - Session session = login(defaultWorkspace); - Node versionStorageNode = session.getNode(JCR_VERSION_STORAGE_PATH); - try { - for (NodeIterator nit = versionStorageNode.getNodes(); nit.hasNext();) { - Node nodeToExport = nit.nextNode(); - String nodePath = nodeToExport.getPath(); - if (isBackupFailed()) - return; - Future> contentPathsFuture = executorService - .submit(() -> performNodeBackup(defaultWorkspace, nodePath)); - executorService.submit(() -> performFilesBackup(defaultWorkspace, contentPathsFuture)); - } - } finally { - Jcr.logout(session); - } - - } - - protected Set performNodeBackup(String workspaceName, String nodePath) { - Session session = login(workspaceName); - try { - Node nodeToExport = session.getNode(nodePath); -// String nodeName = nodeToExport.getName(); -// if (nodeName.startsWith("jcr:") || nodeName.startsWith("rep:")) -// continue nodes; -// // TODO make it more robust / configurable -// if (nodeName.equals("user")) -// continue nodes; - String relativePath = WORKSPACES_BASE + workspaceName + nodePath + ".xml"; - OutputStream xmlOut = openOutputStream(relativePath); - BackupContentHandler contentHandler; - try (Writer writer = new BufferedWriter(new OutputStreamWriter(xmlOut, StandardCharsets.UTF_8))) { - contentHandler = new BackupContentHandler(writer, nodeToExport); - session.exportSystemView(nodeToExport.getPath(), contentHandler, true, false); - if (log.isDebugEnabled()) - log.debug(workspaceName + ":" + nodePath + " metadata exported to " + relativePath); - } - - // Files - Set contentPaths = contentHandler.getContentPaths(); - return contentPaths; - } catch (Exception e) { - markBackupFailed("Cannot backup node " + workspaceName + ":" + nodePath, e); - throw new ThreadDeath(); - } finally { - Jcr.logout(session); - } - } - - protected void performFilesBackup(String workspaceName, Future> contentPathsFuture) { - Set contentPaths; - try { - contentPaths = contentPathsFuture.get(24, TimeUnit.HOURS); - } catch (InterruptedException | ExecutionException | TimeoutException e1) { - markBackupFailed("Cannot retrieve content paths for workspace " + workspaceName, e1); - return; - } - if (contentPaths == null || contentPaths.size() == 0) - return; - Session session = login(workspaceName); - try { - String workspacesFilesBasePath = FILES_BASE + workspaceName; - for (String path : contentPaths) { - if (isBackupFailed()) - return; - Node contentNode = session.getNode(path); - Binary binary = null; - try { - binary = contentNode.getProperty(Property.JCR_DATA).getBinary(); - String fileRelativePath = workspacesFilesBasePath + contentNode.getParent().getPath(); - - // checksum - boolean skip = false; - String checksum = null; - if (session instanceof JackrabbitSession) { - JackrabbitValue value = (JackrabbitValue) contentNode.getProperty(Property.JCR_DATA).getValue(); -// ReferenceBinary referenceBinary = (ReferenceBinary) binary; - checksum = value.getContentIdentity(); - } - if (checksum != null) { - if (!checksums.containsKey(checksum)) { - checksums.put(checksum, fileRelativePath); - } else { - skip = true; - String sourcePath = checksums.get(checksum); - if (log.isTraceEnabled()) - log.trace(fileRelativePath + " : already " + sourcePath + " with checksum " + checksum); - createLink(sourcePath, fileRelativePath); - try (Writer writerSum = new OutputStreamWriter( - openOutputStream(fileRelativePath + ".sha256"), StandardCharsets.UTF_8)) { - writerSum.write(checksum); - } - } - } - - // copy file - if (!skip) - try (InputStream in = binary.getStream(); - OutputStream out = openOutputStream(fileRelativePath)) { - IOUtils.copy(in, out); - if (log.isTraceEnabled()) - log.trace("Workspace " + workspaceName + ": file content exported to " - + fileRelativePath); - } - } finally { - JcrUtils.closeQuietly(binary); - } - } - if (log.isDebugEnabled()) - log.debug(workspaceName + ":" + contentPaths.size() + " files exported to " + workspacesFilesBasePath); - } catch (Exception e) { - markBackupFailed("Cannot backup files from " + workspaceName + ":", e); - } finally { - Jcr.logout(session); - } - } - - protected OutputStream openOutputStream(String relativePath) throws IOException { - if (zout != null) { - ZipEntry entry = new ZipEntry(relativePath); - zout.putNextEntry(entry); - return zout; - } else if (basePath != null) { - Path targetPath = basePath.resolve(Paths.get(relativePath)); - Files.createDirectories(targetPath.getParent()); - return Files.newOutputStream(targetPath); - } else { - throw new UnsupportedOperationException(); - } - } - - protected void createLink(String source, String target) throws IOException { - if (zout != null) { - // TODO implement for zip - throw new UnsupportedOperationException(); - } else if (basePath != null) { - Path sourcePath = basePath.resolve(Paths.get(source)); - Path targetPath = basePath.resolve(Paths.get(target)); - Path relativeSource = targetPath.getParent().relativize(sourcePath); - Files.createDirectories(targetPath.getParent()); - Files.createSymbolicLink(targetPath, relativeSource); - } else { - throw new UnsupportedOperationException(); - } - } - - protected void closeOutputStream(String relativePath, OutputStream out) throws IOException { - if (zout != null) { - zout.closeEntry(); - } else if (basePath != null) { - out.close(); - } else { - throw new UnsupportedOperationException(); - } - } - - protected Session login(String workspaceName) { - if (bundleContext != null) {// local - return CmsJcrUtils.openDataAdminSession(repository, workspaceName); - } else {// remote - try { - return repository.login(workspaceName); - } catch (RepositoryException e) { - throw new JcrException(e); - } - } - } - - public final static void main(String[] args) throws Exception { - if (args.length == 0) { - printUsage("No argument"); - System.exit(1); - } - URI uri = new URI(args[0]); - Repository repository = createRemoteRepository(uri); - Path basePath = args.length > 1 ? Paths.get(args[1]) : Paths.get(System.getProperty("user.dir")); - if (!Files.exists(basePath)) - Files.createDirectories(basePath); - LogicalBackup backup = new LogicalBackup(null, repository, basePath); - backup.run(); - } - - private static void printUsage(String errorMessage) { - if (errorMessage != null) - System.err.println(errorMessage); - System.out.println("Usage: LogicalBackup []"); - - } - - protected static Repository createRemoteRepository(URI uri) throws RepositoryException { - RepositoryFactory repositoryFactory = new ClientDavexRepositoryFactory(); - Map params = new HashMap(); - params.put(ClientDavexRepositoryFactory.JACKRABBIT_DAVEX_URI, uri.toString()); - // TODO make it configurable - params.put(ClientDavexRepositoryFactory.JACKRABBIT_REMOTE_DEFAULT_WORKSPACE, CmsConstants.SYS_WORKSPACE); - return repositoryFactory.getRepository(params); - } - - public void performSoftwareBackup(BundleContext bundleContext) { - String bootBasePath = OSGI_BASE + "boot"; - Bundle[] bundles = bundleContext.getBundles(); - for (Bundle bundle : bundles) { - String relativePath = bootBasePath + "/" + bundle.getSymbolicName() + ".jar"; - Dictionary headers = bundle.getHeaders(); - Manifest manifest = new Manifest(); - Enumeration headerKeys = headers.keys(); - while (headerKeys.hasMoreElements()) { - String headerKey = headerKeys.nextElement(); - String headerValue = headers.get(headerKey); - manifest.getMainAttributes().putValue(headerKey, headerValue); - } - try (JarOutputStream jarOut = new JarOutputStream(openOutputStream(relativePath), manifest)) { - Enumeration resourcePaths = bundle.findEntries("/", "*", true); - resources: while (resourcePaths.hasMoreElements()) { - URL entryUrl = resourcePaths.nextElement(); - String entryPath = entryUrl.getPath(); - if (entryPath.equals("")) - continue resources; - if (entryPath.endsWith("/")) - continue resources; - String entryName = entryPath.substring(1);// remove first '/' - if (entryUrl.getPath().equals("/META-INF/")) - continue resources; - if (entryUrl.getPath().equals("/META-INF/MANIFEST.MF")) - continue resources; - // dev - if (entryUrl.getPath().startsWith("/target")) - continue resources; - if (entryUrl.getPath().startsWith("/src")) - continue resources; - if (entryUrl.getPath().startsWith("/ext")) - continue resources; - - if (entryName.startsWith("bin/")) {// dev - entryName = entryName.substring("bin/".length()); - } - - ZipEntry entry = new ZipEntry(entryName); - try (InputStream in = entryUrl.openStream()) { - try { - jarOut.putNextEntry(entry); - } catch (ZipException e) {// duplicate - continue resources; - } - IOUtils.copy(in, jarOut); - jarOut.closeEntry(); -// log.info(entryUrl); - } catch (FileNotFoundException e) { - log.warn(entryUrl + ": " + e.getMessage()); - } - } - } catch (IOException e1) { - throw new RuntimeException("Cannot export bundle " + bundle, e1); - } - } - if (log.isDebugEnabled()) - log.debug(bundles.length + " OSGi bundles exported to " + bootBasePath); - - } - - protected synchronized void markBackupFailed(Object message, Exception e) { - log.error(message, e); - backupFailed = true; - notifyAll(); - if (executorService != null) - executorService.shutdownNow(); - } - - protected boolean isBackupFailed() { - return backupFailed; - } -} diff --git a/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/LogicalRestore.java b/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/LogicalRestore.java deleted file mode 100644 index 122c96701..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/LogicalRestore.java +++ /dev/null @@ -1,85 +0,0 @@ -package org.argeo.maintenance.backup; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.DirectoryStream; -import java.nio.file.Files; -import java.nio.file.Path; - -import javax.jcr.ImportUUIDBehavior; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.jcr.Jcr; -import org.argeo.jcr.JcrException; -import org.argeo.jcr.JcrUtils; -import org.osgi.framework.BundleContext; - -/** Restores a backup in the format defined by {@link LogicalBackup}. */ -public class LogicalRestore implements Runnable { - private final static CmsLog log = CmsLog.getLog(LogicalRestore.class); - - private final Repository repository; - private final BundleContext bundleContext; - private final Path basePath; - - public LogicalRestore(BundleContext bundleContext, Repository repository, Path basePath) { - this.repository = repository; - this.basePath = basePath; - this.bundleContext = bundleContext; - } - - @Override - public void run() { - Path workspaces = basePath.resolve(LogicalBackup.WORKSPACES_BASE); - try { - // import jcr:system first -// Session defaultSession = NodeUtils.openDataAdminSession(repository, null); -// try (DirectoryStream xmls = Files.newDirectoryStream( -// workspaces.resolve(NodeConstants.SYS_WORKSPACE + LogicalBackup.JCR_VERSION_STORAGE_PATH), -// "*.xml")) { -// for (Path xml : xmls) { -// try (InputStream in = Files.newInputStream(xml)) { -// defaultSession.getWorkspace().importXML(LogicalBackup.JCR_VERSION_STORAGE_PATH, in, -// ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING); -// if (log.isDebugEnabled()) -// log.debug("Restored " + xml + " to " + defaultSession.getWorkspace().getName() + ":"); -// } -// } -// } finally { -// Jcr.logout(defaultSession); -// } - - // non-system content - try (DirectoryStream workspaceDirs = Files.newDirectoryStream(workspaces)) { - for (Path workspacePath : workspaceDirs) { - String workspaceName = workspacePath.getFileName().toString(); - Session session = JcrUtils.loginOrCreateWorkspace(repository, workspaceName); - try (DirectoryStream xmls = Files.newDirectoryStream(workspacePath, "*.xml")) { - xmls: for (Path xml : xmls) { - if (xml.getFileName().toString().startsWith("rep:")) - continue xmls; - try (InputStream in = Files.newInputStream(xml)) { - session.getWorkspace().importXML("/", in, - ImportUUIDBehavior.IMPORT_UUID_COLLISION_REPLACE_EXISTING); - if (log.isDebugEnabled()) - log.debug("Restored " + xml + " to workspace " + workspaceName); - } - } - } finally { - Jcr.logout(session); - } - } - } - } catch (IOException e) { - throw new RuntimeException("Cannot restore backup from " + basePath, e); - } catch (RepositoryException e) { - throw new JcrException("Cannot restore backup from " + basePath, e); - } - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/package-info.java b/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/package-info.java deleted file mode 100644 index a61e19bd4..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/maintenance/backup/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Argeo Node backup utilities. */ -package org.argeo.maintenance.backup; \ No newline at end of file diff --git a/org.argeo.cms.jcr/src/org/argeo/maintenance/internal/Activator.java b/org.argeo.cms.jcr/src/org/argeo/maintenance/internal/Activator.java deleted file mode 100644 index ef40ab3a3..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/maintenance/internal/Activator.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.argeo.maintenance.internal; - -import java.nio.file.Path; -import java.nio.file.Paths; - -import javax.jcr.Repository; - -import org.argeo.maintenance.backup.LogicalBackup; -import org.osgi.framework.BundleActivator; -import org.osgi.framework.BundleContext; - -public class Activator implements BundleActivator { - - @Override - public void start(BundleContext context) throws Exception { - // Start backup - Repository repository = context.getService(context.getServiceReference(Repository.class)); - Path basePath = Paths.get(System.getProperty("user.dir"), "backup"); - LogicalBackup backup = new LogicalBackup(context, repository, basePath); - backup.run(); - } - - @Override - public void stop(BundleContext context) throws Exception { - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/maintenance/package-info.java b/org.argeo.cms.jcr/src/org/argeo/maintenance/package-info.java deleted file mode 100644 index 1ce974c6f..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/maintenance/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Utilities for the maintenance of an Argeo Node. */ -package org.argeo.maintenance; \ No newline at end of file diff --git a/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAccessControlProvider.java b/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAccessControlProvider.java deleted file mode 100644 index bffe531a1..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAccessControlProvider.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.argeo.security.jackrabbit; - -import java.security.Principal; -import java.util.Map; -import java.util.Set; - -import javax.jcr.RepositoryException; -import javax.jcr.Session; - -import org.apache.jackrabbit.core.security.authorization.acl.ACLProvider; - -/** Argeo specific access control provider */ -public class ArgeoAccessControlProvider extends ACLProvider { - - @SuppressWarnings({ "rawtypes", "unchecked" }) - @Override - public void init(Session systemSession, Map configuration) throws RepositoryException { - if (!configuration.containsKey(PARAM_ALLOW_UNKNOWN_PRINCIPALS)) - configuration.put(PARAM_ALLOW_UNKNOWN_PRINCIPALS, "true"); - if (!configuration.containsKey(PARAM_OMIT_DEFAULT_PERMISSIONS)) - configuration.put(PARAM_OMIT_DEFAULT_PERMISSIONS, "true"); - super.init(systemSession, configuration); - } - - @Override - public boolean canAccessRoot(Set principals) throws RepositoryException { - return super.canAccessRoot(principals); - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAccessManager.java b/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAccessManager.java deleted file mode 100644 index 7464078d8..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAccessManager.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.argeo.security.jackrabbit; - -import javax.jcr.PathNotFoundException; -import javax.jcr.RepositoryException; -import javax.jcr.security.Privilege; - -import org.apache.jackrabbit.core.id.ItemId; -import org.apache.jackrabbit.core.security.DefaultAccessManager; -import org.apache.jackrabbit.spi.Path; - -/** - * Intermediary class in order to have a consistent naming in config files. Does - * nothing for the time being, but may in the future. - */ -public class ArgeoAccessManager extends DefaultAccessManager { - - @Override - public boolean canRead(Path itemPath, ItemId itemId) - throws RepositoryException { - return super.canRead(itemPath, itemId); - } - - @Override - public Privilege[] getPrivileges(String absPath) - throws PathNotFoundException, RepositoryException { - return super.getPrivileges(absPath); - } - - @Override - public boolean hasPrivileges(String absPath, Privilege[] privileges) - throws PathNotFoundException, RepositoryException { - return super.hasPrivileges(absPath, privileges); - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAuthContext.java b/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAuthContext.java deleted file mode 100644 index d679c45f9..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoAuthContext.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.argeo.security.jackrabbit; - -import javax.security.auth.Subject; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; - -import org.apache.jackrabbit.core.security.authentication.AuthContext; - -/** Wraps a regular {@link LoginContext}, using the proper class loader. */ -class ArgeoAuthContext implements AuthContext { - private LoginContext lc; - - public ArgeoAuthContext(String appName, Subject subject, CallbackHandler callbackHandler) { - try { - lc = new LoginContext(appName, subject, callbackHandler); - } catch (LoginException e) { - throw new IllegalStateException("Cannot configure Jackrabbit login context", e); - } - } - - @Override - public void login() throws LoginException { - lc.login(); - } - - @Override - public Subject getSubject() { - return lc.getSubject(); - } - - @Override - public void logout() throws LoginException { - lc.logout(); - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoSecurityManager.java b/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoSecurityManager.java deleted file mode 100644 index 36ee547e5..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/ArgeoSecurityManager.java +++ /dev/null @@ -1,163 +0,0 @@ -package org.argeo.security.jackrabbit; - -import java.security.Principal; -import java.util.HashSet; -import java.util.Properties; -import java.util.Set; - -import javax.jcr.Credentials; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.security.auth.Subject; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.x500.X500Principal; - -import org.apache.jackrabbit.api.security.user.UserManager; -import org.apache.jackrabbit.core.DefaultSecurityManager; -import org.apache.jackrabbit.core.security.AMContext; -import org.apache.jackrabbit.core.security.AccessManager; -import org.apache.jackrabbit.core.security.SecurityConstants; -import org.apache.jackrabbit.core.security.SystemPrincipal; -import org.apache.jackrabbit.core.security.authentication.AuthContext; -import org.apache.jackrabbit.core.security.authentication.CallbackHandlerImpl; -import org.apache.jackrabbit.core.security.authorization.WorkspaceAccessManager; -import org.apache.jackrabbit.core.security.principal.AdminPrincipal; -import org.apache.jackrabbit.core.security.principal.PrincipalProvider; -import org.argeo.api.cms.CmsSession; -import org.argeo.api.cms.DataAdminPrincipal; -import org.argeo.api.cms.CmsLog; -import org.argeo.api.cms.AnonymousPrincipal; -import org.argeo.api.cms.CmsConstants; -import org.argeo.cms.osgi.CmsOsgiUtils; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; - -/** Customises Jackrabbit security. */ -public class ArgeoSecurityManager extends DefaultSecurityManager { - private final static CmsLog log = CmsLog.getLog(ArgeoSecurityManager.class); - - private BundleContext cmsBundleContext = null; - - public ArgeoSecurityManager() { - if (FrameworkUtil.getBundle(CmsSession.class) != null) { - cmsBundleContext = FrameworkUtil.getBundle(CmsSession.class).getBundleContext(); - } - } - - public AuthContext getAuthContext(Credentials creds, Subject subject, String workspaceName) - throws RepositoryException { - checkInitialized(); - - CallbackHandler cbHandler = new CallbackHandlerImpl(creds, getSystemSession(), getPrincipalProviderRegistry(), - adminId, anonymousId); - String appName = "Jackrabbit"; - return new ArgeoAuthContext(appName, subject, cbHandler); - } - - @Override - public AccessManager getAccessManager(Session session, AMContext amContext) throws RepositoryException { - synchronized (getSystemSession()) { - return super.getAccessManager(session, amContext); - } - } - - @Override - public UserManager getUserManager(Session session) throws RepositoryException { - synchronized (getSystemSession()) { - return super.getUserManager(session); - } - } - - @Override - protected PrincipalProvider createDefaultPrincipalProvider(Properties[] moduleConfig) throws RepositoryException { - return super.createDefaultPrincipalProvider(moduleConfig); - } - - /** Called once when the session is created */ - @Override - public String getUserID(Subject subject, String workspaceName) throws RepositoryException { - boolean isAnonymous = !subject.getPrincipals(AnonymousPrincipal.class).isEmpty(); - boolean isDataAdmin = !subject.getPrincipals(DataAdminPrincipal.class).isEmpty(); - boolean isJackrabbitSystem = !subject.getPrincipals(SystemPrincipal.class).isEmpty(); - Set userPrincipal = subject.getPrincipals(X500Principal.class); - boolean isRegularUser = !userPrincipal.isEmpty(); - CmsSession cmsSession = null; - if (cmsBundleContext != null) { - cmsSession = CmsOsgiUtils.getCmsSession(cmsBundleContext, subject); - if (log.isTraceEnabled()) - log.trace("Opening JCR session for CMS session " + cmsSession); - } - - if (isAnonymous) { - if (isDataAdmin || isJackrabbitSystem || isRegularUser) - throw new IllegalStateException("Inconsistent " + subject); - else - return CmsConstants.ROLE_ANONYMOUS; - } else if (isRegularUser) {// must be before DataAdmin - if (isAnonymous || isJackrabbitSystem) - throw new IllegalStateException("Inconsistent " + subject); - else { - if (userPrincipal.size() > 1) { - StringBuilder buf = new StringBuilder(); - for (X500Principal principal : userPrincipal) - buf.append(' ').append('\"').append(principal).append('\"'); - throw new RuntimeException("Multiple user principals:" + buf); - } - return userPrincipal.iterator().next().getName(); - } - } else if (isDataAdmin) { - if (isAnonymous || isJackrabbitSystem || isRegularUser) - throw new IllegalStateException("Inconsistent " + subject); - else { - assert !subject.getPrincipals(AdminPrincipal.class).isEmpty(); - return CmsConstants.ROLE_DATA_ADMIN; - } - } else if (isJackrabbitSystem) { - if (isAnonymous || isDataAdmin || isRegularUser) - throw new IllegalStateException("Inconsistent " + subject); - else - return super.getUserID(subject, workspaceName); - } else { - throw new IllegalStateException("Unrecognized subject type: " + subject); - } - } - - @Override - protected WorkspaceAccessManager createDefaultWorkspaceAccessManager() { - WorkspaceAccessManager wam = super.createDefaultWorkspaceAccessManager(); - ArgeoWorkspaceAccessManagerImpl workspaceAccessManager = new ArgeoWorkspaceAccessManagerImpl(wam); - if (log.isTraceEnabled()) - log.trace("Created workspace access manager"); - return workspaceAccessManager; - } - - private class ArgeoWorkspaceAccessManagerImpl implements SecurityConstants, WorkspaceAccessManager { - private final WorkspaceAccessManager wam; - - public ArgeoWorkspaceAccessManagerImpl(WorkspaceAccessManager wam) { - super(); - this.wam = wam; - } - - public void init(Session systemSession) throws RepositoryException { - wam.init(systemSession); - Repository repository = systemSession.getRepository(); - if (log.isTraceEnabled()) - log.trace("Initialised workspace access manager on repository " + repository - + ", systemSession workspace: " + systemSession.getWorkspace().getName()); - } - - public void close() throws RepositoryException { - } - - public boolean grants(Set principals, String workspaceName) throws RepositoryException { - // TODO: implements finer access to workspaces - if (log.isTraceEnabled()) - log.trace("Grants " + new HashSet<>(principals) + " access to workspace '" + workspaceName + "'"); - return true; - // return wam.grants(principals, workspaceName); - } - } - -} diff --git a/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/SystemJackrabbitLoginModule.java b/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/SystemJackrabbitLoginModule.java deleted file mode 100644 index 0f63957b7..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/SystemJackrabbitLoginModule.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.argeo.security.jackrabbit; - -import java.util.Map; -import java.util.Set; - -import javax.security.auth.Subject; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.login.LoginException; -import javax.security.auth.spi.LoginModule; -import javax.security.auth.x500.X500Principal; - -import org.apache.jackrabbit.core.security.AnonymousPrincipal; -import org.apache.jackrabbit.core.security.SecurityConstants; -import org.apache.jackrabbit.core.security.principal.AdminPrincipal; -import org.argeo.api.cms.DataAdminPrincipal; - -/** JAAS login module used when initiating a new Jackrabbit session. */ -public class SystemJackrabbitLoginModule implements LoginModule { - private Subject subject; - - @Override - public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, - Map options) { - this.subject = subject; - } - - @Override - public boolean login() throws LoginException { - return true; - } - - @Override - public boolean commit() throws LoginException { - Set anonPrincipal = subject - .getPrincipals(org.argeo.api.cms.AnonymousPrincipal.class); - if (!anonPrincipal.isEmpty()) { - subject.getPrincipals().add(new AnonymousPrincipal()); - return true; - } - - Set initPrincipal = subject.getPrincipals(DataAdminPrincipal.class); - if (!initPrincipal.isEmpty()) { - subject.getPrincipals().add(new AdminPrincipal(SecurityConstants.ADMIN_ID)); - return true; - } - - Set userPrincipal = subject.getPrincipals(X500Principal.class); - if (userPrincipal.isEmpty()) - throw new LoginException("Subject must be pre-authenticated"); - if (userPrincipal.size() > 1) - throw new LoginException("Multiple user principals " + userPrincipal); - - return true; - } - - @Override - public boolean abort() throws LoginException { - return true; - } - - @Override - public boolean logout() throws LoginException { - subject.getPrincipals().removeAll(subject.getPrincipals(AnonymousPrincipal.class)); - subject.getPrincipals().removeAll(subject.getPrincipals(AdminPrincipal.class)); - return true; - } -} diff --git a/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/package-info.java b/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/package-info.java deleted file mode 100644 index 8529cc207..000000000 --- a/org.argeo.cms.jcr/src/org/argeo/security/jackrabbit/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Integration of Jackrabbit with Argeo security model. */ -package org.argeo.security.jackrabbit; \ No newline at end of file diff --git a/org.argeo.cms.servlet/.classpath b/org.argeo.cms.servlet/.classpath deleted file mode 100644 index e801ebfb4..000000000 --- a/org.argeo.cms.servlet/.classpath +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/org.argeo.cms.servlet/.project b/org.argeo.cms.servlet/.project deleted file mode 100644 index d39f97472..000000000 --- a/org.argeo.cms.servlet/.project +++ /dev/null @@ -1,33 +0,0 @@ - - - org.argeo.cms.servlet - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.pde.ManifestBuilder - - - - - org.eclipse.pde.SchemaBuilder - - - - - org.eclipse.pde.ds.core.builder - - - - - - org.eclipse.pde.PluginNature - org.eclipse.jdt.core.javanature - - diff --git a/org.argeo.cms.servlet/OSGI-INF/jettyServiceFactory.xml b/org.argeo.cms.servlet/OSGI-INF/jettyServiceFactory.xml deleted file mode 100644 index c007351aa..000000000 --- a/org.argeo.cms.servlet/OSGI-INF/jettyServiceFactory.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - diff --git a/org.argeo.cms.servlet/OSGI-INF/pkgServlet.xml b/org.argeo.cms.servlet/OSGI-INF/pkgServlet.xml deleted file mode 100644 index 00fcaff99..000000000 --- a/org.argeo.cms.servlet/OSGI-INF/pkgServlet.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/org.argeo.cms.servlet/OSGI-INF/pkgServletContext.xml b/org.argeo.cms.servlet/OSGI-INF/pkgServletContext.xml deleted file mode 100644 index 7540a2cdb..000000000 --- a/org.argeo.cms.servlet/OSGI-INF/pkgServletContext.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - - diff --git a/org.argeo.cms.servlet/bnd.bnd b/org.argeo.cms.servlet/bnd.bnd deleted file mode 100644 index b539a49bb..000000000 --- a/org.argeo.cms.servlet/bnd.bnd +++ /dev/null @@ -1,11 +0,0 @@ -Import-Package:\ -org.osgi.service.http;version=0.0.0,\ -org.osgi.service.http.whiteboard;version=0.0.0,\ -org.osgi.framework.namespace;version=0.0.0,\ -org.argeo.cms.osgi,\ -* - -Service-Component:\ -OSGI-INF/jettyServiceFactory.xml,\ -OSGI-INF/pkgServletContext.xml,\ -OSGI-INF/pkgServlet.xml diff --git a/org.argeo.cms.servlet/build.properties b/org.argeo.cms.servlet/build.properties deleted file mode 100644 index ee94f53be..000000000 --- a/org.argeo.cms.servlet/build.properties +++ /dev/null @@ -1,5 +0,0 @@ -output.. = bin/ -bin.includes = META-INF/,\ - .,\ - OSGI-INF/jettyServiceFactory.xml -source.. = src/ diff --git a/org.argeo.cms.servlet/pom.xml b/org.argeo.cms.servlet/pom.xml deleted file mode 100644 index a60b42d04..000000000 --- a/org.argeo.cms.servlet/pom.xml +++ /dev/null @@ -1,23 +0,0 @@ - - - 4.0.0 - - org.argeo.commons - 2.3-SNAPSHOT - argeo-commons - .. - - org.argeo.cms.servlet - jar - CMS Servlet - CMS components depending on the Servlet APIs - - - org.argeo.commons - org.argeo.cms - 2.3-SNAPSHOT - - - \ No newline at end of file diff --git a/org.argeo.cms.servlet/src/org/argeo/cms/servlet/CmsServletContext.java b/org.argeo.cms.servlet/src/org/argeo/cms/servlet/CmsServletContext.java deleted file mode 100644 index 1ae6286ac..000000000 --- a/org.argeo.cms.servlet/src/org/argeo/cms/servlet/CmsServletContext.java +++ /dev/null @@ -1,98 +0,0 @@ -package org.argeo.cms.servlet; - -import java.io.IOException; -import java.net.URL; -import java.security.PrivilegedAction; -import java.util.Map; - -import javax.security.auth.Subject; -import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.argeo.api.cms.CmsAuth; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.auth.RemoteAuthCallbackHandler; -import org.argeo.cms.auth.RemoteAuthUtils; -import org.argeo.cms.servlet.internal.HttpUtils; -import org.osgi.framework.Bundle; -import org.osgi.framework.FrameworkUtil; -import org.osgi.service.http.context.ServletContextHelper; - -/** - * Default servlet context degrading to anonymous if the the session is not - * pre-authenticated. - */ -public class CmsServletContext extends ServletContextHelper { - private final static CmsLog log = CmsLog.getLog(CmsServletContext.class); - // use CMS bundle for resources - private Bundle bundle = FrameworkUtil.getBundle(getClass()); - - public void init(Map properties) { - - } - - public void destroy() { - - } - - @Override - public boolean handleSecurity(HttpServletRequest request, HttpServletResponse response) throws IOException { - if (log.isTraceEnabled()) - HttpUtils.logRequestHeaders(log, request); - LoginContext lc; - try { - lc = CmsAuth.USER.newLoginContext( - new RemoteAuthCallbackHandler(new ServletHttpRequest(request), new ServletHttpResponse(response))); - lc.login(); - } catch (LoginException e) { - lc = processUnauthorized(request, response); - if (log.isTraceEnabled()) - HttpUtils.logResponseHeaders(log, response); - if (lc == null) - return false; - } - - Subject subject = lc.getSubject(); - // log.debug("SERVLET CONTEXT: "+subject); - Subject.doAs(subject, new PrivilegedAction() { - - @Override - public Void run() { - // TODO also set login context in order to log out ? - RemoteAuthUtils.configureRequestSecurity(new ServletHttpRequest(request)); - return null; - } - - }); - return true; - } - - @Override - public void finishSecurity(HttpServletRequest request, HttpServletResponse response) { - RemoteAuthUtils.clearRequestSecurity(new ServletHttpRequest(request)); - } - - protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) { - // anonymous - try { - LoginContext lc = new LoginContext(CmsAuth.LOGIN_CONTEXT_ANONYMOUS, - new RemoteAuthCallbackHandler(new ServletHttpRequest(request), new ServletHttpResponse(response))); - lc.login(); - return lc; - } catch (LoginException e1) { - if (log.isDebugEnabled()) - log.error("Cannot log in as anonymous", e1); - return null; - } - } - - @Override - public URL getResource(String name) { - // TODO make it more robust and versatile - // if used directly it can only load from within this bundle - return bundle.getResource(name); - } - -} diff --git a/org.argeo.cms.servlet/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java b/org.argeo.cms.servlet/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java deleted file mode 100644 index 3bea0b4de..000000000 --- a/org.argeo.cms.servlet/src/org/argeo/cms/servlet/PrivateWwwAuthServletContext.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.argeo.cms.servlet; - -import javax.security.auth.login.LoginContext; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.argeo.cms.auth.SpnegoLoginModule; -import org.argeo.cms.servlet.internal.HttpUtils; - -/** Servlet context forcing authentication. */ -public class PrivateWwwAuthServletContext extends CmsServletContext { - // TODO make it configurable - private final String httpAuthRealm = "Argeo"; - private final boolean forceBasic = false; - - @Override - protected LoginContext processUnauthorized(HttpServletRequest request, HttpServletResponse response) { - askForWwwAuth(request, response); - return null; - } - - protected void askForWwwAuth(HttpServletRequest request, HttpServletResponse response) { - // response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "basic - // realm=\"" + httpAuthRealm + "\""); - if (SpnegoLoginModule.hasAcceptorCredentials() && !forceBasic)// SPNEGO - response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "Negotiate"); - else - response.setHeader(HttpUtils.HEADER_WWW_AUTHENTICATE, "Basic realm=\"" + httpAuthRealm + "\""); - - // response.setDateHeader("Date", System.currentTimeMillis()); - // response.setDateHeader("Expires", System.currentTimeMillis() + (24 * - // 60 * 60 * 1000)); - // response.setHeader("Accept-Ranges", "bytes"); - // response.setHeader("Connection", "Keep-Alive"); - // response.setHeader("Keep-Alive", "timeout=5, max=97"); - // response.setContentType("text/html; charset=UTF-8"); - response.setStatus(401); - } -} diff --git a/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpRequest.java b/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpRequest.java deleted file mode 100644 index 95912e407..000000000 --- a/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpRequest.java +++ /dev/null @@ -1,63 +0,0 @@ -package org.argeo.cms.servlet; - -import java.util.Locale; -import java.util.Objects; - -import javax.servlet.http.HttpServletRequest; - -import org.argeo.cms.auth.RemoteAuthRequest; -import org.argeo.cms.auth.RemoteAuthSession; - -public class ServletHttpRequest implements RemoteAuthRequest { - private final HttpServletRequest request; - - public ServletHttpRequest(HttpServletRequest request) { - Objects.requireNonNull(request); - this.request = request; - } - - @Override - public RemoteAuthSession getSession() { - return new ServletHttpSession(request.getSession(false)); - } - - @Override - public RemoteAuthSession createSession() { - return new ServletHttpSession(request.getSession(true)); - } - - @Override - public Locale getLocale() { - return request.getLocale(); - } - - @Override - public Object getAttribute(String key) { - return request.getAttribute(key); - } - - @Override - public void setAttribute(String key, Object object) { - request.setAttribute(key, object); - } - - @Override - public String getHeader(String key) { - return request.getHeader(key); - } - - @Override - public String getRemoteAddr() { - return request.getRemoteAddr(); - } - - @Override - public int getLocalPort() { - return request.getLocalPort(); - } - - @Override - public int getRemotePort() { - return request.getRemotePort(); - } -} diff --git a/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpResponse.java b/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpResponse.java deleted file mode 100644 index de47365ca..000000000 --- a/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpResponse.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.argeo.cms.servlet; - -import java.util.Objects; - -import javax.servlet.http.HttpServletResponse; - -import org.argeo.cms.auth.RemoteAuthResponse; - -public class ServletHttpResponse implements RemoteAuthResponse { - private final HttpServletResponse response; - - public ServletHttpResponse(HttpServletResponse response) { - Objects.requireNonNull(response); - this.response = response; - } - - @Override - public void setHeader(String keys, String value) { - response.setHeader(keys, value); - } - -} diff --git a/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpSession.java b/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpSession.java deleted file mode 100644 index 8d087daa7..000000000 --- a/org.argeo.cms.servlet/src/org/argeo/cms/servlet/ServletHttpSession.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.argeo.cms.servlet; - -import org.argeo.cms.auth.RemoteAuthSession; - -public class ServletHttpSession implements RemoteAuthSession { - private javax.servlet.http.HttpSession session; - - public ServletHttpSession(javax.servlet.http.HttpSession session) { - super(); - this.session = session; - } - - @Override - public boolean isValid() { - try {// test http session - session.getCreationTime(); - return true; - } catch (IllegalStateException ise) { - return false; - } - } - - @Override - public String getId() { - return session.getId(); - } - -} diff --git a/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/HttpUtils.java b/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/HttpUtils.java deleted file mode 100644 index 70f2cc6b0..000000000 --- a/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/HttpUtils.java +++ /dev/null @@ -1,70 +0,0 @@ -package org.argeo.cms.servlet.internal; - -import java.util.Enumeration; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.argeo.api.cms.CmsLog; - -public class HttpUtils { - public final static String HEADER_AUTHORIZATION = "Authorization"; - public final static String HEADER_WWW_AUTHENTICATE = "WWW-Authenticate"; - - static boolean isBrowser(String userAgent) { - return userAgent.contains("webkit") || userAgent.contains("gecko") || userAgent.contains("firefox") - || userAgent.contains("msie") || userAgent.contains("chrome") || userAgent.contains("chromium") - || userAgent.contains("opera") || userAgent.contains("browser"); - } - - public static void logResponseHeaders(CmsLog log, HttpServletResponse response) { - if (!log.isDebugEnabled()) - return; - for (String headerName : response.getHeaderNames()) { - Object headerValue = response.getHeader(headerName); - log.debug(headerName + ": " + headerValue); - } - } - - public static void logRequestHeaders(CmsLog log, HttpServletRequest request) { - if (!log.isDebugEnabled()) - return; - for (Enumeration headerNames = request.getHeaderNames(); headerNames.hasMoreElements();) { - String headerName = headerNames.nextElement(); - Object headerValue = request.getHeader(headerName); - log.debug(headerName + ": " + headerValue); - } - log.debug(request.getRequestURI() + "\n"); - } - - public static void logRequest(CmsLog log, HttpServletRequest request) { - log.debug("contextPath=" + request.getContextPath()); - log.debug("servletPath=" + request.getServletPath()); - log.debug("requestURI=" + request.getRequestURI()); - log.debug("queryString=" + request.getQueryString()); - StringBuilder buf = new StringBuilder(); - // headers - Enumeration en = request.getHeaderNames(); - while (en.hasMoreElements()) { - String header = en.nextElement(); - Enumeration values = request.getHeaders(header); - while (values.hasMoreElements()) - buf.append(" " + header + ": " + values.nextElement()); - buf.append('\n'); - } - - // attributed - Enumeration an = request.getAttributeNames(); - while (an.hasMoreElements()) { - String attr = an.nextElement(); - Object value = request.getAttribute(attr); - buf.append(" " + attr + ": " + value); - buf.append('\n'); - } - log.debug("\n" + buf); - } - - private HttpUtils() { - - } -} diff --git a/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/PkgServlet.java b/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/PkgServlet.java deleted file mode 100644 index c762b67ec..000000000 --- a/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/PkgServlet.java +++ /dev/null @@ -1,133 +0,0 @@ -package org.argeo.cms.servlet.internal; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.Collection; -import java.util.SortedMap; -import java.util.TreeMap; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -import org.apache.commons.io.IOUtils; -import org.argeo.cms.osgi.PublishNamespace; -import org.argeo.osgi.util.FilterRequirement; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; -import org.osgi.framework.Version; -import org.osgi.framework.VersionRange; -import org.osgi.framework.namespace.PackageNamespace; -import org.osgi.framework.wiring.BundleCapability; -import org.osgi.framework.wiring.BundleWiring; -import org.osgi.framework.wiring.FrameworkWiring; -import org.osgi.resource.Requirement; - -public class PkgServlet extends HttpServlet { - private static final long serialVersionUID = 7660824185145214324L; - - private BundleContext bundleContext = FrameworkUtil.getBundle(PkgServlet.class).getBundleContext(); - - @Override - protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { - String pathInfo = req.getPathInfo(); - - String pkg, versionStr, file; - String[] parts = pathInfo.split("/"); - // first is always empty - if (parts.length == 4) { - pkg = parts[1]; - versionStr = parts[2]; - file = parts[3]; - } else if (parts.length == 3) { - pkg = parts[1]; - versionStr = null; - file = parts[2]; - } else { - throw new IllegalArgumentException("Unsupported path length " + pathInfo); - } - - FrameworkWiring frameworkWiring = bundleContext.getBundle(0).adapt(FrameworkWiring.class); - String filter; - if (versionStr == null) { - filter = "(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")"; - } else { - if (versionStr.startsWith("[") || versionStr.startsWith("(")) {// range - VersionRange versionRange = new VersionRange(versionStr); - filter = "(&(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")" - + versionRange.toFilterString(PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE) + ")"; - - } else { - Version version = new Version(versionStr); - filter = "(&(" + PackageNamespace.PACKAGE_NAMESPACE + "=" + pkg + ")(" - + PackageNamespace.CAPABILITY_VERSION_ATTRIBUTE + "=" + version + "))"; - } - } - Requirement requirement = new FilterRequirement(PackageNamespace.PACKAGE_NAMESPACE, filter); - Collection packages = frameworkWiring.findProviders(requirement); - if (packages.isEmpty()) { - resp.sendError(404); - return; - } - - // TODO verify that it works with multiple versions - SortedMap sorted = new TreeMap<>(); - for (BundleCapability capability : packages) { - sorted.put(capability.getRevision().getVersion(), capability); - } - - Bundle bundle = sorted.get(sorted.firstKey()).getRevision().getBundle(); - String entryPath = '/' + pkg.replace('.', '/') + '/' + file; - URL internalURL = bundle.getResource(entryPath); - if (internalURL == null) { - resp.sendError(404); - return; - } - - // Resource found, we now check whether it can be published - boolean publish = false; - BundleWiring bundleWiring = bundle.adapt(BundleWiring.class); - capabilities: for (BundleCapability bundleCapability : bundleWiring - .getCapabilities(PublishNamespace.CMS_PUBLISH_NAMESPACE)) { - Object publishedPkg = bundleCapability.getAttributes().get(PublishNamespace.PKG); - if (publishedPkg != null) { - if (publishedPkg.equals("*") || publishedPkg.equals(pkg)) { - Object publishedFile = bundleCapability.getAttributes().get(PublishNamespace.FILE); - if (publishedFile == null) { - publish = true; - break capabilities; - } else { - String[] publishedFiles = publishedFile.toString().split(","); - for (String pattern : publishedFiles) { - if (pattern.startsWith("*.")) { - String ext = pattern.substring(1); - if (file.endsWith(ext)) { - publish = true; - break capabilities; - } - } else { - if (publishedFile.equals(file)) { - publish = true; - break capabilities; - } - } - } - } - } - } - } - - if (!publish) { - resp.sendError(404); - return; - } - - try (InputStream in = internalURL.openStream()) { - IOUtils.copy(in, resp.getOutputStream()); - } - } - -} diff --git a/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/RobotServlet.java b/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/RobotServlet.java deleted file mode 100644 index 288ee268c..000000000 --- a/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/RobotServlet.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.argeo.cms.servlet.internal; - -import java.io.IOException; -import java.io.PrintWriter; - -import javax.servlet.ServletException; -import javax.servlet.http.HttpServlet; -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; - -public class RobotServlet extends HttpServlet { - private static final long serialVersionUID = 7935661175336419089L; - - @Override - protected void service(HttpServletRequest request, HttpServletResponse response) - throws ServletException, IOException { - PrintWriter writer = response.getWriter(); - writer.append("User-agent: *\n"); - writer.append("Disallow:\n"); - response.setHeader("Content-Type", "text/plain"); - writer.flush(); - } - -} diff --git a/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/jetty/JettyServiceFactory.java b/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/jetty/JettyServiceFactory.java deleted file mode 100644 index 05de32c48..000000000 --- a/org.argeo.cms.servlet/src/org/argeo/cms/servlet/internal/jetty/JettyServiceFactory.java +++ /dev/null @@ -1,79 +0,0 @@ -package org.argeo.cms.servlet.internal.jetty; - -import java.util.Dictionary; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsLog; -import org.eclipse.equinox.http.jetty.JettyConfigurator; -import org.osgi.framework.FrameworkUtil; -import org.osgi.service.cm.ConfigurationException; -import org.osgi.service.cm.ManagedServiceFactory; - -public class JettyServiceFactory implements ManagedServiceFactory { - private final CmsLog log = CmsLog.getLog(JettyServiceFactory.class); - - public void start() { - - } - - @Override - public String getName() { - return "Jetty Service Factory"; - } - - @Override - public void updated(String pid, Dictionary properties) throws ConfigurationException { - // Explicitly configures Jetty so that the default server is not started by the - // activator of the Equinox Jetty bundle. - -// if (!webServerConfig.isEmpty()) { -// webServerConfig.put("customizer.class", KernelConstants.CMS_JETTY_CUSTOMIZER_CLASS); -// -// // TODO centralise with Jetty extender -// Object webSocketEnabled = webServerConfig.get(InternalHttpConstants.WEBSOCKET_ENABLED); -// if (webSocketEnabled != null && webSocketEnabled.toString().equals("true")) { -// bc.registerService(ServerEndpointConfig.Configurator.class, new CmsWebSocketConfigurator(), null); -// webServerConfig.put(InternalHttpConstants.WEBSOCKET_ENABLED, "true"); -// } -// } - - int tryCount = 60; - try { - tryGettyJetty: while (tryCount > 0) { - try { - // FIXME deal with multiple ids - JettyConfigurator.startServer(CmsConstants.DEFAULT, properties); - // Explicitly starts Jetty OSGi HTTP bundle, so that it gets triggered if OSGi - // configuration is not cleaned - FrameworkUtil.getBundle(JettyConfigurator.class).start(); - break tryGettyJetty; - } catch (IllegalStateException e) { - // Jetty may not be ready - try { - Thread.sleep(1000); - } catch (Exception e1) { - // silent - } - tryCount--; - } - } - } catch (Exception e) { - log.error("Cannot start default Jetty server with config " + properties, e); - } - - } - - @Override - public void deleted(String pid) { - } - - public void stop() { - try { - JettyConfigurator.stopServer(CmsConstants.DEFAULT); - } catch (Exception e) { - log.error("Cannot stop default Jetty server.", e); - } - - } - -} diff --git a/org.argeo.cms.swt/.classpath b/org.argeo.cms.swt/.classpath deleted file mode 100644 index e03d341b1..000000000 --- a/org.argeo.cms.swt/.classpath +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - diff --git a/org.argeo.cms.swt/.project b/org.argeo.cms.swt/.project deleted file mode 100644 index 082112e6d..000000000 --- a/org.argeo.cms.swt/.project +++ /dev/null @@ -1,28 +0,0 @@ - - - org.argeo.cms.swt - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.pde.ManifestBuilder - - - - - org.eclipse.pde.SchemaBuilder - - - - - - org.eclipse.pde.PluginNature - org.eclipse.jdt.core.javanature - - diff --git a/org.argeo.cms.swt/META-INF/.gitignore b/org.argeo.cms.swt/META-INF/.gitignore deleted file mode 100644 index 4854a41b9..000000000 --- a/org.argeo.cms.swt/META-INF/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/MANIFEST.MF diff --git a/org.argeo.cms.swt/bnd.bnd b/org.argeo.cms.swt/bnd.bnd deleted file mode 100644 index 9d4eae6c3..000000000 --- a/org.argeo.cms.swt/bnd.bnd +++ /dev/null @@ -1,7 +0,0 @@ -Import-Package: org.eclipse.swt,\ - org.eclipse.jface.window,\ - org.eclipse.core.commands.common,\ - * - -Bundle-ActivationPolicy: lazy - \ No newline at end of file diff --git a/org.argeo.cms.swt/build.properties b/org.argeo.cms.swt/build.properties deleted file mode 100644 index 0e0438744..000000000 --- a/org.argeo.cms.swt/build.properties +++ /dev/null @@ -1,5 +0,0 @@ -source.. = src/ -output.. = bin/ -bin.includes = META-INF/,\ - . - \ No newline at end of file diff --git a/org.argeo.cms.swt/icons/actions/add.png b/org.argeo.cms.swt/icons/actions/add.png deleted file mode 100644 index 5c06bf082..000000000 Binary files a/org.argeo.cms.swt/icons/actions/add.png and /dev/null differ diff --git a/org.argeo.cms.swt/icons/actions/close-all.png b/org.argeo.cms.swt/icons/actions/close-all.png deleted file mode 100644 index 81bfc950b..000000000 Binary files a/org.argeo.cms.swt/icons/actions/close-all.png and /dev/null differ diff --git a/org.argeo.cms.swt/icons/actions/delete.png b/org.argeo.cms.swt/icons/actions/delete.png deleted file mode 100644 index 9712723d7..000000000 Binary files a/org.argeo.cms.swt/icons/actions/delete.png and /dev/null differ diff --git a/org.argeo.cms.swt/icons/actions/edit.png b/org.argeo.cms.swt/icons/actions/edit.png deleted file mode 100644 index ad3db9f42..000000000 Binary files a/org.argeo.cms.swt/icons/actions/edit.png and /dev/null differ diff --git a/org.argeo.cms.swt/icons/actions/save-all.png b/org.argeo.cms.swt/icons/actions/save-all.png deleted file mode 100644 index f48ed320b..000000000 Binary files a/org.argeo.cms.swt/icons/actions/save-all.png and /dev/null differ diff --git a/org.argeo.cms.swt/icons/actions/save.png b/org.argeo.cms.swt/icons/actions/save.png deleted file mode 100644 index 1c58ada49..000000000 Binary files a/org.argeo.cms.swt/icons/actions/save.png and /dev/null differ diff --git a/org.argeo.cms.swt/icons/active.gif b/org.argeo.cms.swt/icons/active.gif deleted file mode 100644 index 7d24707ee..000000000 Binary files a/org.argeo.cms.swt/icons/active.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/add.gif b/org.argeo.cms.swt/icons/add.gif deleted file mode 100644 index 252d7ebcb..000000000 Binary files a/org.argeo.cms.swt/icons/add.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/add.png b/org.argeo.cms.swt/icons/add.png deleted file mode 100644 index c7edfecaa..000000000 Binary files a/org.argeo.cms.swt/icons/add.png and /dev/null differ diff --git a/org.argeo.cms.swt/icons/addFolder.gif b/org.argeo.cms.swt/icons/addFolder.gif deleted file mode 100644 index d3f43d977..000000000 Binary files a/org.argeo.cms.swt/icons/addFolder.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/addPrivileges.gif b/org.argeo.cms.swt/icons/addPrivileges.gif deleted file mode 100644 index a6b251fc8..000000000 Binary files a/org.argeo.cms.swt/icons/addPrivileges.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/addRepo.gif b/org.argeo.cms.swt/icons/addRepo.gif deleted file mode 100644 index 26d81c065..000000000 Binary files a/org.argeo.cms.swt/icons/addRepo.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/addWorkspace.png b/org.argeo.cms.swt/icons/addWorkspace.png deleted file mode 100644 index bbee7755f..000000000 Binary files a/org.argeo.cms.swt/icons/addWorkspace.png and /dev/null differ diff --git a/org.argeo.cms.swt/icons/adminLog.gif b/org.argeo.cms.swt/icons/adminLog.gif deleted file mode 100644 index 6ef3bca66..000000000 Binary files a/org.argeo.cms.swt/icons/adminLog.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/batch.gif b/org.argeo.cms.swt/icons/batch.gif deleted file mode 100644 index b8ca14a8b..000000000 Binary files a/org.argeo.cms.swt/icons/batch.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/begin.gif b/org.argeo.cms.swt/icons/begin.gif deleted file mode 100755 index feb8e94a7..000000000 Binary files a/org.argeo.cms.swt/icons/begin.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/binary.png b/org.argeo.cms.swt/icons/binary.png deleted file mode 100644 index fdf4f82be..000000000 Binary files a/org.argeo.cms.swt/icons/binary.png and /dev/null differ diff --git a/org.argeo.cms.swt/icons/browser.gif b/org.argeo.cms.swt/icons/browser.gif deleted file mode 100644 index 6c7320c69..000000000 Binary files a/org.argeo.cms.swt/icons/browser.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/bundles.gif b/org.argeo.cms.swt/icons/bundles.gif deleted file mode 100644 index e9a6bd966..000000000 Binary files a/org.argeo.cms.swt/icons/bundles.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/changePassword.gif b/org.argeo.cms.swt/icons/changePassword.gif deleted file mode 100644 index 274a850e4..000000000 Binary files a/org.argeo.cms.swt/icons/changePassword.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/clear.gif b/org.argeo.cms.swt/icons/clear.gif deleted file mode 100644 index 6bc10f9d0..000000000 Binary files a/org.argeo.cms.swt/icons/clear.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/close-all.png b/org.argeo.cms.swt/icons/close-all.png deleted file mode 100644 index 85d4d429b..000000000 Binary files a/org.argeo.cms.swt/icons/close-all.png and /dev/null differ diff --git a/org.argeo.cms.swt/icons/commit.gif b/org.argeo.cms.swt/icons/commit.gif deleted file mode 100755 index 876f3eb16..000000000 Binary files a/org.argeo.cms.swt/icons/commit.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/delete.png b/org.argeo.cms.swt/icons/delete.png deleted file mode 100644 index 676a39dcf..000000000 Binary files a/org.argeo.cms.swt/icons/delete.png and /dev/null differ diff --git a/org.argeo.cms.swt/icons/dumpNode.gif b/org.argeo.cms.swt/icons/dumpNode.gif deleted file mode 100644 index 14eb1be09..000000000 Binary files a/org.argeo.cms.swt/icons/dumpNode.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/file.gif b/org.argeo.cms.swt/icons/file.gif deleted file mode 100644 index ef3028807..000000000 Binary files a/org.argeo.cms.swt/icons/file.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/folder.gif b/org.argeo.cms.swt/icons/folder.gif deleted file mode 100644 index 42e027c93..000000000 Binary files a/org.argeo.cms.swt/icons/folder.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/getSize.gif b/org.argeo.cms.swt/icons/getSize.gif deleted file mode 100644 index b05bf3e3d..000000000 Binary files a/org.argeo.cms.swt/icons/getSize.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/group.png b/org.argeo.cms.swt/icons/group.png deleted file mode 100644 index cc6683aff..000000000 Binary files a/org.argeo.cms.swt/icons/group.png and /dev/null differ diff --git a/org.argeo.cms.swt/icons/home.gif b/org.argeo.cms.swt/icons/home.gif deleted file mode 100644 index fd0c66950..000000000 Binary files a/org.argeo.cms.swt/icons/home.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/home.png b/org.argeo.cms.swt/icons/home.png deleted file mode 100644 index 5eb096790..000000000 Binary files a/org.argeo.cms.swt/icons/home.png and /dev/null differ diff --git a/org.argeo.cms.swt/icons/import_fs.png b/org.argeo.cms.swt/icons/import_fs.png deleted file mode 100644 index d7c890c81..000000000 Binary files a/org.argeo.cms.swt/icons/import_fs.png and /dev/null differ diff --git a/org.argeo.cms.swt/icons/installed.gif b/org.argeo.cms.swt/icons/installed.gif deleted file mode 100644 index 298871653..000000000 Binary files a/org.argeo.cms.swt/icons/installed.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/log.gif b/org.argeo.cms.swt/icons/log.gif deleted file mode 100644 index e3ecc5535..000000000 Binary files a/org.argeo.cms.swt/icons/log.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/logout.png b/org.argeo.cms.swt/icons/logout.png deleted file mode 100644 index f2952fa5b..000000000 Binary files a/org.argeo.cms.swt/icons/logout.png and /dev/null differ diff --git a/org.argeo.cms.swt/icons/maintenance.gif b/org.argeo.cms.swt/icons/maintenance.gif deleted file mode 100644 index e5690ecb1..000000000 Binary files a/org.argeo.cms.swt/icons/maintenance.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/node.gif b/org.argeo.cms.swt/icons/node.gif deleted file mode 100644 index 364c0e70b..000000000 Binary files a/org.argeo.cms.swt/icons/node.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/nodes.gif b/org.argeo.cms.swt/icons/nodes.gif deleted file mode 100644 index bba3dbc69..000000000 Binary files a/org.argeo.cms.swt/icons/nodes.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/osgi_explorer.gif b/org.argeo.cms.swt/icons/osgi_explorer.gif deleted file mode 100644 index e9a6bd966..000000000 Binary files a/org.argeo.cms.swt/icons/osgi_explorer.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/password.gif b/org.argeo.cms.swt/icons/password.gif deleted file mode 100644 index a6b251fc8..000000000 Binary files a/org.argeo.cms.swt/icons/password.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/person-logged-in.png b/org.argeo.cms.swt/icons/person-logged-in.png deleted file mode 100644 index 87acc1435..000000000 Binary files a/org.argeo.cms.swt/icons/person-logged-in.png and /dev/null differ diff --git a/org.argeo.cms.swt/icons/person.png b/org.argeo.cms.swt/icons/person.png deleted file mode 100644 index 7d979a531..000000000 Binary files a/org.argeo.cms.swt/icons/person.png and /dev/null differ diff --git a/org.argeo.cms.swt/icons/query.png b/org.argeo.cms.swt/icons/query.png deleted file mode 100644 index 54c089de1..000000000 Binary files a/org.argeo.cms.swt/icons/query.png and /dev/null differ diff --git a/org.argeo.cms.swt/icons/refresh.png b/org.argeo.cms.swt/icons/refresh.png deleted file mode 100644 index 71b3481c9..000000000 Binary files a/org.argeo.cms.swt/icons/refresh.png and /dev/null differ diff --git a/org.argeo.cms.swt/icons/remote_connected.gif b/org.argeo.cms.swt/icons/remote_connected.gif deleted file mode 100644 index 1492b4efa..000000000 Binary files a/org.argeo.cms.swt/icons/remote_connected.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/remote_disconnected.gif b/org.argeo.cms.swt/icons/remote_disconnected.gif deleted file mode 100644 index 6c54da9ad..000000000 Binary files a/org.argeo.cms.swt/icons/remote_disconnected.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/remove.gif b/org.argeo.cms.swt/icons/remove.gif deleted file mode 100644 index 0ae6decd0..000000000 Binary files a/org.argeo.cms.swt/icons/remove.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/removePrivileges.gif b/org.argeo.cms.swt/icons/removePrivileges.gif deleted file mode 100644 index aa78fd2fa..000000000 Binary files a/org.argeo.cms.swt/icons/removePrivileges.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/rename.gif b/org.argeo.cms.swt/icons/rename.gif deleted file mode 100644 index 8048405a7..000000000 Binary files a/org.argeo.cms.swt/icons/rename.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/repositories.gif b/org.argeo.cms.swt/icons/repositories.gif deleted file mode 100644 index c13bea1ca..000000000 Binary files a/org.argeo.cms.swt/icons/repositories.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/repository_connected.gif b/org.argeo.cms.swt/icons/repository_connected.gif deleted file mode 100644 index a15fa5538..000000000 Binary files a/org.argeo.cms.swt/icons/repository_connected.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/repository_disconnected.gif b/org.argeo.cms.swt/icons/repository_disconnected.gif deleted file mode 100644 index 4576dc563..000000000 Binary files a/org.argeo.cms.swt/icons/repository_disconnected.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/resolved.gif b/org.argeo.cms.swt/icons/resolved.gif deleted file mode 100644 index f4a1ea150..000000000 Binary files a/org.argeo.cms.swt/icons/resolved.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/role.gif b/org.argeo.cms.swt/icons/role.gif deleted file mode 100644 index 274a850e4..000000000 Binary files a/org.argeo.cms.swt/icons/role.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/rollback.gif b/org.argeo.cms.swt/icons/rollback.gif deleted file mode 100755 index c75399599..000000000 Binary files a/org.argeo.cms.swt/icons/rollback.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/save-all.png b/org.argeo.cms.swt/icons/save-all.png deleted file mode 100644 index b68a29b2c..000000000 Binary files a/org.argeo.cms.swt/icons/save-all.png and /dev/null differ diff --git a/org.argeo.cms.swt/icons/save.gif b/org.argeo.cms.swt/icons/save.gif deleted file mode 100644 index 654ad7b42..000000000 Binary files a/org.argeo.cms.swt/icons/save.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/save.png b/org.argeo.cms.swt/icons/save.png deleted file mode 100644 index f27ef2d26..000000000 Binary files a/org.argeo.cms.swt/icons/save.png and /dev/null differ diff --git a/org.argeo.cms.swt/icons/save_security.png b/org.argeo.cms.swt/icons/save_security.png deleted file mode 100644 index ca41dc92b..000000000 Binary files a/org.argeo.cms.swt/icons/save_security.png and /dev/null differ diff --git a/org.argeo.cms.swt/icons/save_security_disabled.png b/org.argeo.cms.swt/icons/save_security_disabled.png deleted file mode 100644 index fb7d08d9a..000000000 Binary files a/org.argeo.cms.swt/icons/save_security_disabled.png and /dev/null differ diff --git a/org.argeo.cms.swt/icons/security.gif b/org.argeo.cms.swt/icons/security.gif deleted file mode 100644 index 57fb95edc..000000000 Binary files a/org.argeo.cms.swt/icons/security.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/service_published.gif b/org.argeo.cms.swt/icons/service_published.gif deleted file mode 100644 index 17f771aff..000000000 Binary files a/org.argeo.cms.swt/icons/service_published.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/service_referenced.gif b/org.argeo.cms.swt/icons/service_referenced.gif deleted file mode 100644 index c24a95fba..000000000 Binary files a/org.argeo.cms.swt/icons/service_referenced.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/sort.gif b/org.argeo.cms.swt/icons/sort.gif deleted file mode 100644 index 23c5d0b11..000000000 Binary files a/org.argeo.cms.swt/icons/sort.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/starting.gif b/org.argeo.cms.swt/icons/starting.gif deleted file mode 100644 index 563743d39..000000000 Binary files a/org.argeo.cms.swt/icons/starting.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/sync.gif b/org.argeo.cms.swt/icons/sync.gif deleted file mode 100644 index b4fa052de..000000000 Binary files a/org.argeo.cms.swt/icons/sync.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/user.gif b/org.argeo.cms.swt/icons/user.gif deleted file mode 100644 index 90a00147b..000000000 Binary files a/org.argeo.cms.swt/icons/user.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/users.gif b/org.argeo.cms.swt/icons/users.gif deleted file mode 100644 index 2de7edd64..000000000 Binary files a/org.argeo.cms.swt/icons/users.gif and /dev/null differ diff --git a/org.argeo.cms.swt/icons/workgroup.png b/org.argeo.cms.swt/icons/workgroup.png deleted file mode 100644 index 7fef996df..000000000 Binary files a/org.argeo.cms.swt/icons/workgroup.png and /dev/null differ diff --git a/org.argeo.cms.swt/icons/workgroup.xcf b/org.argeo.cms.swt/icons/workgroup.xcf deleted file mode 100644 index f517c827c..000000000 Binary files a/org.argeo.cms.swt/icons/workgroup.xcf and /dev/null differ diff --git a/org.argeo.cms.swt/icons/workspace_connected.png b/org.argeo.cms.swt/icons/workspace_connected.png deleted file mode 100644 index 0430baaf5..000000000 Binary files a/org.argeo.cms.swt/icons/workspace_connected.png and /dev/null differ diff --git a/org.argeo.cms.swt/icons/workspace_disconnected.png b/org.argeo.cms.swt/icons/workspace_disconnected.png deleted file mode 100644 index fddcb8c4e..000000000 Binary files a/org.argeo.cms.swt/icons/workspace_disconnected.png and /dev/null differ diff --git a/org.argeo.cms.swt/pom.xml b/org.argeo.cms.swt/pom.xml deleted file mode 100644 index f31cfa004..000000000 --- a/org.argeo.cms.swt/pom.xml +++ /dev/null @@ -1,53 +0,0 @@ - - - 4.0.0 - - org.argeo.commons - 2.3-SNAPSHOT - argeo-commons - .. - - org.argeo.cms.swt - CMS SWT - - - - - - - - org.argeo.commons - org.argeo.cms - 2.3-SNAPSHOT - - - org.argeo.commons - org.argeo.cms.servlet - 2.3-SNAPSHOT - - - - - org.argeo.commons.rap - org.argeo.swt.specific.rap - 2.3-SNAPSHOT - provided - - - - org.argeo.tp.rap.e4 - org.eclipse.rap.rwt - provided - - - org.argeo.tp.rap.e4 - org.eclipse.core.commands - provided - - - org.argeo.tp.rap.e4 - org.eclipse.rap.jface - provided - - - \ No newline at end of file diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsIcon.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsIcon.java deleted file mode 100644 index 4ff89f27a..000000000 --- a/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsIcon.java +++ /dev/null @@ -1,25 +0,0 @@ -package org.argeo.cms.swt; - -import org.argeo.api.cms.CmsTheme; -import org.eclipse.swt.graphics.Image; - -/** Can be applied to {@link Enum}s in order to generated {@link Image}s. */ -public interface CmsIcon { - String name(); - - default Image getSmallIcon(CmsTheme theme) { - return ((CmsSwtTheme) theme).getIcon(name(), getSmallIconSize()); - } - - default Image getBigIcon(CmsTheme theme) { - return ((CmsSwtTheme) theme).getIcon(name(), getBigIconSize()); - } - - default Integer getSmallIconSize() { - return 16; - } - - default Integer getBigIconSize() { - return 32; - } -} diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsStyles.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsStyles.java deleted file mode 100644 index 9eba6f6ec..000000000 --- a/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsStyles.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.argeo.cms.swt; - -/** Styles references in the CSS. */ -@Deprecated -public interface CmsStyles { - // General - public final static String CMS_SHELL = "cms_shell"; - public final static String CMS_MENU_LINK = "cms_menu_link"; - - // Header - public final static String CMS_HEADER = "cms_header"; - public final static String CMS_HEADER_LEAD = "cms_header-lead"; - public final static String CMS_HEADER_CENTER = "cms_header-center"; - public final static String CMS_HEADER_END = "cms_header-end"; - - public final static String CMS_LEAD = "cms_lead"; - public final static String CMS_END = "cms_end"; - public final static String CMS_FOOTER = "cms_footer"; - - public final static String CMS_USER_MENU = "cms_user_menu"; - public final static String CMS_USER_MENU_LINK = "cms_user_menu-link"; - public final static String CMS_USER_MENU_ITEM = "cms_user_menu-item"; - public final static String CMS_LOGIN_DIALOG = "cms_login_dialog"; - public final static String CMS_LOGIN_DIALOG_USERNAME = "cms_login_dialog-username"; - public final static String CMS_LOGIN_DIALOG_PASSWORD = "cms_login_dialog-password"; - - // Body - public final static String CMS_SCROLLED_AREA = "cms_scrolled_area"; - public final static String CMS_BODY = "cms_body"; - public final static String CMS_STATIC_TEXT = "cms_static-text"; - public final static String CMS_LINK = "cms_link"; -} diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtTheme.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtTheme.java deleted file mode 100644 index b5f7c0e4d..000000000 --- a/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtTheme.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.argeo.cms.swt; - -import org.argeo.api.cms.CmsTheme; -import org.eclipse.swt.graphics.Image; - -/** SWT specific {@link CmsTheme}. */ -public interface CmsSwtTheme extends CmsTheme { - /** The image registered at this path, or null if not found. */ - Image getImage(String path); - - /** - * And icon with this file name (without the extension), with a best effort to - * find the appropriate size, or null if not found. - * - * @param name An icon file name without path and extension. - * @param preferredSize the preferred size, if null, - * {@link #getDefaultIconSize()} will be tried. - */ - Image getIcon(String name, Integer preferredSize); - -} diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtUtils.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtUtils.java deleted file mode 100644 index a94d70706..000000000 --- a/org.argeo.cms.swt/src/org/argeo/cms/swt/CmsSwtUtils.java +++ /dev/null @@ -1,251 +0,0 @@ -package org.argeo.cms.swt; - -import java.util.HashMap; -import java.util.Map; - -import org.argeo.api.cms.CmsStyle; -import org.argeo.api.cms.CmsTheme; -import org.argeo.api.cms.CmsView; -import org.argeo.eclipse.ui.specific.EclipseUiSpecificUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.layout.FormAttachment; -import org.eclipse.swt.layout.FormData; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.layout.RowData; -import org.eclipse.swt.layout.RowLayout; -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.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; -import org.eclipse.swt.widgets.Widget; - -/** SWT utilities. */ -public class CmsSwtUtils { - - /** Singleton. */ - private CmsSwtUtils() { - } - - public static CmsTheme getCmsTheme(Composite parent) { - CmsTheme theme = (CmsTheme) parent.getData(CmsTheme.class.getName()); - if (theme == null) { - // find parent shell - Shell topShell = parent.getShell(); - while (topShell.getParent() != null) - topShell = (Shell) topShell.getParent(); - theme = (CmsTheme) topShell.getData(CmsTheme.class.getName()); - parent.setData(CmsTheme.class.getName(), theme); - } - return theme; - } - - public static void registerCmsTheme(Shell shell, CmsTheme theme) { - // find parent shell - Shell topShell = shell; - while (topShell.getParent() != null) - topShell = (Shell) topShell.getParent(); - // check if already set - if (topShell.getData(CmsTheme.class.getName()) != null) { - CmsTheme registeredTheme = (CmsTheme) topShell.getData(CmsTheme.class.getName()); - throw new IllegalArgumentException( - "Theme " + registeredTheme.getThemeId() + " already registered in this shell"); - } - topShell.setData(CmsTheme.class.getName(), theme); - } - - public static CmsView getCmsView(Control parent) { - // find parent shell - Shell topShell = parent.getShell(); - while (topShell.getParent() != null) - topShell = (Shell) topShell.getParent(); - return (CmsView) topShell.getData(CmsView.class.getName()); - } - - public static void registerCmsView(Shell shell, CmsView view) { - // find parent shell - Shell topShell = shell; - while (topShell.getParent() != null) - topShell = (Shell) topShell.getParent(); - // check if already set - if (topShell.getData(CmsView.class.getName()) != null) { - CmsView registeredView = (CmsView) topShell.getData(CmsView.class.getName()); - throw new IllegalArgumentException("Cms view " + registeredView + " already registered in this shell"); - } - shell.setData(CmsView.class.getName(), view); - } - - /** Sends an event via {@link CmsView#sendEvent(String, Map)}. */ - public static void sendEventOnSelect(Control control, String topic, Map properties) { - SelectionListener listener = (Selected) (e) -> { - getCmsView(control.getParent()).sendEvent(topic, properties); - }; - if (control instanceof Button) { - ((Button) control).addSelectionListener(listener); - } else - throw new UnsupportedOperationException("Control type " + control.getClass() + " is not supported."); - } - - /** - * Convenience method to sends an event via - * {@link CmsView#sendEvent(String, Map)}. - */ - public static void sendEventOnSelect(Control control, String topic, String key, Object value) { - Map properties = new HashMap<>(); - properties.put(key, value); - sendEventOnSelect(control, topic, properties); - } - - /* - * GRID LAYOUT - */ - public static GridLayout noSpaceGridLayout() { - return noSpaceGridLayout(new GridLayout()); - } - - public static GridLayout noSpaceGridLayout(int columns) { - return noSpaceGridLayout(new GridLayout(columns, false)); - } - - /** @return the same layout, with spaces removed. */ - public static GridLayout noSpaceGridLayout(GridLayout layout) { - layout.horizontalSpacing = 0; - layout.verticalSpacing = 0; - layout.marginWidth = 0; - layout.marginHeight = 0; - return layout; - } - - public static GridData fillAll() { - return new GridData(SWT.FILL, SWT.FILL, true, true); - } - - public static GridData fillWidth() { - return grabWidth(SWT.FILL, SWT.FILL); - } - - public static GridData grabWidth(int horizontalAlignment, int verticalAlignment) { - return new GridData(horizontalAlignment, horizontalAlignment, true, false); - } - - public static GridData fillHeight() { - return grabHeight(SWT.FILL, SWT.FILL); - } - - public static GridData grabHeight(int horizontalAlignment, int verticalAlignment) { - return new GridData(horizontalAlignment, horizontalAlignment, false, true); - } - - /* - * ROW LAYOUT - */ - /** @return the same layout, with margins removed. */ - public static RowLayout noMarginsRowLayout(RowLayout rowLayout) { - rowLayout.marginTop = 0; - rowLayout.marginBottom = 0; - rowLayout.marginLeft = 0; - rowLayout.marginRight = 0; - return rowLayout; - } - - public static RowLayout noMarginsRowLayout(int type) { - return noMarginsRowLayout(new RowLayout(type)); - } - - public static RowData rowData16px() { - return new RowData(16, 16); - } - - /* - * FORM LAYOUT - */ - public static FormData coverAll() { - FormData fdLabel = new FormData(); - fdLabel.top = new FormAttachment(0, 0); - fdLabel.left = new FormAttachment(0, 0); - fdLabel.right = new FormAttachment(100, 0); - fdLabel.bottom = new FormAttachment(100, 0); - return fdLabel; - } - - /* - * STYLING - */ - - /** Style widget */ - public static T style(T widget, String style) { - if (style == null) - return widget;// does nothing - EclipseUiSpecificUtils.setStyleData(widget, style); - if (widget instanceof Control) { - CmsView cmsView = getCmsView((Control) widget); - if (cmsView != null) - cmsView.applyStyles(widget); - } - return widget; - } - - /** Style widget */ - public static T style(T widget, CmsStyle style) { - return style(widget, style.style()); - } - - /** Enable markups on widget */ - public static T markup(T widget) { - EclipseUiSpecificUtils.setMarkupData(widget); - return widget; - } - - /** Disable markup validation. */ - public static T disableMarkupValidation(T widget) { - EclipseUiSpecificUtils.setMarkupValidationDisabledData(widget); - return widget; - } - - /** - * Apply markup and set text on {@link Label}, {@link Button}, {@link Text}. - * - * @param widget the widget to style and to use in order to display text - * @param txt the object to display via its toString() method. - * This argument should not be null, but if it is null and - * assertions are disabled "" is displayed instead; if - * assertions are enabled the call will fail. - * - * @see markup - */ - public static T text(T widget, Object txt) { - assert txt != null; - String str = txt != null ? txt.toString() : ""; - markup(widget); - if (widget instanceof Label) - ((Label) widget).setText(str); - else if (widget instanceof Button) - ((Button) widget).setText(str); - else if (widget instanceof Text) - ((Text) widget).setText(str); - else - throw new IllegalArgumentException("Unsupported widget type " + widget.getClass()); - return widget; - } - - /** A {@link Label} with markup activated. */ - public static Label lbl(Composite parent, Object txt) { - return text(new Label(parent, SWT.NONE), txt); - } - - /** A read-only {@link Text} whose content can be copy/pasted. */ - public static Text txt(Composite parent, Object txt) { - return text(new Text(parent, SWT.NONE), txt); - } - - /** Dispose all children of a Composite */ - public static void clear(Composite composite) { - if (composite.isDisposed()) - return; - for (Control child : composite.getChildren()) - child.dispose(); - } -} diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDoubleClick.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDoubleClick.java deleted file mode 100644 index b818b06d9..000000000 --- a/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDoubleClick.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.argeo.cms.swt; - -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.events.MouseListener; - -/** - * {@link MouseListener#mouseDoubleClick(MouseEvent)} as a functional interface - * in order to use as a short lambda expression in UI code. - * {@link MouseListener#mouseDownouseEvent)} and - * {@link MouseListener#mouseUp(MouseEvent)} do nothing by default. - */ -@FunctionalInterface -public interface MouseDoubleClick extends MouseListener { - @Override - void mouseDoubleClick(MouseEvent e); - - @Override - default void mouseDown(MouseEvent e) { - // does nothing - } - - @Override - default void mouseUp(MouseEvent e) { - // does nothing - } -} diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDown.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDown.java deleted file mode 100644 index baecb0072..000000000 --- a/org.argeo.cms.swt/src/org/argeo/cms/swt/MouseDown.java +++ /dev/null @@ -1,26 +0,0 @@ -package org.argeo.cms.swt; - -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.events.MouseListener; - -/** - * {@link MouseListener#mouseDown(MouseEvent)} as a functional interface in - * order to use as a short lambda expression in UI code. - * {@link MouseListener#mouseDoubleClick(MouseEvent)} and - * {@link MouseListener#mouseUp(MouseEvent)} do nothing by default. - */ -@FunctionalInterface -public interface MouseDown extends MouseListener { - @Override - void mouseDown(MouseEvent e); - - @Override - default void mouseDoubleClick(MouseEvent e) { - // does nothing - } - - @Override - default void mouseUp(MouseEvent e) { - // does nothing - } -} diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/Selected.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/Selected.java deleted file mode 100644 index 03fbad01e..000000000 --- a/org.argeo.cms.swt/src/org/argeo/cms/swt/Selected.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.argeo.cms.swt; - -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.SelectionListener; - -/** - * {@link SelectionListener} as a functional interface in order to use as a - * short lambda expression in UI code. - * {@link SelectionListener#widgetDefaultSelected(SelectionEvent)} does nothing - * by default. - */ -@FunctionalInterface -public interface Selected extends SelectionListener { - @Override - public void widgetSelected(SelectionEvent e); - - default public void widgetDefaultSelected(SelectionEvent e) { - // does nothing - } - -} diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/SimpleSwtUxContext.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/SimpleSwtUxContext.java deleted file mode 100644 index 9c55e8b10..000000000 --- a/org.argeo.cms.swt/src/org/argeo/cms/swt/SimpleSwtUxContext.java +++ /dev/null @@ -1,50 +0,0 @@ -package org.argeo.cms.swt; - -import org.argeo.api.cms.UxContext; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.graphics.Rectangle; -import org.eclipse.swt.widgets.Display; - -public class SimpleSwtUxContext implements UxContext { - private Point size; - private Point small = new Point(400, 400); - - public SimpleSwtUxContext() { - this(Display.getCurrent().getBounds()); - } - - public SimpleSwtUxContext(Rectangle rect) { - this.size = new Point(rect.width, rect.height); - } - - public SimpleSwtUxContext(Point size) { - this.size = size; - } - - @Override - public boolean isPortrait() { - return size.x >= size.y; - } - - @Override - public boolean isLandscape() { - return size.x < size.y; - } - - @Override - public boolean isSquare() { - return size.x == size.y; - } - - @Override - public boolean isSmall() { - return size.x <= small.x || size.y <= small.y; - } - - @Override - public boolean isMasterData() { - // TODO make it configurable - return true; - } - -} diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java deleted file mode 100644 index 9c8680c4d..000000000 --- a/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLogin.java +++ /dev/null @@ -1,336 +0,0 @@ -package org.argeo.cms.swt.auth; - -import static org.argeo.cms.CmsMsg.password; -import static org.argeo.cms.CmsMsg.username; - -import java.io.IOException; -import java.util.List; -import java.util.Locale; - -import javax.security.auth.Subject; -import javax.security.auth.callback.Callback; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.callback.LanguageCallback; -import javax.security.auth.callback.NameCallback; -import javax.security.auth.callback.PasswordCallback; -import javax.security.auth.callback.UnsupportedCallbackException; -import javax.security.auth.login.LoginContext; -import javax.security.auth.login.LoginException; - -import org.argeo.api.cms.CmsAuth; -import org.argeo.api.cms.CmsContext; -import org.argeo.api.cms.CmsLog; -import org.argeo.api.cms.CmsView; -import org.argeo.cms.CmsMsg; -import org.argeo.cms.LocaleUtils; -import org.argeo.cms.auth.RemoteAuthCallback; -import org.argeo.cms.servlet.ServletHttpRequest; -import org.argeo.cms.servlet.ServletHttpResponse; -import org.argeo.cms.swt.CmsStyles; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.eclipse.ui.specific.UiContext; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.MouseAdapter; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.events.TraverseEvent; -import org.eclipse.swt.events.TraverseListener; -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.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; - -public class CmsLogin implements CmsStyles, CallbackHandler { - private final static CmsLog log = CmsLog.getLog(CmsLogin.class); - - private Composite parent; - private Text usernameT, passwordT; - private Composite credentialsBlock; - private final SelectionListener loginSelectionListener; - - private final Locale defaultLocale; - private LocaleChoice localeChoice = null; - - private final CmsView cmsView; - - // optional subject to be set explicitly - private Subject subject = null; - - public CmsLogin(CmsView cmsView) { - this.cmsView = cmsView; - CmsContext nodeState = null;// = Activator.getNodeState(); - // FIXME reactivate locales - if (nodeState != null) { - defaultLocale = nodeState.getDefaultLocale(); - List locales = nodeState.getLocales(); - if (locales != null) - localeChoice = new LocaleChoice(locales, defaultLocale); - } else { - defaultLocale = Locale.getDefault(); - } - loginSelectionListener = new SelectionListener() { - private static final long serialVersionUID = -8832133363830973578L; - - @Override - public void widgetSelected(SelectionEvent e) { - login(); - } - - @Override - public void widgetDefaultSelected(SelectionEvent e) { - } - }; - } - - protected boolean isAnonymous() { - return cmsView.isAnonymous(); - } - - public final void createUi(Composite parent) { - this.parent = parent; - createContents(parent); - } - - protected void createContents(Composite parent) { - defaultCreateContents(parent); - } - - public final void defaultCreateContents(Composite parent) { - parent.setLayout(CmsSwtUtils.noSpaceGridLayout()); - Composite credentialsBlock = createCredentialsBlock(parent); - if (parent instanceof Shell) { - credentialsBlock.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true)); - } - } - - public final Composite createCredentialsBlock(Composite parent) { - if (isAnonymous()) { - return anonymousUi(parent); - } else { - return userUi(parent); - } - } - - public Composite getCredentialsBlock() { - return credentialsBlock; - } - - protected Composite userUi(Composite parent) { - Locale locale = localeChoice == null ? this.defaultLocale : localeChoice.getSelectedLocale(); - credentialsBlock = new Composite(parent, SWT.NONE); - credentialsBlock.setLayout(new GridLayout()); - // credentialsBlock.setLayoutData(CmsUiUtils.fillAll()); - - specificUserUi(credentialsBlock); - - Label l = new Label(credentialsBlock, SWT.NONE); - CmsSwtUtils.style(l, CMS_USER_MENU_ITEM); - l.setText(CmsMsg.logout.lead(locale)); - GridData lData = CmsSwtUtils.fillWidth(); - lData.widthHint = 120; - l.setLayoutData(lData); - - l.addMouseListener(new MouseAdapter() { - private static final long serialVersionUID = 6444395812777413116L; - - public void mouseDown(MouseEvent e) { - logout(); - } - }); - return credentialsBlock; - } - - /** To be overridden */ - protected void specificUserUi(Composite parent) { - - } - - protected Composite anonymousUi(Composite parent) { - Locale locale = localeChoice == null ? this.defaultLocale : localeChoice.getSelectedLocale(); - // We need a composite for the traversal - credentialsBlock = new Composite(parent, SWT.NONE); - credentialsBlock.setLayout(new GridLayout()); - // credentialsBlock.setLayoutData(CmsUiUtils.fillAll()); - CmsSwtUtils.style(credentialsBlock, CMS_LOGIN_DIALOG); - - Integer textWidth = 120; - if (parent instanceof Shell) - CmsSwtUtils.style(parent, CMS_USER_MENU); - // new Label(this, SWT.NONE).setText(CmsMsg.username.lead()); - usernameT = new Text(credentialsBlock, SWT.BORDER); - usernameT.setMessage(username.lead(locale)); - CmsSwtUtils.style(usernameT, CMS_LOGIN_DIALOG_USERNAME); - GridData gd = CmsSwtUtils.fillWidth(); - gd.widthHint = textWidth; - usernameT.setLayoutData(gd); - - // new Label(this, SWT.NONE).setText(CmsMsg.password.lead()); - passwordT = new Text(credentialsBlock, SWT.BORDER | SWT.PASSWORD); - passwordT.setMessage(password.lead(locale)); - CmsSwtUtils.style(passwordT, CMS_LOGIN_DIALOG_PASSWORD); - gd = CmsSwtUtils.fillWidth(); - gd.widthHint = textWidth; - passwordT.setLayoutData(gd); - - TraverseListener tl = new TraverseListener() { - private static final long serialVersionUID = -1158892811534971856L; - - public void keyTraversed(TraverseEvent e) { - if (e.detail == SWT.TRAVERSE_RETURN) - login(); - } - }; - credentialsBlock.addTraverseListener(tl); - usernameT.addTraverseListener(tl); - passwordT.addTraverseListener(tl); - parent.setTabList(new Control[] { credentialsBlock }); - credentialsBlock.setTabList(new Control[] { usernameT, passwordT }); - - // Button - Button loginButton = new Button(credentialsBlock, SWT.PUSH); - loginButton.setText(CmsMsg.login.lead(locale)); - loginButton.setLayoutData(CmsSwtUtils.fillWidth()); - loginButton.addSelectionListener(loginSelectionListener); - - extendsCredentialsBlock(credentialsBlock, locale, loginSelectionListener); - if (localeChoice != null) - createLocalesBlock(credentialsBlock); - return credentialsBlock; - } - - /** - * To be overridden in order to provide custom login button and other links. - */ - protected void extendsCredentialsBlock(Composite credentialsBlock, Locale selectedLocale, - SelectionListener loginSelectionListener) { - - } - - protected void updateLocale(Locale selectedLocale) { - // save already entered values - String usernameStr = usernameT.getText(); - char[] pwd = passwordT.getTextChars(); - - for (Control child : parent.getChildren()) - child.dispose(); - createContents(parent); - if (parent.getParent() != null) - parent.getParent().layout(true, true); - else - parent.layout(); - usernameT.setText(usernameStr); - passwordT.setTextChars(pwd); - } - - protected Composite createLocalesBlock(final Composite parent) { - Composite c = new Composite(parent, SWT.NONE); - CmsSwtUtils.style(c, CMS_USER_MENU_ITEM); - c.setLayout(CmsSwtUtils.noSpaceGridLayout()); - c.setLayoutData(CmsSwtUtils.fillAll()); - - SelectionListener selectionListener = new SelectionAdapter() { - private static final long serialVersionUID = 4891637813567806762L; - - public void widgetSelected(SelectionEvent event) { - Button button = (Button) event.widget; - if (button.getSelection()) { - localeChoice.setSelectedIndex((Integer) event.widget.getData()); - updateLocale(localeChoice.getSelectedLocale()); - } - }; - }; - - List locales = localeChoice.getLocales(); - for (Integer i = 0; i < locales.size(); i++) { - Locale locale = locales.get(i); - Button button = new Button(c, SWT.RADIO); - CmsSwtUtils.style(button, CMS_USER_MENU_ITEM); - button.setData(i); - button.setText(LocaleUtils.toLead(locale.getDisplayName(locale), locale) + " (" + locale + ")"); - // button.addListener(SWT.Selection, listener); - button.addSelectionListener(selectionListener); - if (i == localeChoice.getSelectedIndex()) - button.setSelection(true); - } - return c; - } - - protected boolean login() { - // TODO use CmsVie in order to retrieve subject? - // Subject subject = cmsView.getLoginContext().getSubject(); - // LoginContext loginContext = cmsView.getLoginContext(); - try { - // - // LOGIN - // - // loginContext.logout(); - LoginContext loginContext; - if (subject == null) - loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, this); - else - loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, subject, this); - loginContext.login(); - cmsView.authChange(loginContext); - return true; - } catch (LoginException e) { - if (log.isTraceEnabled()) - log.warn("Login failed: " + e.getMessage(), e); - else - log.warn("Login failed: " + e.getMessage()); - - try { - Thread.sleep(3000); - } catch (InterruptedException e2) { - // silent - } - // ErrorFeedback.show("Login failed", e); - return false; - } - // catch (LoginException e) { - // log.error("Cannot login", e); - // return false; - // } - } - - protected void logout() { - cmsView.logout(); - cmsView.navigateTo("~"); - } - - @Override - public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { - for (Callback callback : callbacks) { - if (callback instanceof NameCallback && usernameT != null) - ((NameCallback) callback).setName(usernameT.getText()); - else if (callback instanceof PasswordCallback && passwordT != null) - ((PasswordCallback) callback).setPassword(passwordT.getTextChars()); - else if (callback instanceof RemoteAuthCallback) { - ((RemoteAuthCallback) callback).setRequest(new ServletHttpRequest(UiContext.getHttpRequest())); - ((RemoteAuthCallback) callback).setResponse(new ServletHttpResponse(UiContext.getHttpResponse())); - } else if (callback instanceof LanguageCallback) { - Locale toUse = null; - if (localeChoice != null) - toUse = localeChoice.getSelectedLocale(); - else if (defaultLocale != null) - toUse = defaultLocale; - - if (toUse != null) { - ((LanguageCallback) callback).setLocale(toUse); - UiContext.setLocale(toUse); - } - - } - } - } - - public void setSubject(Subject subject) { - this.subject = subject; - } - -} diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLoginShell.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLoginShell.java deleted file mode 100644 index f6a35f136..000000000 --- a/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CmsLoginShell.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.argeo.cms.swt.auth; - -import org.argeo.api.cms.CmsView; -import org.argeo.cms.swt.CmsSwtUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; - -/** The site-related user menu */ -public class CmsLoginShell extends CmsLogin { - private final Shell shell; - - public CmsLoginShell(CmsView cmsView) { - super(cmsView); - shell = createShell(); -// createUi(shell); - } - - /** To be overridden. */ - protected Shell createShell() { - Shell shell = new Shell(Display.getCurrent(), SWT.NO_TRIM); - shell.setMaximized(true); - return shell; - } - - /** To be overridden. */ - public void open() { - CmsSwtUtils.style(shell, CMS_USER_MENU); - shell.open(); - } - - @Override - protected boolean login() { - boolean success = false; - try { - success = super.login(); - return success; - } finally { - if (success) - closeShell(); - else { - for (Control child : shell.getChildren()) - child.dispose(); - createUi(shell); - shell.layout(); - // TODO error message - } - } - } - - @Override - protected void logout() { - closeShell(); - super.logout(); - } - - protected void closeShell() { - if (!shell.isDisposed()) { - shell.close(); - shell.dispose(); - } - } - - public Shell getShell() { - return shell; - } - - public void createUi(){ - createUi(shell); - } -} diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CompositeCallbackHandler.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CompositeCallbackHandler.java deleted file mode 100644 index 495007cb2..000000000 --- a/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/CompositeCallbackHandler.java +++ /dev/null @@ -1,273 +0,0 @@ -package org.argeo.cms.swt.auth; - -import java.io.IOException; -import java.util.Arrays; - -import javax.security.auth.callback.Callback; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.callback.NameCallback; -import javax.security.auth.callback.PasswordCallback; -import javax.security.auth.callback.TextOutputCallback; -import javax.security.auth.callback.UnsupportedCallbackException; - -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.events.SelectionEvent; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Combo; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; - -/** - * A composite that can populate itself based on {@link Callback}s. It can be - * used directly as a {@link CallbackHandler} or be used by one by calling the - * {@link #createCallbackHandlers(Callback[])}. Supported standard - * {@link Callback}s are:
- *

    - *
  • {@link PasswordCallback}
  • - *
  • {@link NameCallback}
  • - *
  • {@link TextOutputCallback}
  • - *
- * Supported Argeo {@link Callback}s are:
- *
    - *
  • {@link LocaleChoice}
  • - *
- */ -public class CompositeCallbackHandler extends Composite implements CallbackHandler { - private static final long serialVersionUID = -928223893722723777L; - - private boolean wasUsedAlready = false; - private boolean isSubmitted = false; - private boolean isCanceled = false; - - public CompositeCallbackHandler(Composite parent, int style) { - super(parent, style); - } - - @Override - public synchronized void handle(final Callback[] callbacks) throws IOException, UnsupportedCallbackException { - // reset - if (wasUsedAlready && !isSubmitted() && !isCanceled()) { - cancel(); - for (Control control : getChildren()) - control.dispose(); - isSubmitted = false; - isCanceled = false; - } - - for (Callback callback : callbacks) - checkCallbackSupported(callback); - // create controls synchronously in the UI thread - getDisplay().syncExec(new Runnable() { - - @Override - public void run() { - createCallbackHandlers(callbacks); - } - }); - - if (!wasUsedAlready) - wasUsedAlready = true; - - // while (!isSubmitted() && !isCanceled()) { - // try { - // wait(1000l); - // } catch (InterruptedException e) { - // // silent - // } - // } - - // cleanCallbacksAfterCancel(callbacks); - } - - public void checkCallbackSupported(Callback callback) throws UnsupportedCallbackException { - if (callback instanceof TextOutputCallback || callback instanceof NameCallback - || callback instanceof PasswordCallback || callback instanceof LocaleChoice) { - return; - } else { - throw new UnsupportedCallbackException(callback); - } - } - - /** - * Set writable callbacks to null if the handle is canceled (check is done - * by the method) - */ - public void cleanCallbacksAfterCancel(Callback[] callbacks) { - if (isCanceled()) { - for (Callback callback : callbacks) { - if (callback instanceof NameCallback) { - ((NameCallback) callback).setName(null); - } else if (callback instanceof PasswordCallback) { - PasswordCallback pCallback = (PasswordCallback) callback; - char[] arr = pCallback.getPassword(); - if (arr != null) { - Arrays.fill(arr, '*'); - pCallback.setPassword(null); - } - } - } - } - } - - public void createCallbackHandlers(Callback[] callbacks) { - Composite composite = this; - for (int i = 0; i < callbacks.length; i++) { - Callback callback = callbacks[i]; - if (callback instanceof TextOutputCallback) { - createLabelTextoutputHandler(composite, (TextOutputCallback) callback); - } else if (callback instanceof NameCallback) { - createNameHandler(composite, (NameCallback) callback); - } else if (callback instanceof PasswordCallback) { - createPasswordHandler(composite, (PasswordCallback) callback); - } else if (callback instanceof LocaleChoice) { - createLocaleHandler(composite, (LocaleChoice) callback); - } - } - } - - protected Text createNameHandler(Composite composite, final NameCallback callback) { - Label label = new Label(composite, SWT.NONE); - label.setText(callback.getPrompt()); - final Text text = new Text(composite, SWT.SINGLE | SWT.LEAD | SWT.BORDER); - if (callback.getDefaultName() != null) { - // set default value, if provided - text.setText(callback.getDefaultName()); - callback.setName(callback.getDefaultName()); - } - text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - text.addModifyListener(new ModifyListener() { - private static final long serialVersionUID = 7300032545287292973L; - - public void modifyText(ModifyEvent event) { - callback.setName(text.getText()); - } - }); - text.addSelectionListener(new SelectionListener() { - private static final long serialVersionUID = 1820530045857665111L; - - @Override - public void widgetSelected(SelectionEvent e) { - } - - @Override - public void widgetDefaultSelected(SelectionEvent e) { - submit(); - } - }); - - text.addKeyListener(new KeyListener() { - private static final long serialVersionUID = -8698107785092095713L; - - @Override - public void keyReleased(KeyEvent e) { - } - - @Override - public void keyPressed(KeyEvent e) { - } - }); - return text; - } - - protected Text createPasswordHandler(Composite composite, final PasswordCallback callback) { - Label label = new Label(composite, SWT.NONE); - label.setText(callback.getPrompt()); - final Text passwordText = new Text(composite, SWT.SINGLE | SWT.LEAD | SWT.PASSWORD | SWT.BORDER); - passwordText.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - passwordText.addModifyListener(new ModifyListener() { - private static final long serialVersionUID = -7099363995047686732L; - - public void modifyText(ModifyEvent event) { - callback.setPassword(passwordText.getTextChars()); - } - }); - passwordText.addSelectionListener(new SelectionListener() { - private static final long serialVersionUID = 1820530045857665111L; - - @Override - public void widgetSelected(SelectionEvent e) { - } - - @Override - public void widgetDefaultSelected(SelectionEvent e) { - submit(); - } - }); - return passwordText; - } - - protected Combo createLocaleHandler(Composite composite, final LocaleChoice callback) { - String[] labels = callback.getSupportedLocalesLabels(); - if (labels.length == 0) - return null; - Label label = new Label(composite, SWT.NONE); - label.setText("Language"); - - final Combo combo = new Combo(composite, SWT.READ_ONLY); - combo.setItems(labels); - combo.select(callback.getDefaultIndex()); - combo.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - combo.addSelectionListener(new SelectionListener() { - private static final long serialVersionUID = 38678989091946277L; - - @Override - public void widgetSelected(SelectionEvent e) { - callback.setSelectedIndex(combo.getSelectionIndex()); - } - - @Override - public void widgetDefaultSelected(SelectionEvent e) { - } - }); - return combo; - } - - protected Label createLabelTextoutputHandler(Composite composite, final TextOutputCallback callback) { - Label label = new Label(composite, SWT.NONE); - label.setText(callback.getMessage()); - GridData data = new GridData(SWT.FILL, SWT.FILL, true, true); - data.horizontalSpan = 2; - label.setLayoutData(data); - return label; - // TODO: find a way to pass this information - // int messageType = callback.getMessageType(); - // int dialogMessageType = IMessageProvider.NONE; - // switch (messageType) { - // case TextOutputCallback.INFORMATION: - // dialogMessageType = IMessageProvider.INFORMATION; - // break; - // case TextOutputCallback.WARNING: - // dialogMessageType = IMessageProvider.WARNING; - // break; - // case TextOutputCallback.ERROR: - // dialogMessageType = IMessageProvider.ERROR; - // break; - // } - // setMessage(callback.getMessage(), dialogMessageType); - } - - synchronized boolean isSubmitted() { - return isSubmitted; - } - - synchronized boolean isCanceled() { - return isCanceled; - } - - protected synchronized void submit() { - isSubmitted = true; - notifyAll(); - } - - protected synchronized void cancel() { - isCanceled = true; - notifyAll(); - } -} diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/DynamicCallbackHandler.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/DynamicCallbackHandler.java deleted file mode 100644 index b0c36c602..000000000 --- a/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/DynamicCallbackHandler.java +++ /dev/null @@ -1,34 +0,0 @@ -package org.argeo.cms.swt.auth; - -import java.io.IOException; - -import javax.security.auth.callback.Callback; -import javax.security.auth.callback.CallbackHandler; -import javax.security.auth.callback.UnsupportedCallbackException; - -import org.argeo.eclipse.ui.dialogs.LightweightDialog; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; - -public class DynamicCallbackHandler implements CallbackHandler { - - @Override - public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { - Shell activeShell = Display.getCurrent().getActiveShell(); - LightweightDialog dialog = new LightweightDialog(activeShell) { - - @Override - protected Control createDialogArea(Composite parent) { - CompositeCallbackHandler cch = new CompositeCallbackHandler(parent, SWT.NONE); - cch.createCallbackHandlers(callbacks); - return cch; - } - }; - dialog.setBlockOnOpen(true); - dialog.open(); - } - -} diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/LocaleChoice.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/LocaleChoice.java deleted file mode 100644 index e98e390ee..000000000 --- a/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/LocaleChoice.java +++ /dev/null @@ -1,86 +0,0 @@ -package org.argeo.cms.swt.auth; - -import java.util.Collections; -import java.util.List; -import java.util.Locale; - -import javax.security.auth.callback.LanguageCallback; - -import org.argeo.cms.CmsException; -import org.argeo.cms.LocaleUtils; - -/** Choose in a list of locales. TODO: replace with {@link LanguageCallback} */ -public class LocaleChoice { - private final List locales; - - private Integer selectedIndex = null; - private final Integer defaultIndex; - - public LocaleChoice(List locales, Locale defaultLocale) { - Integer defaultIndex = null; - this.locales = Collections.unmodifiableList(locales); - for (int i = 0; i < locales.size(); i++) - if (locales.get(i).equals(defaultLocale)) - defaultIndex = i; - - // based on language only - if (defaultIndex == null) - for (int i = 0; i < locales.size(); i++) - if (locales.get(i).getLanguage().equals(defaultLocale.getLanguage())) - defaultIndex = i; - - if (defaultIndex == null) - throw new CmsException("Default locale " + defaultLocale + " is not in available locales " + locales); - this.defaultIndex = defaultIndex; - - this.selectedIndex = defaultIndex; - } - - /** - * Convenience constructor based on a comma separated list of iso codes (en, - * en_US, fr_CA, etc.). Default selection is default locale. - */ - public LocaleChoice(String locales, Locale defaultLocale) { - this(LocaleUtils.asLocaleList(locales), defaultLocale); - } - - public String[] getSupportedLocalesLabels() { - String[] labels = new String[locales.size()]; - for (int i = 0; i < locales.size(); i++) { - Locale locale = locales.get(i); - if (locale.getCountry().equals("")) - labels[i] = locale.getDisplayLanguage(locale) + " [" + locale.getLanguage() + "]"; - else - labels[i] = locale.getDisplayLanguage(locale) + " (" + locale.getDisplayCountry(locale) + ") [" - + locale.getLanguage() + "_" + locale.getCountry() + "]"; - - } - return labels; - } - - public Locale getSelectedLocale() { - if (selectedIndex == null) - return null; - return locales.get(selectedIndex); - } - - public void setSelectedIndex(Integer selectedIndex) { - this.selectedIndex = selectedIndex; - } - - public Integer getSelectedIndex() { - return selectedIndex; - } - - public Integer getDefaultIndex() { - return defaultIndex; - } - - public List getLocales() { - return locales; - } - - public Locale getDefaultLocale() { - return locales.get(getDefaultIndex()); - } -} diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/package-info.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/package-info.java deleted file mode 100644 index b431423d8..000000000 --- a/org.argeo.cms.swt/src/org/argeo/cms/swt/auth/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Argeo CMS authentication widgets, based on SWT. */ -package org.argeo.cms.swt.auth; \ No newline at end of file diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/ChangePasswordDialog.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/ChangePasswordDialog.java deleted file mode 100644 index 8ff086283..000000000 --- a/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/ChangePasswordDialog.java +++ /dev/null @@ -1,83 +0,0 @@ -package org.argeo.cms.swt.dialogs; - -import java.security.PrivilegedAction; -import java.util.Arrays; - -import org.argeo.api.cms.CmsView; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.CmsMsg; -import org.argeo.cms.CmsUserManager; -import org.argeo.cms.swt.CmsSwtUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; - -/** Dialog to change a password. */ -public class ChangePasswordDialog extends CmsMessageDialog { - private final static CmsLog log = CmsLog.getLog(ChangePasswordDialog.class); - - private CmsUserManager cmsUserManager; - private CmsView cmsView; - - private PrivilegedAction doIt; - - public ChangePasswordDialog(Shell parentShell, String message, int kind, CmsUserManager cmsUserManager) { - super(parentShell, message, kind); - this.cmsUserManager = cmsUserManager; - cmsView = CmsSwtUtils.getCmsView(parentShell); - } - - @Override - protected Control createInputArea(Composite userSection) { - addFormLabel(userSection, CmsMsg.currentPassword.lead()); - Text previousPassword = new Text(userSection, SWT.BORDER | SWT.PASSWORD); - previousPassword.setLayoutData(CmsSwtUtils.fillWidth()); - addFormLabel(userSection, CmsMsg.newPassword.lead()); - Text newPassword = new Text(userSection, SWT.BORDER | SWT.PASSWORD); - newPassword.setLayoutData(CmsSwtUtils.fillWidth()); - addFormLabel(userSection, CmsMsg.repeatNewPassword.lead()); - Text confirmPassword = new Text(userSection, SWT.BORDER | SWT.PASSWORD); - confirmPassword.setLayoutData(CmsSwtUtils.fillWidth()); - - doIt = () -> { - if (Arrays.equals(newPassword.getTextChars(), confirmPassword.getTextChars())) { - try { - cmsUserManager.changeOwnPassword(previousPassword.getTextChars(), newPassword.getTextChars()); - return OK; - } catch (Exception e1) { - log.error("Could not change password", e1); - cancel(); - CmsMessageDialog.openError(CmsMsg.invalidPassword.lead()); - return CANCEL; - } - } else { - cancel(); - CmsMessageDialog.openError(CmsMsg.repeatNewPassword.lead()); - return CANCEL; - } - }; - - pack(); - return previousPassword; - } - - @Override - protected void okPressed() { - Integer returnCode = cmsView.doAs(doIt); - if (returnCode.equals(OK)) { - super.okPressed(); - CmsMessageDialog.openInformation(CmsMsg.passwordChanged.lead()); - } - } - - private static Label addFormLabel(Composite parent, String label) { - Label lbl = new Label(parent, SWT.WRAP); - lbl.setText(label); -// CmsUiUtils.style(lbl, SuiteStyle.simpleLabel); - return lbl; - } - -} diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsFeedback.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsFeedback.java deleted file mode 100644 index a01c919e9..000000000 --- a/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsFeedback.java +++ /dev/null @@ -1,100 +0,0 @@ -package org.argeo.cms.swt.dialogs; - -import java.io.PrintWriter; -import java.io.StringWriter; - -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.CmsMsg; -import org.argeo.cms.swt.Selected; -import org.eclipse.swt.SWT; -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.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; - -/** A dialog feedback based on a {@link LightweightDialog}. */ -public class CmsFeedback extends LightweightDialog { - private final static CmsLog log = CmsLog.getLog(CmsFeedback.class); - - private String message; - private Throwable exception; - - public CmsFeedback(Shell parentShell, String message, Throwable e) { - super(parentShell); - this.message = message; - this.exception = e; - log.error(message, e); - } - - public static CmsFeedback show(String message, Throwable e) { - // rethrow ThreaDeath in order to make sure that RAP will properly clean - // up the UI thread - if (e instanceof ThreadDeath) - throw (ThreadDeath) e; - - try { - CmsFeedback cmsFeedback = new CmsFeedback(null, message, e); - cmsFeedback.setBlockOnOpen(false); - cmsFeedback.open(); - return cmsFeedback; - } catch (Throwable e1) { - log.error("Cannot open error feedback (" + e.getMessage() + "), original error below", e); - return null; - } - } - - public static CmsFeedback show(String message) { - CmsFeedback cmsFeedback = new CmsFeedback(null, message, null); - cmsFeedback.open(); - return cmsFeedback; - } - - /** Tries to find a display */ - // private static Display getDisplay() { - // try { - // Display display = Display.getCurrent(); - // if (display != null) - // return display; - // else - // return Display.getDefault(); - // } catch (Exception e) { - // return Display.getCurrent(); - // } - // } - - protected Control createDialogArea(Composite parent) { - parent.setLayout(new GridLayout(2, false)); - - Label messageLbl = new Label(parent, SWT.WRAP); - if (message != null) - messageLbl.setText(message); - else if (exception != null) - messageLbl.setText(exception.getLocalizedMessage()); - - Button close = new Button(parent, SWT.FLAT); - close.setText(CmsMsg.close.lead()); - close.setLayoutData(new GridData(SWT.END, SWT.TOP, false, false)); - close.addSelectionListener((Selected) (e) -> closeShell(OK)); - - // Composite composite = new Composite(dialogarea, SWT.NONE); - // composite.setLayout(new GridLayout(2, false)); - // composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - - if (exception != null) { - Text stack = new Text(parent, SWT.MULTI | SWT.LEAD | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL); - stack.setEditable(false); - stack.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 2, 1)); - StringWriter sw = new StringWriter(); - exception.printStackTrace(new PrintWriter(sw)); - stack.setText(sw.toString()); - } - - // parent.pack(); - return messageLbl; - } - -} diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsMessageDialog.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsMessageDialog.java deleted file mode 100644 index 66e640595..000000000 --- a/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsMessageDialog.java +++ /dev/null @@ -1,167 +0,0 @@ -package org.argeo.cms.swt.dialogs; - -import org.argeo.cms.CmsMsg; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.swt.Selected; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.TraverseEvent; -import org.eclipse.swt.events.TraverseListener; -import org.eclipse.swt.graphics.Point; -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.Display; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Shell; - -/** Base class for dialogs displaying messages or small forms. */ -public class CmsMessageDialog extends LightweightDialog { - public final static int NONE = 0; - public final static int ERROR = 1; - public final static int INFORMATION = 2; - public final static int QUESTION = 3; - public final static int WARNING = 4; - public final static int CONFIRM = 5; - public final static int QUESTION_WITH_CANCEL = 6; - - private int kind; - private String message; - - public CmsMessageDialog(Shell parentShell, String message, int kind) { - super(parentShell); - this.kind = kind; - this.message = message; - } - - protected Control createDialogArea(Composite parent) { - parent.setLayout(new GridLayout()); - - TraverseListener traverseListener = new TraverseListener() { - private static final long serialVersionUID = -1158892811534971856L; - - public void keyTraversed(TraverseEvent e) { - if (e.detail == SWT.TRAVERSE_RETURN) - okPressed(); - else if (e.detail == SWT.TRAVERSE_ESCAPE) - cancelPressed(); - } - }; - - // message - Composite body = new Composite(parent, SWT.NONE); - body.addTraverseListener(traverseListener); - GridLayout bodyGridLayout = new GridLayout(); - bodyGridLayout.marginHeight = 20; - bodyGridLayout.marginWidth = 20; - body.setLayout(bodyGridLayout); - body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - - if (message != null) { - Label messageLbl = new Label(body, SWT.WRAP); - CmsSwtUtils.markup(messageLbl); - messageLbl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); - messageLbl.setFont(EclipseUiUtils.getBoldFont(parent)); - messageLbl.setText(message); - } - - // buttons - Composite buttons = new Composite(parent, SWT.NONE); - buttons.addTraverseListener(traverseListener); - buttons.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false)); - if (kind == INFORMATION || kind == WARNING || kind == ERROR || kind == ERROR) { - GridLayout layout = new GridLayout(1, true); - layout.marginWidth = 0; - layout.marginHeight = 0; - buttons.setLayout(layout); - - Button close = new Button(buttons, SWT.FLAT); - close.setText(CmsMsg.close.lead()); - close.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); - close.addSelectionListener((Selected) (e) -> closeShell(OK)); - close.setFocus(); - close.addTraverseListener(traverseListener); - - buttons.setTabList(new Control[] { close }); - } else if (kind == CONFIRM || kind == QUESTION || kind == QUESTION_WITH_CANCEL) { - Control input = createInputArea(body); - if (input != null) { - input.addTraverseListener(traverseListener); - body.setTabList(new Control[] { input }); - } - GridLayout layout = new GridLayout(2, true); - layout.marginWidth = 0; - layout.marginHeight = 0; - buttons.setLayout(layout); - - Button cancel = new Button(buttons, SWT.FLAT); - cancel.setText(CmsMsg.cancel.lead()); - cancel.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); - cancel.addSelectionListener((Selected) (e) -> cancelPressed()); - cancel.addTraverseListener(traverseListener); - - Button ok = new Button(buttons, SWT.FLAT); - ok.setText(CmsMsg.ok.lead()); - ok.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); - ok.addSelectionListener((Selected) (e) -> okPressed()); - ok.addTraverseListener(traverseListener); - if (input == null) - ok.setFocus(); - else - input.setFocus(); - - buttons.setTabList(new Control[] { ok, cancel }); - } - // pack(); - parent.setTabList(new Control[] { body, buttons }); - return body; - } - - protected Control createInputArea(Composite parent) { - return null; - } - - protected void okPressed() { - closeShell(OK); - } - - protected void cancelPressed() { - closeShell(CANCEL); - } - - protected void cancel() { - closeShell(CANCEL); - } - - protected Point getInitialSize() { - return new Point(400, 200); - } - - public static boolean open(int kind, Shell parent, String message) { - CmsMessageDialog dialog = new CmsMessageDialog(parent, message, kind); - return dialog.open() == 0; - } - - public static boolean openConfirm(String message) { - return open(CONFIRM, Display.getCurrent().getActiveShell(), message); - } - - public static void openInformation(String message) { - open(INFORMATION, Display.getCurrent().getActiveShell(), message); - } - - public static boolean openQuestion(String message) { - return open(QUESTION, Display.getCurrent().getActiveShell(), message); - } - - public static void openWarning(String message) { - open(WARNING, Display.getCurrent().getActiveShell(), message); - } - - public static void openError(String message) { - open(ERROR, Display.getCurrent().getActiveShell(), message); - } - -} diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsWizardDialog.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsWizardDialog.java deleted file mode 100644 index 59d9ab7f5..000000000 --- a/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/CmsWizardDialog.java +++ /dev/null @@ -1,220 +0,0 @@ -package org.argeo.cms.swt.dialogs; - -import java.lang.reflect.InvocationTargetException; - -import org.argeo.cms.CmsMsg; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.swt.Selected; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.eclipse.jface.operation.IRunnableWithProgress; -import org.eclipse.jface.wizard.IWizard; -import org.eclipse.jface.wizard.IWizardContainer2; -import org.eclipse.jface.wizard.IWizardPage; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.FormAttachment; -import org.eclipse.swt.layout.FormData; -import org.eclipse.swt.layout.FormLayout; -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.eclipse.swt.widgets.Shell; - -/** A wizard dialog based on {@link LightweightDialog}. */ -public class CmsWizardDialog extends LightweightDialog implements IWizardContainer2 { - private static final long serialVersionUID = -2123153353654812154L; - - private IWizard wizard; - private IWizardPage currentPage; - private int currentPageIndex; - - private Label titleBar; - private Label message; - private Composite[] pageBodies; - private Composite buttons; - private Button back; - private Button next; - private Button finish; - - public CmsWizardDialog(Shell parentShell, IWizard wizard) { - super(parentShell); - this.wizard = wizard; - wizard.setContainer(this); - // create the pages - wizard.addPages(); - currentPage = wizard.getStartingPage(); - if (currentPage == null) - throw new IllegalArgumentException("At least one wizard page is required"); - } - - @Override - protected Control createDialogArea(Composite parent) { - updateWindowTitle(); - - Composite messageArea = new Composite(parent, SWT.NONE); - messageArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); - { - messageArea.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(2, false))); - titleBar = new Label(messageArea, SWT.WRAP); - titleBar.setFont(EclipseUiUtils.getBoldFont(parent)); - titleBar.setLayoutData(new GridData(SWT.BEGINNING, SWT.FILL, true, false)); - updateTitleBar(); - Button cancelButton = new Button(messageArea, SWT.FLAT); - cancelButton.setText(CmsMsg.cancel.lead()); - cancelButton.setLayoutData(new GridData(SWT.END, SWT.TOP, false, false, 1, 3)); - cancelButton.addSelectionListener((Selected) (e) -> closeShell(CANCEL)); - message = new Label(messageArea, SWT.WRAP); - message.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false, 1, 2)); - updateMessage(); - } - - Composite body = new Composite(parent, SWT.BORDER); - body.setLayout(new FormLayout()); - body.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - pageBodies = new Composite[wizard.getPageCount()]; - IWizardPage[] pages = wizard.getPages(); - for (int i = 0; i < pages.length; i++) { - pageBodies[i] = new Composite(body, SWT.NONE); - pageBodies[i].setLayout(CmsSwtUtils.noSpaceGridLayout()); - setSwitchingFormData(pageBodies[i]); - pages[i].createControl(pageBodies[i]); - } - showPage(currentPage); - - buttons = new Composite(parent, SWT.NONE); - buttons.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false)); - { - boolean singlePage = wizard.getPageCount() == 1; - // singlePage = false;// dev - GridLayout layout = new GridLayout(singlePage ? 1 : 3, true); - layout.marginWidth = 0; - layout.marginHeight = 0; - buttons.setLayout(layout); - // TODO revert order for right-to-left languages - - if (!singlePage) { - back = new Button(buttons, SWT.PUSH); - back.setText(CmsMsg.wizardBack.lead()); - back.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); - back.addSelectionListener((Selected) (e) -> backPressed()); - - next = new Button(buttons, SWT.PUSH); - next.setText(CmsMsg.wizardNext.lead()); - next.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); - next.addSelectionListener((Selected) (e) -> nextPressed()); - } - finish = new Button(buttons, SWT.PUSH); - finish.setText(CmsMsg.wizardFinish.lead()); - finish.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); - finish.addSelectionListener((Selected) (e) -> finishPressed()); - - updateButtons(); - } - return body; - } - - @Override - public IWizardPage getCurrentPage() { - return currentPage; - } - - @Override - public Shell getShell() { - return getForegoundShell(); - } - - @Override - public void showPage(IWizardPage page) { - IWizardPage[] pages = wizard.getPages(); - int index = -1; - for (int i = 0; i < pages.length; i++) { - if (page == pages[i]) { - index = i; - break; - } - } - if (index < 0) - throw new IllegalArgumentException("Cannot find index of wizard page " + page); - pageBodies[index].moveAbove(pageBodies[currentPageIndex]); - - // // clear - // for (Control c : body.getChildren()) - // c.dispose(); - // page.createControl(body); - // body.layout(true, true); - currentPageIndex = index; - currentPage = page; - } - - @Override - public void updateButtons() { - if (back != null) - back.setEnabled(wizard.getPreviousPage(currentPage) != null); - if (next != null) - next.setEnabled(wizard.getNextPage(currentPage) != null && currentPage.canFlipToNextPage()); - if (finish != null) { - finish.setEnabled(wizard.canFinish()); - } - } - - @Override - public void updateMessage() { - if (currentPage.getMessage() != null) - message.setText(currentPage.getMessage()); - } - - @Override - public void updateTitleBar() { - if (currentPage.getTitle() != null) - titleBar.setText(currentPage.getTitle()); - } - - @Override - public void updateWindowTitle() { - setTitle(wizard.getWindowTitle()); - } - - @Override - public void run(boolean fork, boolean cancelable, IRunnableWithProgress runnable) - throws InvocationTargetException, InterruptedException { - runnable.run(null); - } - - @Override - public void updateSize() { - // TODO pack? - } - - protected boolean onCancel() { - return wizard.performCancel(); - } - - protected void nextPressed() { - IWizardPage page = wizard.getNextPage(currentPage); - showPage(page); - updateButtons(); - } - - protected void backPressed() { - IWizardPage page = wizard.getPreviousPage(currentPage); - showPage(page); - updateButtons(); - } - - protected void finishPressed() { - if (wizard.performFinish()) - closeShell(OK); - } - - private static void setSwitchingFormData(Composite composite) { - FormData fdLabel = new FormData(); - fdLabel.top = new FormAttachment(0, 0); - fdLabel.left = new FormAttachment(0, 0); - fdLabel.right = new FormAttachment(100, 0); - fdLabel.bottom = new FormAttachment(100, 0); - composite.setLayoutData(fdLabel); - } - -} diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/LightweightDialog.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/LightweightDialog.java deleted file mode 100644 index bf6417bea..000000000 --- a/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/LightweightDialog.java +++ /dev/null @@ -1,255 +0,0 @@ -package org.argeo.cms.swt.dialogs; - -import org.argeo.api.cms.CmsLog; -import org.argeo.eclipse.ui.EclipseUiException; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.FocusEvent; -import org.eclipse.swt.events.FocusListener; -import org.eclipse.swt.events.ShellAdapter; -import org.eclipse.swt.events.ShellEvent; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.graphics.Rectangle; -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.Display; -import org.eclipse.swt.widgets.Shell; - -/** Generic lightweight dialog, not based on JFace. */ -public class LightweightDialog { - private final static CmsLog log = CmsLog.getLog(LightweightDialog.class); - - // must be the same value as org.eclipse.jface.window.Window#OK - public final static int OK = 0; - // must be the same value as org.eclipse.jface.window.Window#CANCEL - public final static int CANCEL = 1; - - private Shell parentShell; - private Shell backgroundShell; - private Shell foregoundShell; - - private Integer returnCode = null; - private boolean block = true; - - private String title; - - /** Tries to find a display */ - private static Display getDisplay() { - try { - Display display = Display.getCurrent(); - if (display != null) - return display; - else - return Display.getDefault(); - } catch (Exception e) { - return Display.getCurrent(); - } - } - - public LightweightDialog(Shell parentShell) { - this.parentShell = parentShell; - } - - public int open() { - if (foregoundShell != null) - throw new EclipseUiException("There is already a shell"); - backgroundShell = new Shell(parentShell, SWT.ON_TOP); - backgroundShell.setFullScreen(true); - // if (parentShell != null) { - // backgroundShell.setBounds(parentShell.getBounds()); - // } else - // backgroundShell.setMaximized(true); - backgroundShell.setAlpha(128); - backgroundShell.setBackground(getDisplay().getSystemColor(SWT.COLOR_BLACK)); - foregoundShell = new Shell(backgroundShell, SWT.NO_TRIM | SWT.ON_TOP); - if (title != null) - setTitle(title); - foregoundShell.setLayout(new GridLayout()); - foregoundShell.setSize(getInitialSize()); - createDialogArea(foregoundShell); - // shell.pack(); - // shell.layout(); - - Rectangle shellBounds = parentShell != null ? parentShell.getBounds() : Display.getCurrent().getBounds();// RAP - Point dialogSize = foregoundShell.getSize(); - int x = shellBounds.x + (shellBounds.width - dialogSize.x) / 2; - int y = shellBounds.y + (shellBounds.height - dialogSize.y) / 2; - foregoundShell.setLocation(x, y); - - foregoundShell.addShellListener(new ShellAdapter() { - private static final long serialVersionUID = -2701270481953688763L; - - @Override - public void shellDeactivated(ShellEvent e) { - if (hasChildShells()) - return; - if (returnCode == null)// not yet closed - closeShell(CANCEL); - } - - @Override - public void shellClosed(ShellEvent e) { - notifyClose(); - } - - }); - - backgroundShell.open(); - foregoundShell.open(); - // after the foreground shell has been opened - backgroundShell.addFocusListener(new FocusListener() { - private static final long serialVersionUID = 3137408447474661070L; - - @Override - public void focusLost(FocusEvent event) { - } - - @Override - public void focusGained(FocusEvent event) { - if (hasChildShells()) - return; - if (returnCode == null)// not yet closed - closeShell(CANCEL); - } - }); - - if (block) { - block(); - } - if (returnCode == null) - returnCode = OK; - return returnCode; - } - - public void block() { - try { - runEventLoop(foregoundShell); - } catch (ThreadDeath t) { - returnCode = CANCEL; - if (log.isTraceEnabled()) - log.error("Thread death, canceling dialog", t); - } catch (Throwable t) { - returnCode = CANCEL; - log.error("Cannot open blocking lightweight dialog", t); - } - } - - private boolean hasChildShells() { - if (foregoundShell == null) - return false; - return foregoundShell.getShells().length != 0; - } - - // public synchronized int openAndWait() { - // open(); - // while (returnCode == null) - // try { - // wait(100); - // } catch (InterruptedException e) { - // // silent - // } - // return returnCode; - // } - - private synchronized void notifyClose() { - if (returnCode == null) - returnCode = CANCEL; - notifyAll(); - } - - protected void closeShell(int returnCode) { - this.returnCode = returnCode; - if (CANCEL == returnCode) - onCancel(); - if (foregoundShell != null && !foregoundShell.isDisposed()) { - foregoundShell.close(); - foregoundShell.dispose(); - foregoundShell = null; - } - - if (backgroundShell != null && !backgroundShell.isDisposed()) { - backgroundShell.close(); - backgroundShell.dispose(); - } - } - - protected Point getInitialSize() { - // if (exception != null) - // return new Point(800, 600); - // else - return new Point(600, 400); - } - - protected Control createDialogArea(Composite parent) { - Composite dialogarea = new Composite(parent, SWT.NONE); - dialogarea.setLayout(new GridLayout()); - dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - return dialogarea; - } - - protected Shell getBackgroundShell() { - return backgroundShell; - } - - protected Shell getForegoundShell() { - return foregoundShell; - } - - public void setBlockOnOpen(boolean shouldBlock) { - block = shouldBlock; - } - - public void pack() { - foregoundShell.pack(); - } - - private void runEventLoop(Shell loopShell) { - Display display; - if (foregoundShell == null) { - display = Display.getCurrent(); - } else { - display = loopShell.getDisplay(); - } - - while (loopShell != null && !loopShell.isDisposed()) { - try { - if (!display.readAndDispatch()) { - display.sleep(); - } - } catch (UnsupportedOperationException e) { - throw e; - } catch (Throwable e) { - handleException(e); - } - } - if (!display.isDisposed()) - display.update(); - } - - protected void handleException(Throwable t) { - if (t instanceof ThreadDeath) { - // Don't catch ThreadDeath as this is a normal occurrence when - // the thread dies - throw (ThreadDeath) t; - } - // Try to keep running. - t.printStackTrace(); - } - - /** @return false, if the dialog should not be closed. */ - protected boolean onCancel() { - return true; - } - - public void setTitle(String title) { - this.title = title; - if (title != null && getForegoundShell() != null) - getForegoundShell().setText(title); - } - - public Integer getReturnCode() { - return returnCode; - } - -} \ No newline at end of file diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/SingleValueDialog.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/SingleValueDialog.java deleted file mode 100644 index 9404b81da..000000000 --- a/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/SingleValueDialog.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.argeo.cms.swt.dialogs; - -import org.eclipse.jface.window.Window; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; - -/** A dialog asking a for a single value. */ -public class SingleValueDialog extends CmsMessageDialog { - private Text valueT; - private String value; - private String defaultValue; - - public SingleValueDialog(Shell parentShell, String message) { - super(parentShell, message, QUESTION); - } - - public SingleValueDialog(Shell parentShell, String message, String defaultValue) { - super(parentShell, message, QUESTION); - this.defaultValue = defaultValue; - } - - @Override - protected Control createInputArea(Composite parent) { - valueT = new Text(parent, SWT.LEAD | SWT.BORDER | SWT.SINGLE); - valueT.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true)); - if (defaultValue != null) - valueT.setText(defaultValue); - return valueT; - } - - @Override - protected void okPressed() { - value = valueT.getText(); - super.okPressed(); - } - - public String getString() { - return value; - } - - public Long getLong() { - return Long.valueOf(getString()); - } - - public Double getDouble() { - return Double.valueOf(getString()); - } - - public static String ask(String message) { - return ask(message, null); - } - - public static String ask(String message, String defaultValue) { - SingleValueDialog svd = new SingleValueDialog(Display.getCurrent().getActiveShell(), message, defaultValue); - if (svd.open() == Window.OK) - return svd.getString(); - else - return null; - } - - public static Long askLong(String message) { - SingleValueDialog svd = new SingleValueDialog(Display.getCurrent().getActiveShell(), message); - if (svd.open() == Window.OK) - return svd.getLong(); - else - return null; - } - - public static Double askDouble(String message) { - SingleValueDialog svd = new SingleValueDialog(Display.getCurrent().getActiveShell(), message); - if (svd.open() == Window.OK) - return svd.getDouble(); - else - return null; - } - -} diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/package-info.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/package-info.java deleted file mode 100644 index ac76dba81..000000000 --- a/org.argeo.cms.swt/src/org/argeo/cms/swt/dialogs/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** SWT/JFace dialogs. */ -package org.argeo.cms.swt.dialogs; \ No newline at end of file diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/GcrContentTreeView.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/GcrContentTreeView.java deleted file mode 100644 index 4039f2baa..000000000 --- a/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/GcrContentTreeView.java +++ /dev/null @@ -1,130 +0,0 @@ -package org.argeo.cms.swt.gcr; - -import java.nio.file.Path; -import java.nio.file.Paths; - -import javax.xml.namespace.QName; - -import org.argeo.api.acr.Content; -import org.argeo.cms.acr.fs.FsContentProvider; -import org.argeo.cms.swt.CmsSwtUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.FillLayout; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Table; -import org.eclipse.swt.widgets.TableColumn; -import org.eclipse.swt.widgets.TableItem; -import org.eclipse.swt.widgets.Tree; -import org.eclipse.swt.widgets.TreeItem; - -public class GcrContentTreeView extends Composite { - private Tree tree; - private Table table; - private Content rootContent; - - private Content selected; - - public GcrContentTreeView(Composite parent, int style, Content content) { - super(parent, style); - this.rootContent = content; - this.selected = rootContent; - setLayout(new GridLayout(2, false)); - initTree(); - GridData treeGd = CmsSwtUtils.fillHeight(); - treeGd.widthHint = 300; - tree.setLayoutData(treeGd); - initTable(); - - table.setLayoutData(CmsSwtUtils.fillAll()); - } - - protected void initTree() { - tree = new Tree(this, 0); - for (Content c : rootContent) { - TreeItem root = new TreeItem(tree, 0); - root.setText(c.getName().toString()); - root.setData(c); - new TreeItem(root, 0); - } - tree.addListener(SWT.Expand, event -> { - final TreeItem root = (TreeItem) event.item; - TreeItem[] items = root.getItems(); - for (TreeItem item : items) { - if (item.getData() != null) - return; - item.dispose(); - } - Content content = (Content) root.getData(); - for (Content c : content) { - TreeItem item = new TreeItem(root, 0); - item.setText(c.getName().toString()); - item.setData(c); - boolean hasChildren = true; - if (hasChildren) { - new TreeItem(item, 0); - } - } - }); - tree.addListener(SWT.Selection, event -> { - TreeItem item = (TreeItem) event.item; - selected = (Content) item.getData(); - refreshTable(); - }); - } - - protected void initTable() { - table = new Table(this, 0); - table.setLinesVisible(true); - table.setHeaderVisible(true); - TableColumn keyCol = new TableColumn(table, SWT.NONE); - keyCol.setText("Attribute"); - keyCol.setWidth(200); - TableColumn valueCol = new TableColumn(table, SWT.NONE); - valueCol.setText("Value"); - keyCol.setWidth(300); - refreshTable(); - } - - protected void refreshTable() { - for (TableItem item : table.getItems()) { - item.dispose(); - } - for (QName key : selected.keySet()) { - TableItem item = new TableItem(table, 0); - item.setText(0, key.toString()); - Object value = selected.get(key); - item.setText(1, value.toString()); - } - table.getColumn(0).pack(); - table.getColumn(1).pack(); - } - - public static void main(String[] args) { - Path basePath; - if (args.length > 0) { - basePath = Paths.get(args[0]); - } else { - basePath = Paths.get(System.getProperty("user.home")); - } - - final Display display = new Display(); - final Shell shell = new Shell(display); - shell.setText(basePath.toString()); - shell.setLayout(new FillLayout()); - - FsContentProvider contentSession = new FsContentProvider(basePath); -// GcrContentTreeView treeView = new GcrContentTreeView(shell, 0, contentSession.get("/")); - - shell.setSize(shell.computeSize(800, 600)); - shell.open(); - while (!shell.isDisposed()) { - if (!display.readAndDispatch()) - display.sleep(); - } - display.dispose(); - } -} diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/SwtUiProvider.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/SwtUiProvider.java deleted file mode 100644 index 8109c40ac..000000000 --- a/org.argeo.cms.swt/src/org/argeo/cms/swt/gcr/SwtUiProvider.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.argeo.cms.swt.gcr; - -import org.argeo.api.acr.Content; -import org.argeo.api.cms.MvcProvider; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; - -@FunctionalInterface -public interface SwtUiProvider extends MvcProvider { - -} diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleCmsSwtTheme.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleCmsSwtTheme.java deleted file mode 100644 index b9b2751a7..000000000 --- a/org.argeo.cms.swt/src/org/argeo/cms/swt/osgi/BundleCmsSwtTheme.java +++ /dev/null @@ -1,93 +0,0 @@ -package org.argeo.cms.swt.osgi; - -import java.io.IOException; -import java.io.InputStream; -import java.util.HashMap; -import java.util.Map; - -import org.argeo.cms.osgi.BundleCmsTheme; -import org.argeo.cms.swt.CmsSwtTheme; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.graphics.ImageData; -import org.eclipse.swt.widgets.Display; - -/** Centralises some generic {@link CmsSwtTheme} patterns. */ -public class BundleCmsSwtTheme extends BundleCmsTheme implements CmsSwtTheme { - private Map imageCache = new HashMap<>(); - - private Map> iconPaths = new HashMap<>(); - - public Image getImage(String path) { - if (!imageCache.containsKey(path)) { - try (InputStream in = getResourceAsStream(path)) { - if (in == null) - return null; - ImageData imageData = new ImageData(in); - imageCache.put(path, imageData); - } catch (IOException e) { - throw new IllegalStateException(e); - } - } - ImageData imageData = imageCache.get(path); - Image image = new Image(Display.getCurrent(), imageData); - return image; - } - - /** - * And icon with this file name (without the extension), with a best effort to - * find the appropriate size, or null if not found. - * - * @param name An icon file name without path and extension. - * @param preferredSize the preferred size, if null, - * {@link #getDefaultIconSize()} will be tried. - */ - public Image getIcon(String name, Integer preferredSize) { - if (preferredSize == null) - preferredSize = getDefaultIconSize(); - Map subCache; - if (!iconPaths.containsKey(name)) - subCache = new HashMap<>(); - else - subCache = iconPaths.get(name); - Image image = null; - if (!subCache.containsKey(preferredSize)) { - Image bestMatchSoFar = null; - paths: for (String p : getImagesPaths()) { - int lastSlash = p.lastIndexOf('/'); - String fileName = p; - if (lastSlash >= 0) - fileName = p.substring(lastSlash + 1); - int lastDot = fileName.lastIndexOf('.'); - if (lastDot >= 0) - fileName = fileName.substring(0, lastDot); - if (fileName.equals(name)) {// matched - Image img = getImage(p); - int width = img.getBounds().width; - if (width == preferredSize) {// perfect match - subCache.put(preferredSize, p); - image = img; - break paths; - } - if (bestMatchSoFar == null) { - bestMatchSoFar = img; - } else { - if (Math.abs(width - preferredSize) < Math - .abs(bestMatchSoFar.getBounds().width - preferredSize)) - bestMatchSoFar = img; - } - } - } - - if (image == null) - image = bestMatchSoFar; - } else { - image = getImage(subCache.get(preferredSize)); - } - - if (image != null && !iconPaths.containsKey(name)) - iconPaths.put(name, subCache); - - return image; - } - -} diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/PickUpUserDialog.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/PickUpUserDialog.java deleted file mode 100644 index ed1bfd868..000000000 --- a/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/PickUpUserDialog.java +++ /dev/null @@ -1,246 +0,0 @@ -package org.argeo.cms.swt.useradmin; - -import java.util.ArrayList; -import java.util.List; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.eclipse.ui.ColumnDefinition; -import org.argeo.eclipse.ui.EclipseUiException; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.argeo.eclipse.ui.parts.LdifUsersTable; -import org.argeo.util.naming.LdapAttrs; -import org.argeo.util.naming.LdapObjs; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.jface.dialogs.TrayDialog; -import org.eclipse.jface.viewers.DoubleClickEvent; -import org.eclipse.jface.viewers.IDoubleClickListener; -import org.eclipse.jface.viewers.ISelectionChangedListener; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.SelectionChangedEvent; -import org.eclipse.jface.viewers.TableViewer; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.layout.FillLayout; -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.Shell; -import org.osgi.framework.InvalidSyntaxException; -import org.osgi.service.useradmin.Group; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.User; -import org.osgi.service.useradmin.UserAdmin; - -/** Dialog with a user (or group) list to pick up one */ -public class PickUpUserDialog extends TrayDialog { - private static final long serialVersionUID = -1420106871173920369L; - - // Business objects - private final UserAdmin userAdmin; - private User selectedUser; - - // this page widgets and UI objects - private String title; - private LdifUsersTable userTableViewerCmp; - private TableViewer userViewer; - private List columnDefs = new ArrayList(); - - /** - * A dialog to pick up a group or a user, showing a table with default - * columns - */ - public PickUpUserDialog(Shell parentShell, String title, UserAdmin userAdmin) { - super(parentShell); - this.title = title; - this.userAdmin = userAdmin; - - columnDefs.add(new ColumnDefinition(new UserLP(UserLP.COL_ICON), "", - 24, 24)); - columnDefs.add(new ColumnDefinition( - new UserLP(UserLP.COL_DISPLAY_NAME), "Common Name", 150, 100)); - columnDefs.add(new ColumnDefinition(new UserLP(UserLP.COL_DOMAIN), - "Domain", 100, 120)); - columnDefs.add(new ColumnDefinition(new UserLP(UserLP.COL_DN), - "Distinguished Name", 300, 100)); - } - - /** A dialog to pick up a group or a user */ - public PickUpUserDialog(Shell parentShell, String title, - UserAdmin userAdmin, List columnDefs) { - super(parentShell); - this.title = title; - this.userAdmin = userAdmin; - this.columnDefs = columnDefs; - } - - @Override - protected void okPressed() { - if (getSelected() == null) - MessageDialog.openError(getShell(), "No user chosen", - "Please, choose a user or press Cancel."); - else - super.okPressed(); - } - - protected Control createDialogArea(Composite parent) { - Composite dialogArea = (Composite) super.createDialogArea(parent); - dialogArea.setLayout(new FillLayout()); - - Composite bodyCmp = new Composite(dialogArea, SWT.NO_FOCUS); - bodyCmp.setLayout(new GridLayout()); - - // Create and configure the table - userTableViewerCmp = new MyUserTableViewer(bodyCmp, SWT.MULTI - | SWT.H_SCROLL | SWT.V_SCROLL); - - userTableViewerCmp.setColumnDefinitions(columnDefs); - userTableViewerCmp.populateWithStaticFilters(false, false); - GridData gd = EclipseUiUtils.fillAll(); - gd.minimumHeight = 300; - userTableViewerCmp.setLayoutData(gd); - userTableViewerCmp.refresh(); - - // Controllers - userViewer = userTableViewerCmp.getTableViewer(); - userViewer.addDoubleClickListener(new MyDoubleClickListener()); - userViewer - .addSelectionChangedListener(new MySelectionChangedListener()); - - parent.pack(); - return dialogArea; - } - - public User getSelected() { - if (selectedUser == null) - return null; - else - return selectedUser; - } - - protected void configureShell(Shell shell) { - super.configureShell(shell); - shell.setText(title); - } - - class MyDoubleClickListener implements IDoubleClickListener { - public void doubleClick(DoubleClickEvent evt) { - if (evt.getSelection().isEmpty()) - return; - - Object obj = ((IStructuredSelection) evt.getSelection()) - .getFirstElement(); - if (obj instanceof User) { - selectedUser = (User) obj; - okPressed(); - } - } - } - - class MySelectionChangedListener implements ISelectionChangedListener { - @Override - public void selectionChanged(SelectionChangedEvent event) { - if (event.getSelection().isEmpty()) { - selectedUser = null; - return; - } - Object obj = ((IStructuredSelection) event.getSelection()) - .getFirstElement(); - if (obj instanceof Group) { - selectedUser = (Group) obj; - } - } - } - - private class MyUserTableViewer extends LdifUsersTable { - private static final long serialVersionUID = 8467999509931900367L; - - private final String[] knownProps = { LdapAttrs.uid.name(), - LdapAttrs.cn.name(), LdapAttrs.DN }; - - private Button showSystemRoleBtn; - private Button showUserBtn; - - public MyUserTableViewer(Composite parent, int style) { - super(parent, style); - } - - protected void populateStaticFilters(Composite staticFilterCmp) { - staticFilterCmp.setLayout(new GridLayout()); - showSystemRoleBtn = new Button(staticFilterCmp, SWT.CHECK); - showSystemRoleBtn.setText("Show system roles "); - - showUserBtn = new Button(staticFilterCmp, SWT.CHECK); - showUserBtn.setText("Show users "); - - SelectionListener sl = new SelectionAdapter() { - private static final long serialVersionUID = -7033424592697691676L; - - @Override - public void widgetSelected(SelectionEvent e) { - refresh(); - } - }; - - showSystemRoleBtn.addSelectionListener(sl); - showUserBtn.addSelectionListener(sl); - } - - @Override - protected List listFilteredElements(String filter) { - Role[] roles; - try { - StringBuilder builder = new StringBuilder(); - - StringBuilder filterBuilder = new StringBuilder(); - if (notNull(filter)) - for (String prop : knownProps) { - filterBuilder.append("("); - filterBuilder.append(prop); - filterBuilder.append("=*"); - filterBuilder.append(filter); - filterBuilder.append("*)"); - } - - String typeStr = "(" + LdapAttrs.objectClass.name() + "=" - + LdapObjs.groupOfNames.name() + ")"; - if ((showUserBtn.getSelection())) - typeStr = "(|(" + LdapAttrs.objectClass.name() + "=" - + LdapObjs.inetOrgPerson.name() + ")" + typeStr - + ")"; - - if (!showSystemRoleBtn.getSelection()) - typeStr = "(& " + typeStr + "(!(" + LdapAttrs.DN + "=*" - + CmsConstants.ROLES_BASEDN + ")))"; - - if (filterBuilder.length() > 1) { - builder.append("(&" + typeStr); - builder.append("(|"); - builder.append(filterBuilder.toString()); - builder.append("))"); - } else { - builder.append(typeStr); - } - roles = userAdmin.getRoles(builder.toString()); - } catch (InvalidSyntaxException e) { - throw new EclipseUiException( - "Unable to get roles with filter: " + filter, e); - } - List users = new ArrayList(); - for (Role role : roles) - if (!users.contains(role)) - users.add((User) role); - return users; - } - } - - private boolean notNull(String string) { - if (string == null) - return false; - else - return !"".equals(string.trim()); - } -} diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/UserLP.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/UserLP.java deleted file mode 100644 index d1c90a43f..000000000 --- a/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/UserLP.java +++ /dev/null @@ -1,76 +0,0 @@ -package org.argeo.cms.swt.useradmin; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.cms.auth.UserAdminUtils; -import org.eclipse.jface.resource.JFaceResources; -import org.eclipse.jface.viewers.ColumnLabelProvider; -import org.eclipse.swt.SWT; -import org.eclipse.swt.graphics.Font; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.widgets.Display; -import org.osgi.service.useradmin.Role; -import org.osgi.service.useradmin.User; - -/** Centralize label providers for the group table */ -class UserLP extends ColumnLabelProvider { - private static final long serialVersionUID = -4645930210988368571L; - - final static String COL_ICON = "colID.icon"; - final static String COL_DN = "colID.dn"; - final static String COL_DISPLAY_NAME = "colID.displayName"; - final static String COL_DOMAIN = "colID.domain"; - - final String currType; - - // private Font italic; - private Font bold; - - UserLP(String colId) { - this.currType = colId; - } - - @Override - public Font getFont(Object element) { - // Current user as bold - if (UserAdminUtils.isCurrentUser(((User) element))) { - if (bold == null) - bold = JFaceResources.getFontRegistry().defaultFontDescriptor().setStyle(SWT.BOLD) - .createFont(Display.getCurrent()); - return bold; - } - return null; - } - - @Override - public Image getImage(Object element) { - if (COL_ICON.equals(currType)) { - User user = (User) element; - String dn = user.getName(); - if (dn.endsWith(CmsConstants.ROLES_BASEDN)) - return UsersImages.ICON_ROLE; - else if (user.getType() == Role.GROUP) - return UsersImages.ICON_GROUP; - else - return UsersImages.ICON_USER; - } else - return null; - } - - @Override - public String getText(Object element) { - User user = (User) element; - return getText(user); - - } - - public String getText(User user) { - if (COL_DN.equals(currType)) - return user.getName(); - else if (COL_DISPLAY_NAME.equals(currType)) - return UserAdminUtils.getCommonName(user); - else if (COL_DOMAIN.equals(currType)) - return UserAdminUtils.getDomainName(user); - else - return ""; - } -} diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/UsersImages.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/UsersImages.java deleted file mode 100644 index 21fc5afba..000000000 --- a/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/UsersImages.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.argeo.cms.swt.useradmin; - -import org.argeo.cms.ui.theme.CmsImages; -import org.eclipse.swt.graphics.Image; - -/** Specific users icons. */ -public class UsersImages { - private final static String PREFIX = "icons/"; - - public final static Image ICON_USER = CmsImages.createImg(PREFIX + "person.png"); - public final static Image ICON_GROUP = CmsImages.createImg(PREFIX + "group.png"); - public final static Image ICON_ROLE = CmsImages.createImg(PREFIX + "role.gif"); - public final static Image ICON_CHANGE_PASSWORD = CmsImages.createImg(PREFIX + "security.gif"); -} diff --git a/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/package-info.java b/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/package-info.java deleted file mode 100644 index 3597bfc57..000000000 --- a/org.argeo.cms.swt/src/org/argeo/cms/swt/useradmin/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** SWT/JFace users management components. */ -package org.argeo.cms.swt.useradmin; \ No newline at end of file diff --git a/org.argeo.cms.swt/src/org/argeo/cms/ui/theme/CmsImages.java b/org.argeo.cms.swt/src/org/argeo/cms/ui/theme/CmsImages.java deleted file mode 100644 index 1c4d79eee..000000000 --- a/org.argeo.cms.swt/src/org/argeo/cms/ui/theme/CmsImages.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.argeo.cms.ui.theme; - -import java.net.URL; - -import org.eclipse.jface.resource.ImageDescriptor; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.widgets.Display; -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; - -public class CmsImages { - private static BundleContext themeBc = FrameworkUtil.getBundle(CmsImages.class).getBundleContext(); - - final public static String ICONS_BASE = "icons/"; - final public static String TYPES_BASE = ICONS_BASE + "types/"; - final public static String ACTIONS_BASE = ICONS_BASE + "actions/"; - - public static Image createIcon(String name) { - return createImg(CmsImages.ICONS_BASE + name); - } - - public static Image createAction(String name) { - return createImg(CmsImages.ACTIONS_BASE + name); - } - - public static Image createType(String name) { - return createImg(CmsImages.TYPES_BASE + name); - } - - public static Image createImg(String name) { - return CmsImages.createDesc(name).createImage(Display.getDefault()); - } - - public static ImageDescriptor createDesc(String name) { - return createDesc(themeBc, name); - } - - public static ImageDescriptor createDesc(BundleContext bc, String name) { - URL url = bc.getBundle().getResource(name); - if (url == null) - return ImageDescriptor.getMissingImageDescriptor(); - return ImageDescriptor.createFromURL(url); - } - - public static Image createImg(BundleContext bc, String name) { - return createDesc(bc, name).createImage(Display.getDefault()); - } - -} diff --git a/org.argeo.cms.swt/src/org/argeo/cms/ui/theme/package-info.java b/org.argeo.cms.swt/src/org/argeo/cms/ui/theme/package-info.java deleted file mode 100644 index 7d3a260f3..000000000 --- a/org.argeo.cms.swt/src/org/argeo/cms/ui/theme/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Argeo CMS core theme images. */ -package org.argeo.cms.ui.theme; \ No newline at end of file diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/AbstractTreeContentProvider.java b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/AbstractTreeContentProvider.java deleted file mode 100644 index c882eb766..000000000 --- a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/AbstractTreeContentProvider.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.argeo.eclipse.ui; - -import org.eclipse.jface.viewers.ITreeContentProvider; -import org.eclipse.jface.viewers.Viewer; - -/** - * Tree content provider dealing with tree objects and providing reasonable - * defaults. - */ -public abstract class AbstractTreeContentProvider implements - ITreeContentProvider { - private static final long serialVersionUID = 8246126401957763868L; - - /** Does nothing */ - public void dispose() { - } - - /** Does nothing */ - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - } - - public Object[] getChildren(Object element) { - if (element instanceof TreeParent) { - return ((TreeParent) element).getChildren(); - } - return new Object[0]; - } - - public Object getParent(Object element) { - if (element instanceof TreeParent) { - return ((TreeParent) element).getParent(); - } - return null; - } - - public boolean hasChildren(Object element) { - if (element instanceof TreeParent) { - return ((TreeParent) element).hasChildren(); - } - return false; - } -} \ No newline at end of file diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnDefinition.java b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnDefinition.java deleted file mode 100644 index a38552c07..000000000 --- a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnDefinition.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.argeo.eclipse.ui; - -import org.eclipse.jface.viewers.ColumnLabelProvider; - -/** - * Wraps the definition of a column to be used in the various JFace viewers - * (typically tree and table). It enables definition of generic viewers which - * column can be then defined externally. Also used to generate export. - */ -public class ColumnDefinition { - private ColumnLabelProvider labelProvider; - private String label; - private int weight = 0; - private int minWidth = 120; - - public ColumnDefinition(ColumnLabelProvider labelProvider, String label) { - this.labelProvider = labelProvider; - this.label = label; - } - - public ColumnDefinition(ColumnLabelProvider labelProvider, String label, - int weight) { - this.labelProvider = labelProvider; - this.label = label; - this.weight = weight; - this.minWidth = weight; - } - - public ColumnDefinition(ColumnLabelProvider labelProvider, String label, - int weight, int minimumWidth) { - this.labelProvider = labelProvider; - this.label = label; - this.weight = weight; - this.minWidth = minimumWidth; - } - - public ColumnLabelProvider getLabelProvider() { - return labelProvider; - } - - public void setLabelProvider(ColumnLabelProvider labelProvider) { - this.labelProvider = labelProvider; - } - - public String getLabel() { - return label; - } - - public void setLabel(String label) { - this.label = label; - } - - public int getWeight() { - return weight; - } - - public void setWeight(int weight) { - this.weight = weight; - } - - public int getMinWidth() { - return minWidth; - } - - public void setMinWidth(int minWidth) { - this.minWidth = minWidth; - } -} \ No newline at end of file diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnViewerComparator.java b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnViewerComparator.java deleted file mode 100644 index 9430a2083..000000000 --- a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/ColumnViewerComparator.java +++ /dev/null @@ -1,81 +0,0 @@ -package org.argeo.eclipse.ui; - -import org.eclipse.jface.viewers.ColumnViewer; -import org.eclipse.jface.viewers.TableViewerColumn; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.jface.viewers.ViewerComparator; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; - -/** Generic column viewer sorter */ -public class ColumnViewerComparator extends ViewerComparator { - private static final long serialVersionUID = -2266218906355859909L; - - public static final int ASC = 1; - - public static final int NONE = 0; - - public static final int DESC = -1; - - private int direction = 0; - - private TableViewerColumn column; - - private ColumnViewer viewer; - - public ColumnViewerComparator(TableViewerColumn column) { - super(null); - this.column = column; - this.viewer = column.getViewer(); - this.column.getColumn().addSelectionListener(new SelectionAdapter() { - private static final long serialVersionUID = 7586796298965472189L; - - public void widgetSelected(SelectionEvent e) { - if (ColumnViewerComparator.this.viewer.getComparator() != null) { - if (ColumnViewerComparator.this.viewer.getComparator() == ColumnViewerComparator.this) { - int tdirection = ColumnViewerComparator.this.direction; - - if (tdirection == ASC) { - setSortDirection(DESC); - } else if (tdirection == DESC) { - setSortDirection(NONE); - } - } else { - setSortDirection(ASC); - } - } else { - setSortDirection(ASC); - } - } - }); - } - - private void setSortDirection(int direction) { - if (direction == NONE) { - column.getColumn().getParent().setSortColumn(null); - column.getColumn().getParent().setSortDirection(SWT.NONE); - viewer.setComparator(null); - } else { - column.getColumn().getParent().setSortColumn(column.getColumn()); - this.direction = direction; - - if (direction == ASC) { - column.getColumn().getParent().setSortDirection(SWT.DOWN); - } else { - column.getColumn().getParent().setSortDirection(SWT.UP); - } - - if (viewer.getComparator() == this) { - viewer.refresh(); - } else { - viewer.setComparator(this); - } - - } - } - - public int compare(Viewer viewer, Object e1, Object e2) { - return direction * super.compare(viewer, e1, e2); - } -} diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiException.java b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiException.java deleted file mode 100644 index 37a36e859..000000000 --- a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiException.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.argeo.eclipse.ui; - -/** CMS specific exceptions. */ -public class EclipseUiException extends RuntimeException { - private static final long serialVersionUID = -5341764743356771313L; - - public EclipseUiException(String message) { - super(message); - } - - public EclipseUiException(String message, Throwable e) { - super(message, e); - } - -} diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiUtils.java b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiUtils.java deleted file mode 100644 index 95b45fed6..000000000 --- a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/EclipseUiUtils.java +++ /dev/null @@ -1,195 +0,0 @@ -package org.argeo.eclipse.ui; - -import org.eclipse.jface.resource.JFaceResources; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.ModifyListener; -import org.eclipse.swt.graphics.Font; -import org.eclipse.swt.layout.FormAttachment; -import org.eclipse.swt.layout.FormData; -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.Label; -import org.eclipse.swt.widgets.Text; - -/** Utilities to simplify UI development. */ -public class EclipseUiUtils { - - /** Dispose all children of a Composite */ - public static void clear(Composite composite) { - for (Control child : composite.getChildren()) - child.dispose(); - } - - /** - * Enables efficient call to the layout method of a composite, refreshing only - * some of the children controls. - */ - public static void layout(Composite parent, Control... toUpdateControls) { - parent.layout(toUpdateControls); - } - - // - // FONTS - // - /** Shortcut to retrieve default italic font from display */ - public static Font getItalicFont(Composite parent) { - return JFaceResources.getFontRegistry().defaultFontDescriptor().setStyle(SWT.ITALIC) - .createFont(parent.getDisplay()); - } - - /** Shortcut to retrieve default bold font from display */ - public static Font getBoldFont(Composite parent) { - return JFaceResources.getFontRegistry().defaultFontDescriptor().setStyle(SWT.BOLD) - .createFont(parent.getDisplay()); - } - - /** Shortcut to retrieve default bold italic font from display */ - public static Font getBoldItalicFont(Composite parent) { - return JFaceResources.getFontRegistry().defaultFontDescriptor().setStyle(SWT.BOLD | SWT.ITALIC) - .createFont(parent.getDisplay()); - } - - // - // Simplify grid layouts management - // - public static GridLayout noSpaceGridLayout() { - return noSpaceGridLayout(new GridLayout()); - } - - public static GridLayout noSpaceGridLayout(int columns) { - return noSpaceGridLayout(new GridLayout(columns, false)); - } - - public static GridLayout noSpaceGridLayout(GridLayout layout) { - layout.horizontalSpacing = 0; - layout.verticalSpacing = 0; - layout.marginWidth = 0; - layout.marginHeight = 0; - return layout; - } - - public static GridData fillWidth() { - return grabWidth(SWT.FILL, SWT.FILL); - } - - public static GridData fillWidth(int colSpan) { - GridData gd = grabWidth(SWT.FILL, SWT.FILL); - gd.horizontalSpan = colSpan; - return gd; - } - - public static GridData fillAll() { - return new GridData(SWT.FILL, SWT.FILL, true, true); - } - - public static GridData fillAll(int colSpan, int rowSpan) { - return new GridData(SWT.FILL, SWT.FILL, true, true, colSpan, rowSpan); - } - - public static GridData grabWidth(int horizontalAlignment, int verticalAlignment) { - return new GridData(horizontalAlignment, horizontalAlignment, true, false); - } - - // - // Simplify Form layout management - // - - /** - * Creates a basic form data that is attached to the 4 corners of the parent - * composite - */ - public static FormData fillFormData() { - FormData formData = new FormData(); - formData.top = new FormAttachment(0, 0); - formData.left = new FormAttachment(0, 0); - formData.right = new FormAttachment(100, 0); - formData.bottom = new FormAttachment(100, 0); - return formData; - } - - /** - * Create a label and a text field for a grid layout, the text field grabbing - * excess horizontal - * - * @param parent - * the parent composite - * @param label - * the label to display - * @param modifyListener - * a {@link ModifyListener} to listen on events on the text, can be - * null - * @return the created text - * - */ - // FIXME why was this deprecated. - // * @ deprecated use { @ link #createGridLT(Composite, String)} instead - // @ Deprecated - public static Text createGridLT(Composite parent, String label, ModifyListener modifyListener) { - Label lbl = new Label(parent, SWT.LEAD); - lbl.setText(label); - lbl.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false)); - Text txt = new Text(parent, SWT.LEAD | SWT.BORDER); - txt.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); - if (modifyListener != null) - txt.addModifyListener(modifyListener); - return txt; - } - - /** - * Create a label and a text field for a grid layout, the text field grabbing - * excess horizontal - */ - public static Text createGridLT(Composite parent, String label) { - return createGridLT(parent, label, null); - } - - /** - * Creates one label and a text field not editable with background colour of the - * parent (like a label but with selectable text) - */ - public static Text createGridLL(Composite parent, String label, String text) { - Text txt = createGridLT(parent, label); - txt.setText(text); - txt.setEditable(false); - txt.setBackground(parent.getBackground()); - return txt; - } - - /** - * Create a label and a text field with password display for a grid layout, the - * text field grabbing excess horizontal - */ - public static Text createGridLP(Composite parent, String label) { - return createGridLP(parent, label, null); - } - - /** - * Create a label and a text field with password display for a grid layout, the - * text field grabbing excess horizontal. The given modify listener will be - * added to the newly created text field if not null. - */ - public static Text createGridLP(Composite parent, String label, ModifyListener modifyListener) { - Label lbl = new Label(parent, SWT.LEAD); - lbl.setText(label); - lbl.setLayoutData(new GridData(SWT.RIGHT, SWT.CENTER, false, false)); - Text txt = new Text(parent, SWT.LEAD | SWT.BORDER | SWT.PASSWORD); - txt.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false)); - if (modifyListener != null) - txt.addModifyListener(modifyListener); - return txt; - } - - // MISCELLANEOUS - - /** Simply checks if a string is not null nor empty */ - public static boolean notEmpty(String stringToTest) { - return !(stringToTest == null || "".equals(stringToTest.trim())); - } - - /** Simply checks if a string is null or empty */ - public static boolean isEmpty(String stringToTest) { - return stringToTest == null || "".equals(stringToTest.trim()); - } -} \ No newline at end of file diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/FileProvider.java b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/FileProvider.java deleted file mode 100644 index e82505df5..000000000 --- a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/FileProvider.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.argeo.eclipse.ui; - -import java.io.InputStream; - -/** - * Used for file download : subclasses must implement model specific methods to - * get a byte array representing a file given is ID. - */ -@Deprecated -public interface FileProvider { - - public byte[] getByteArrayFileFromId(String fileId); - - public InputStream getInputStreamFromFileId(String fileId); - -} diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/GenericTableComparator.java b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/GenericTableComparator.java deleted file mode 100644 index e1d8b05ea..000000000 --- a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/GenericTableComparator.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.argeo.eclipse.ui; - -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.jface.viewers.ViewerComparator; - -public abstract class GenericTableComparator extends ViewerComparator { - private static final long serialVersionUID = -1175894935075325810L; - protected int propertyIndex; - public static final int ASCENDING = 0, DESCENDING = 1; - protected int direction = DESCENDING; - - /** - * Creates an instance of a sorter for TableViewer. - * - * @param defaultColumnIndex - * the default sorter column - */ - - public GenericTableComparator(int defaultColumnIndex, int direction) { - propertyIndex = defaultColumnIndex; - this.direction = direction; - } - - public void setColumn(int column) { - if (column == this.propertyIndex) { - // Same column as last sort; toggle the direction - direction = 1 - direction; - } else { - // New column; do a descending sort - this.propertyIndex = column; - direction = DESCENDING; - } - } - - /** - * Must be Overriden in each view. - */ - public abstract int compare(Viewer viewer, Object e1, Object e2); -} diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/IListProvider.java b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/IListProvider.java deleted file mode 100644 index ac7b2d8fb..000000000 --- a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/IListProvider.java +++ /dev/null @@ -1,20 +0,0 @@ -package org.argeo.eclipse.ui; - -import java.util.List; - -/** - * Views and editors can implement this interface so that one of the list that - * is displayed in the part (For instance in a Table or a Tree Viewer) can be - * rebuilt externally. Typically to generate csv or calc extract. - */ -public interface IListProvider { - /** - * Returns an array of current and relevant elements - */ - public Object[] getElements(String extractId); - - /** - * Returns the column definition for passed ID - */ - public List getColumnDefinition(String extractId); -} diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/TreeParent.java b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/TreeParent.java deleted file mode 100644 index cf3c15795..000000000 --- a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/TreeParent.java +++ /dev/null @@ -1,133 +0,0 @@ -package org.argeo.eclipse.ui; - -import java.util.ArrayList; -import java.util.List; - -/** Parent / children semantic to be used for simple UI Tree structure */ -public class TreeParent { - private String name; - private TreeParent parent; - - private List children; - - /** - * Unique id within the context of a tree display. If set, equals() and - * hashCode() methods will be based on it - */ - private String path = null; - - /** False until at least one child has been added, then true until cleared */ - private boolean loaded = false; - - public TreeParent(String name) { - this.name = name; - children = new ArrayList(); - } - - public synchronized void addChild(Object child) { - loaded = true; - children.add(child); - if (child instanceof TreeParent) - ((TreeParent) child).setParent(this); - } - - /** - * Remove this child. The child is disposed. - */ - public synchronized void removeChild(Object child) { - children.remove(child); - if (child instanceof TreeParent) { - ((TreeParent) child).dispose(); - } - } - - public synchronized void clearChildren() { - for (Object obj : children) { - if (obj instanceof TreeParent) - ((TreeParent) obj).dispose(); - } - loaded = false; - children.clear(); - } - - /** - * If overridden, super.dispose() must be called, typically - * after custom cleaning. - */ - public synchronized void dispose() { - clearChildren(); - parent = null; - children = null; - } - - public synchronized Object[] getChildren() { - return children.toArray(new Object[children.size()]); - } - - @SuppressWarnings("unchecked") - public synchronized List getChildrenOfType(Class clss) { - List lst = new ArrayList(); - for (Object obj : children) { - if (clss.isAssignableFrom(obj.getClass())) - lst.add((T) obj); - } - return lst; - } - - public synchronized boolean hasChildren() { - return children.size() > 0; - } - - public Object getChildByName(String name) { - for (Object child : children) { - if (child.toString().equals(name)) - return child; - } - return null; - } - - public synchronized Boolean isLoaded() { - return loaded; - } - - public String getName() { - return name; - } - - public void setParent(TreeParent parent) { - this.parent = parent; - if (parent != null && parent.path != null) - this.path = parent.path + '/' + name; - else - this.path = '/' + name; - } - - public TreeParent getParent() { - return parent; - } - - public String toString() { - return getName(); - } - - public int compareTo(TreeParent o) { - return name.compareTo(o.name); - } - - @Override - public int hashCode() { - if (path != null) - return path.hashCode(); - else - return name.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (path != null && obj instanceof TreeParent) - return path.equals(((TreeParent) obj).path); - else - return name.equals(obj.toString()); - } - -} diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/ErrorFeedback.java b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/ErrorFeedback.java deleted file mode 100644 index a388e745e..000000000 --- a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/ErrorFeedback.java +++ /dev/null @@ -1,106 +0,0 @@ -package org.argeo.eclipse.ui.dialogs; - -import java.io.PrintWriter; -import java.io.StringWriter; - -import org.argeo.api.cms.CmsLog; -import org.eclipse.jface.dialogs.IMessageProvider; -import org.eclipse.jface.dialogs.TitleAreaDialog; -import org.eclipse.swt.SWT; -import org.eclipse.swt.graphics.Point; -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.Display; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; - -/** - * Generic error dialog to be used in try/catch blocks. - * - * @deprecated Use CMS dialogs instead. - */ -@Deprecated -public class ErrorFeedback extends TitleAreaDialog { - private static final long serialVersionUID = -8918084784628179044L; - - private final static CmsLog log = CmsLog.getLog(ErrorFeedback.class); - - private final String message; - private final Throwable exception; - - public static void show(String message, Throwable e) { - // rethrow ThreaDeath in order to make sure that RAP will properly clean - // up the UI thread - if (e instanceof ThreadDeath) - throw (ThreadDeath) e; - - new ErrorFeedback(newShell(), message, e).open(); - } - - public static void show(String message) { - new ErrorFeedback(newShell(), message, null).open(); - } - - private static Shell newShell() { - return new Shell(getDisplay(), SWT.NO_TRIM); - } - - /** Tries to find a display */ - private static Display getDisplay() { - try { - Display display = Display.getCurrent(); - if (display != null) - return display; - else - return Display.getDefault(); - } catch (Exception e) { - return Display.getCurrent(); - } - } - - public ErrorFeedback(Shell parentShell, String message, Throwable e) { - super(parentShell); - setShellStyle(SWT.NO_TRIM); - this.message = message; - this.exception = e; - log.error(message, e); - } - - protected Point getInitialSize() { - if (exception != null) - return new Point(800, 600); - else - return new Point(400, 300); - } - - @Override - protected Control createDialogArea(Composite parent) { - Composite dialogarea = (Composite) super.createDialogArea(parent); - dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - Composite composite = new Composite(dialogarea, SWT.NONE); - composite.setLayout(new GridLayout(2, false)); - composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - - setMessage(message != null ? message + (exception != null ? ": " + exception.getMessage() : "") - : exception != null ? exception.getMessage() : "Unkown Error", IMessageProvider.ERROR); - - if (exception != null) { - Text stack = new Text(composite, SWT.MULTI | SWT.LEAD | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL); - stack.setEditable(false); - stack.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - StringWriter sw = new StringWriter(); - exception.printStackTrace(new PrintWriter(sw)); - stack.setText(sw.toString()); - } - - parent.pack(); - return composite; - } - - protected void configureShell(Shell shell) { - super.configureShell(shell); - shell.setText("Error"); - } -} \ No newline at end of file diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/FeedbackDialog.java b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/FeedbackDialog.java deleted file mode 100644 index f2715bc05..000000000 --- a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/FeedbackDialog.java +++ /dev/null @@ -1,141 +0,0 @@ -package org.argeo.eclipse.ui.dialogs; - -import java.io.PrintWriter; -import java.io.StringWriter; - -import org.argeo.api.cms.CmsLog; -import org.argeo.eclipse.ui.EclipseUiException; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.ShellAdapter; -import org.eclipse.swt.events.ShellEvent; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.graphics.Rectangle; -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.Display; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; - -/** - * Generic lightweight dialog, not based on JFace. - * - * @deprecated Use CMS dialogs instead. - */ -@Deprecated -public class FeedbackDialog extends LightweightDialog { - private final static CmsLog log = CmsLog.getLog(FeedbackDialog.class); - - private String message; - private Throwable exception; - -// private Shell parentShell; - private Shell shell; - - public static void show(String message, Throwable e) { - // rethrow ThreaDeath in order to make sure that RAP will properly clean - // up the UI thread - if (e instanceof ThreadDeath) - throw (ThreadDeath) e; - - new FeedbackDialog(getDisplay().getActiveShell(), message, e).open(); - } - - public static void show(String message) { - new FeedbackDialog(getDisplay().getActiveShell(), message, null).open(); - } - - /** Tries to find a display */ - private static Display getDisplay() { - try { - Display display = Display.getCurrent(); - if (display != null) - return display; - else - return Display.getDefault(); - } catch (Exception e) { - return Display.getCurrent(); - } - } - - public FeedbackDialog(Shell parentShell, String message, Throwable e) { - super(parentShell); - this.message = message; - this.exception = e; - log.error(message, e); - } - - public int open() { - if (shell != null) - throw new EclipseUiException("There is already a shell"); - shell = new Shell(getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP); - shell.setLayout(new GridLayout()); - // shell.setText("Error"); - shell.setSize(getInitialSize()); - createDialogArea(shell); - // shell.pack(); - // shell.layout(); - - Rectangle shellBounds = Display.getCurrent().getBounds();// RAP - Point dialogSize = shell.getSize(); - int x = shellBounds.x + (shellBounds.width - dialogSize.x) / 2; - int y = shellBounds.y + (shellBounds.height - dialogSize.y) / 2; - shell.setLocation(x, y); - - shell.addShellListener(new ShellAdapter() { - private static final long serialVersionUID = -2701270481953688763L; - - @Override - public void shellDeactivated(ShellEvent e) { - closeShell(); - } - }); - - shell.open(); - return OK; - } - - protected void closeShell() { - shell.close(); - shell.dispose(); - shell = null; - } - - protected Point getInitialSize() { - // if (exception != null) - // return new Point(800, 600); - // else - return new Point(400, 300); - } - - protected Control createDialogArea(Composite parent) { - Composite dialogarea = new Composite(parent, SWT.NONE); - dialogarea.setLayout(new GridLayout()); - // Composite dialogarea = (Composite) super.createDialogArea(parent); - dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - - Label messageLbl = new Label(dialogarea, SWT.NONE); - if (message != null) - messageLbl.setText(message); - else if (exception != null) - messageLbl.setText(exception.getLocalizedMessage()); - - Composite composite = new Composite(dialogarea, SWT.NONE); - composite.setLayout(new GridLayout(2, false)); - composite.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - - if (exception != null) { - Text stack = new Text(composite, SWT.MULTI | SWT.LEAD | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL); - stack.setEditable(false); - stack.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - StringWriter sw = new StringWriter(); - exception.printStackTrace(new PrintWriter(sw)); - stack.setText(sw.toString()); - } - - // parent.pack(); - return composite; - } -} \ No newline at end of file diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/LightweightDialog.java b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/LightweightDialog.java deleted file mode 100644 index 615e1417a..000000000 --- a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/LightweightDialog.java +++ /dev/null @@ -1,256 +0,0 @@ -package org.argeo.eclipse.ui.dialogs; - -import org.argeo.api.cms.CmsLog; -import org.argeo.eclipse.ui.EclipseUiException; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.FocusEvent; -import org.eclipse.swt.events.FocusListener; -import org.eclipse.swt.events.ShellAdapter; -import org.eclipse.swt.events.ShellEvent; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.graphics.Rectangle; -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.Display; -import org.eclipse.swt.widgets.Shell; - -/** Generic lightweight dialog, not based on JFace. */ -@Deprecated -public class LightweightDialog { - private final static CmsLog log = CmsLog.getLog(LightweightDialog.class); - - // must be the same value as org.eclipse.jface.window.Window#OK - public final static int OK = 0; - // must be the same value as org.eclipse.jface.window.Window#CANCEL - public final static int CANCEL = 1; - - private Shell parentShell; - private Shell backgroundShell; - private Shell foregoundShell; - - private Integer returnCode = null; - private boolean block = true; - - private String title; - - /** Tries to find a display */ - private static Display getDisplay() { - try { - Display display = Display.getCurrent(); - if (display != null) - return display; - else - return Display.getDefault(); - } catch (Exception e) { - return Display.getCurrent(); - } - } - - public LightweightDialog(Shell parentShell) { - this.parentShell = parentShell; - } - - public int open() { - if (foregoundShell != null) - throw new EclipseUiException("There is already a shell"); - backgroundShell = new Shell(parentShell, SWT.ON_TOP); - backgroundShell.setFullScreen(true); - // if (parentShell != null) { - // backgroundShell.setBounds(parentShell.getBounds()); - // } else - // backgroundShell.setMaximized(true); - backgroundShell.setAlpha(128); - backgroundShell.setBackground(getDisplay().getSystemColor(SWT.COLOR_BLACK)); - foregoundShell = new Shell(backgroundShell, SWT.NO_TRIM | SWT.ON_TOP); - if (title != null) - setTitle(title); - foregoundShell.setLayout(new GridLayout()); - foregoundShell.setSize(getInitialSize()); - createDialogArea(foregoundShell); - // shell.pack(); - // shell.layout(); - - Rectangle shellBounds = parentShell != null ? parentShell.getBounds() : Display.getCurrent().getBounds();// RAP - Point dialogSize = foregoundShell.getSize(); - int x = shellBounds.x + (shellBounds.width - dialogSize.x) / 2; - int y = shellBounds.y + (shellBounds.height - dialogSize.y) / 2; - foregoundShell.setLocation(x, y); - - foregoundShell.addShellListener(new ShellAdapter() { - private static final long serialVersionUID = -2701270481953688763L; - - @Override - public void shellDeactivated(ShellEvent e) { - if (hasChildShells()) - return; - if (returnCode == null)// not yet closed - closeShell(CANCEL); - } - - @Override - public void shellClosed(ShellEvent e) { - notifyClose(); - } - - }); - - backgroundShell.open(); - foregoundShell.open(); - // after the foreground shell has been opened - backgroundShell.addFocusListener(new FocusListener() { - private static final long serialVersionUID = 3137408447474661070L; - - @Override - public void focusLost(FocusEvent event) { - } - - @Override - public void focusGained(FocusEvent event) { - if (hasChildShells()) - return; - if (returnCode == null)// not yet closed - closeShell(CANCEL); - } - }); - - if (block) { - block(); - } - if (returnCode == null) - returnCode = OK; - return returnCode; - } - - public void block() { - try { - runEventLoop(foregoundShell); - } catch (ThreadDeath t) { - returnCode = CANCEL; - if (log.isTraceEnabled()) - log.error("Thread death, canceling dialog", t); - } catch (Throwable t) { - returnCode = CANCEL; - log.error("Cannot open blocking lightweight dialog", t); - } - } - - private boolean hasChildShells() { - if (foregoundShell == null) - return false; - return foregoundShell.getShells().length != 0; - } - - // public synchronized int openAndWait() { - // open(); - // while (returnCode == null) - // try { - // wait(100); - // } catch (InterruptedException e) { - // // silent - // } - // return returnCode; - // } - - private synchronized void notifyClose() { - if (returnCode == null) - returnCode = CANCEL; - notifyAll(); - } - - protected void closeShell(int returnCode) { - this.returnCode = returnCode; - if (CANCEL == returnCode) - onCancel(); - if (foregoundShell != null && !foregoundShell.isDisposed()) { - foregoundShell.close(); - foregoundShell.dispose(); - foregoundShell = null; - } - - if (backgroundShell != null && !backgroundShell.isDisposed()) { - backgroundShell.close(); - backgroundShell.dispose(); - } - } - - protected Point getInitialSize() { - // if (exception != null) - // return new Point(800, 600); - // else - return new Point(600, 400); - } - - protected Control createDialogArea(Composite parent) { - Composite dialogarea = new Composite(parent, SWT.NONE); - dialogarea.setLayout(new GridLayout()); - dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - return dialogarea; - } - - protected Shell getBackgroundShell() { - return backgroundShell; - } - - protected Shell getForegoundShell() { - return foregoundShell; - } - - public void setBlockOnOpen(boolean shouldBlock) { - block = shouldBlock; - } - - public void pack() { - foregoundShell.pack(); - } - - private void runEventLoop(Shell loopShell) { - Display display; - if (foregoundShell == null) { - display = Display.getCurrent(); - } else { - display = loopShell.getDisplay(); - } - - while (loopShell != null && !loopShell.isDisposed()) { - try { - if (!display.readAndDispatch()) { - display.sleep(); - } - } catch (UnsupportedOperationException e) { - throw e; - } catch (Throwable e) { - handleException(e); - } - } - if (!display.isDisposed()) - display.update(); - } - - protected void handleException(Throwable t) { - if (t instanceof ThreadDeath) { - // Don't catch ThreadDeath as this is a normal occurrence when - // the thread dies - throw (ThreadDeath) t; - } - // Try to keep running. - t.printStackTrace(); - } - - /** @return false, if the dialog should not be closed. */ - protected boolean onCancel() { - return true; - } - - public void setTitle(String title) { - this.title = title; - if (title != null && getForegoundShell() != null) - getForegoundShell().setText(title); - } - - public Integer getReturnCode() { - return returnCode; - } - -} \ No newline at end of file diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/SingleValue.java b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/SingleValue.java deleted file mode 100644 index 8ce9b44fb..000000000 --- a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/SingleValue.java +++ /dev/null @@ -1,130 +0,0 @@ -package org.argeo.eclipse.ui.dialogs; - -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.eclipse.jface.dialogs.IMessageProvider; -import org.eclipse.jface.dialogs.TitleAreaDialog; -import org.eclipse.jface.window.Window; -import org.eclipse.swt.SWT; -import org.eclipse.swt.graphics.Point; -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.Display; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; - -/** - * Dialog to retrieve a single value. - * - * @deprecated Use CMS dialogs instead. - */ -@Deprecated -public class SingleValue extends TitleAreaDialog { - private static final long serialVersionUID = 2843538207460082349L; - - private Text valueT; - private String value; - private final String title, message, label; - private final Boolean multiline; - - public static String ask(String label, String message) { - SingleValue svd = new SingleValue(label, message); - if (svd.open() == Window.OK) - return svd.getString(); - else - return null; - } - - public static Long askLong(String label, String message) { - SingleValue svd = new SingleValue(label, message); - if (svd.open() == Window.OK) - return svd.getLong(); - else - return null; - } - - public static Double askDouble(String label, String message) { - SingleValue svd = new SingleValue(label, message); - if (svd.open() == Window.OK) - return svd.getDouble(); - else - return null; - } - - public SingleValue(String label, String message) { - this(Display.getDefault().getActiveShell(), label, message, label, false); - } - - public SingleValue(Shell parentShell, String title, String message, String label, Boolean multiline) { - super(parentShell); - this.title = title; - this.message = message; - this.label = label; - this.multiline = multiline; - } - - protected Point getInitialSize() { - if (multiline) - return new Point(450, 350); - - else - return new Point(400, 270); - } - - protected Control createDialogArea(Composite parent) { - Composite dialogarea = (Composite) super.createDialogArea(parent); - dialogarea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - Composite composite = new Composite(dialogarea, SWT.NONE); - composite.setLayoutData(EclipseUiUtils.fillAll()); - GridLayout layout = new GridLayout(2, false); - layout.marginWidth = layout.marginHeight = 20; - composite.setLayout(layout); - - valueT = createLT(composite, label); - - setMessage(message, IMessageProvider.NONE); - - parent.pack(); - valueT.setFocus(); - return composite; - } - - @Override - protected void okPressed() { - value = valueT.getText(); - super.okPressed(); - } - - /** Creates label and text. */ - protected Text createLT(Composite parent, String label) { - new Label(parent, SWT.NONE).setText(label); - Text text; - if (multiline) { - text = new Text(parent, SWT.LEAD | SWT.BORDER | SWT.MULTI); - text.setLayoutData(EclipseUiUtils.fillAll()); - } else { - text = new Text(parent, SWT.LEAD | SWT.BORDER | SWT.SINGLE); - text.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, true)); - } - return text; - } - - protected void configureShell(Shell shell) { - super.configureShell(shell); - shell.setText(title); - } - - public String getString() { - return value; - } - - public Long getLong() { - return Long.valueOf(getString()); - } - - public Double getDouble() { - return Double.valueOf(getString()); - } -} diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/package-info.java b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/package-info.java deleted file mode 100644 index d6ab1481e..000000000 --- a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/dialogs/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Generic SWT/JFace dialogs. */ -package org.argeo.eclipse.ui.dialogs; \ No newline at end of file diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/AdvancedFsBrowser.java b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/AdvancedFsBrowser.java deleted file mode 100644 index c01b2d751..000000000 --- a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/AdvancedFsBrowser.java +++ /dev/null @@ -1,450 +0,0 @@ -package org.argeo.eclipse.ui.fs; - -import java.io.IOException; -import java.nio.file.DirectoryStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.LinkedHashMap; - -import org.argeo.api.cms.CmsLog; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.eclipse.jface.viewers.ISelectionChangedListener; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.SelectionChangedEvent; -import org.eclipse.jface.viewers.StructuredSelection; -import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.SashForm; -import org.eclipse.swt.custom.ScrolledComposite; -import org.eclipse.swt.events.ControlAdapter; -import org.eclipse.swt.events.ControlEvent; -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.graphics.Rectangle; -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.Label; -import org.eclipse.swt.widgets.Table; -import org.eclipse.swt.widgets.Text; - -/** Simple UI provider that populates a composite parent given a NIO path */ -public class AdvancedFsBrowser { - private final static CmsLog log = CmsLog.getLog(AdvancedFsBrowser.class); - - // Some local constants to experiment. should be cleaned - // private final static int THUMBNAIL_WIDTH = 400; - // private Point imageWidth = new Point(250, 0); - private final static int COLUMN_WIDTH = 160; - - private Path initialPath; - private Path currEdited; - // Filter - private Composite displayBoxCmp; - private Text parentPathTxt; - private Text filterTxt; - // Browser columns - private ScrolledComposite scrolledCmp; - // Keep a cache of the opened directories - private LinkedHashMap browserCols = new LinkedHashMap<>(); - private Composite scrolledCmpBody; - - public Control createUi(Composite parent, Path basePath) { - if (basePath == null) - throw new IllegalArgumentException("Context cannot be null"); - parent.setLayout(new GridLayout()); - - // top filter - Composite filterCmp = new Composite(parent, SWT.NO_FOCUS); - filterCmp.setLayoutData(EclipseUiUtils.fillWidth()); - addFilterPanel(filterCmp); - - // Bottom part a sash with browser on the left - SashForm form = new SashForm(parent, SWT.HORIZONTAL); - // form.setLayout(new FillLayout()); - form.setLayoutData(EclipseUiUtils.fillAll()); - Composite leftCmp = new Composite(form, SWT.NO_FOCUS); - displayBoxCmp = new Composite(form, SWT.NONE); - form.setWeights(new int[] { 3, 1 }); - - createBrowserPart(leftCmp, basePath); - // leftCmp.addControlListener(new ControlAdapter() { - // @Override - // public void controlResized(ControlEvent e) { - // Rectangle r = leftCmp.getClientArea(); - // log.warn("Browser resized: " + r.toString()); - // scrolledCmp.setMinSize(browserCols.size() * (COLUMN_WIDTH + 2), - // SWT.DEFAULT); - // // scrolledCmp.setMinSize(scrolledCmpBody.computeSize(SWT.DEFAULT, - // // r.height)); - // } - // }); - - populateCurrEditedDisplay(displayBoxCmp, basePath); - - // INIT - setEdited(basePath); - initialPath = basePath; - // form.layout(true, true); - return parent; - } - - private void createBrowserPart(Composite parent, Path context) { - parent.setLayout(EclipseUiUtils.noSpaceGridLayout()); - - // scrolled composite - scrolledCmp = new ScrolledComposite(parent, SWT.H_SCROLL | SWT.BORDER | SWT.NO_FOCUS); - scrolledCmp.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - scrolledCmp.setExpandVertical(true); - scrolledCmp.setExpandHorizontal(true); - scrolledCmp.setShowFocusedControl(true); - - scrolledCmpBody = new Composite(scrolledCmp, SWT.NO_FOCUS); - scrolledCmp.setContent(scrolledCmpBody); - scrolledCmpBody.addControlListener(new ControlAdapter() { - private static final long serialVersionUID = 183238447102854553L; - - @Override - public void controlResized(ControlEvent e) { - Rectangle r = scrolledCmp.getClientArea(); - scrolledCmp.setMinSize(scrolledCmpBody.computeSize(SWT.DEFAULT, r.height)); - } - }); - initExplorer(scrolledCmpBody, context); - scrolledCmpBody.layout(true, true); - scrolledCmp.layout(); - - } - - private Control initExplorer(Composite parent, Path context) { - parent.setLayout(EclipseUiUtils.noSpaceGridLayout()); - return createBrowserColumn(parent, context); - } - - private Control createBrowserColumn(Composite parent, Path context) { - // TODO style is not correctly managed. - FilterEntitiesVirtualTable table = new FilterEntitiesVirtualTable(parent, SWT.BORDER | SWT.NO_FOCUS, context); - // CmsUtils.style(table, ArgeoOrgStyle.browserColumn.style()); - table.filterList("*"); - table.setLayoutData(new GridData(SWT.LEFT, SWT.FILL, false, true)); - browserCols.put(context, table); - parent.layout(true, true); - return table; - } - - public void addFilterPanel(Composite parent) { - parent.setLayout(EclipseUiUtils.noSpaceGridLayout(new GridLayout(2, false))); - - parentPathTxt = new Text(parent, SWT.NO_FOCUS); - parentPathTxt.setEditable(false); - - filterTxt = new Text(parent, SWT.SEARCH | SWT.ICON_CANCEL); - filterTxt.setMessage("Filter current list"); - filterTxt.setLayoutData(EclipseUiUtils.fillWidth()); - filterTxt.addModifyListener(new ModifyListener() { - private static final long serialVersionUID = 1L; - - public void modifyText(ModifyEvent event) { - modifyFilter(false); - } - }); - filterTxt.addKeyListener(new KeyListener() { - private static final long serialVersionUID = 2533535233583035527L; - - @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; - FilterEntitiesVirtualTable currTable = null; - if (currEdited != null) { - FilterEntitiesVirtualTable table = browserCols.get(currEdited); - if (table != null && !table.isDisposed()) - currTable = table; - } - - if (e.keyCode == SWT.ARROW_DOWN) - currTable.setFocus(); - else if (e.keyCode == SWT.BS) { - if (filterTxt.getText().equals("") - && !(currEdited.getNameCount() == 1 || currEdited.equals(initialPath))) { - Path oldEdited = currEdited; - Path parentPath = currEdited.getParent(); - setEdited(parentPath); - if (browserCols.containsKey(parentPath)) - browserCols.get(parentPath).setSelected(oldEdited); - filterTxt.setFocus(); - e.doit = false; - } - } else if (e.keyCode == SWT.TAB && !shiftPressed) { - Path uniqueChild = getOnlyChild(currEdited, filterTxt.getText()); - if (uniqueChild != null) { - // Highlight the unique chosen child - currTable.setSelected(uniqueChild); - setEdited(uniqueChild); - } - filterTxt.setFocus(); - e.doit = false; - } - } - }); - } - - private Path getOnlyChild(Path parent, String filter) { - try (DirectoryStream stream = Files.newDirectoryStream(currEdited, filter + "*")) { - Path uniqueChild = null; - boolean moreThanOne = false; - loop: for (Path entry : stream) { - if (uniqueChild == null) { - uniqueChild = entry; - } else { - moreThanOne = true; - break loop; - } - } - if (!moreThanOne) - return uniqueChild; - return null; - } catch (IOException ioe) { - throw new FsUiException( - "Unable to determine unique child existence and get it under " + parent + " with filter " + filter, - ioe); - } - } - - private void setEdited(Path path) { - currEdited = path; - EclipseUiUtils.clear(displayBoxCmp); - populateCurrEditedDisplay(displayBoxCmp, currEdited); - refreshFilters(path); - refreshBrowser(path); - } - - private void refreshFilters(Path path) { - parentPathTxt.setText(path.toUri().toString()); - filterTxt.setText(""); - filterTxt.getParent().layout(); - } - - private void refreshBrowser(Path currPath) { - Path currParPath = currPath.getParent(); - Object[][] colMatrix = new Object[browserCols.size()][2]; - - int i = 0, currPathIndex = -1, lastLeftOpenedIndex = -1; - for (Path path : browserCols.keySet()) { - colMatrix[i][0] = path; - colMatrix[i][1] = browserCols.get(path); - if (currPathIndex >= 0 && lastLeftOpenedIndex < 0 && currParPath != null) { - boolean leaveOpened = path.startsWith(currPath); - if (!leaveOpened) - lastLeftOpenedIndex = i; - } - if (currParPath.equals(path)) - currPathIndex = i; - i++; - } - - if (currPathIndex >= 0 && lastLeftOpenedIndex >= 0) { - // dispose and remove useless cols - for (int l = i - 1; l >= lastLeftOpenedIndex; l--) { - ((FilterEntitiesVirtualTable) colMatrix[l][1]).dispose(); - browserCols.remove(colMatrix[l][0]); - } - } - - if (browserCols.containsKey(currPath)) { - FilterEntitiesVirtualTable currCol = browserCols.get(currPath); - if (currCol.isDisposed()) { - // Does it still happen ? - log.warn(currPath + " browser column was disposed and still listed"); - browserCols.remove(currPath); - } - } - - if (!browserCols.containsKey(currPath) && Files.isDirectory(currPath)) - createBrowserColumn(scrolledCmpBody, currPath); - - scrolledCmpBody.setLayout(EclipseUiUtils.noSpaceGridLayout(new GridLayout(browserCols.size(), false))); - scrolledCmpBody.layout(true, true); - // also resize the scrolled composite - scrolledCmp.layout(); - } - - private void modifyFilter(boolean fromOutside) { - if (!fromOutside) - if (currEdited != null) { - String filter = filterTxt.getText() + "*"; - FilterEntitiesVirtualTable table = browserCols.get(currEdited); - if (table != null && !table.isDisposed()) - table.filterList(filter); - } - } - - /** - * Recreates the content of the box that displays information about the current - * selected node. - */ - private void populateCurrEditedDisplay(Composite parent, Path context) { - parent.setLayout(new GridLayout()); - - // if (isImg(context)) { - // EditableImage image = new Img(parent, RIGHT, context, imageWidth); - // image.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, false, - // 2, 1)); - // } - - try { - Label contextL = new Label(parent, SWT.NONE); - contextL.setText(context.getFileName().toString()); - contextL.setFont(EclipseUiUtils.getBoldFont(parent)); - addProperty(parent, "Last modified", Files.getLastModifiedTime(context).toString()); - addProperty(parent, "Owner", Files.getOwner(context).getName()); - if (Files.isDirectory(context)) { - addProperty(parent, "Type", "Folder"); - } else { - String mimeType = Files.probeContentType(context); - if (EclipseUiUtils.isEmpty(mimeType)) - mimeType = "Unknown"; - addProperty(parent, "Type", mimeType); - addProperty(parent, "Size", FsUiUtils.humanReadableByteCount(Files.size(context), false)); - } - parent.layout(true, true); - } catch (IOException e) { - throw new FsUiException("Cannot display details for " + context, e); - } - } - - private void addProperty(Composite parent, String propName, String value) { - Label contextL = new Label(parent, SWT.NONE); - contextL.setText(propName + ": " + value); - } - - /** - * Almost canonical implementation of a table that displays the content of a - * directory - */ - private class FilterEntitiesVirtualTable extends Composite { - private static final long serialVersionUID = 2223410043691844875L; - - // Context - private Path context; - private Path currSelected = null; - - // UI Objects - private FsTableViewer viewer; - - @Override - public boolean setFocus() { - if (viewer.getTable().isDisposed()) - return false; - if (currSelected != null) - viewer.setSelection(new StructuredSelection(currSelected), true); - else if (viewer.getSelection().isEmpty()) { - Object first = viewer.getElementAt(0); - if (first != null) - viewer.setSelection(new StructuredSelection(first), true); - } - return viewer.getTable().setFocus(); - } - - /** - * Enable highlighting the correct element in the table when externally browsing - * (typically via the command-line-like Text field) - */ - void setSelected(Path selected) { - // to prevent change selection event to be thrown - currSelected = selected; - viewer.setSelection(new StructuredSelection(currSelected), true); - } - - void filterList(String filter) { - viewer.setInput(context, filter); - } - - public FilterEntitiesVirtualTable(Composite parent, int style, Path context) { - super(parent, SWT.NO_FOCUS); - this.context = context; - createTableViewer(this); - } - - private void createTableViewer(final Composite parent) { - parent.setLayout(EclipseUiUtils.noSpaceGridLayout()); - - // We must limit the size of the table otherwise the full list is - // loaded before the layout happens - // Composite listCmp = new Composite(parent, SWT.NO_FOCUS); - // GridData gd = new GridData(SWT.LEFT, SWT.FILL, false, true); - // gd.widthHint = COLUMN_WIDTH; - // listCmp.setLayoutData(gd); - // listCmp.setLayout(EclipseUiUtils.noSpaceGridLayout()); - // viewer = new TableViewer(listCmp, SWT.VIRTUAL | SWT.MULTI | - // SWT.V_SCROLL); - // Table table = viewer.getTable(); - // table.setLayoutData(EclipseUiUtils.fillAll()); - - viewer = new FsTableViewer(parent, SWT.MULTI); - Table table = viewer.configureDefaultSingleColumnTable(COLUMN_WIDTH); - - viewer.addSelectionChangedListener(new ISelectionChangedListener() { - - @Override - public void selectionChanged(SelectionChangedEvent event) { - IStructuredSelection selection = (IStructuredSelection) viewer.getSelection(); - if (selection.isEmpty()) - return; - Object obj = selection.getFirstElement(); - Path newSelected; - if (obj instanceof Path) - newSelected = (Path) obj; - else if (obj instanceof ParentDir) - newSelected = ((ParentDir) obj).getPath(); - else - return; - if (newSelected.equals(currSelected)) - return; - currSelected = newSelected; - setEdited(newSelected); - - } - }); - - table.addKeyListener(new KeyListener() { - private static final long serialVersionUID = -8083424284436715709L; - - @Override - public void keyReleased(KeyEvent e) { - } - - @Override - public void keyPressed(KeyEvent e) { - IStructuredSelection selection = (IStructuredSelection) viewer.getSelection(); - Path selected = null; - if (!selection.isEmpty()) - selected = ((Path) selection.getFirstElement()); - if (e.keyCode == SWT.ARROW_RIGHT) { - if (!Files.isDirectory(selected)) - return; - if (selected != null) { - setEdited(selected); - browserCols.get(selected).setFocus(); - } - } else if (e.keyCode == SWT.ARROW_LEFT) { - if (context.equals(initialPath)) - return; - Path parent = context.getParent(); - if (parent == null) - return; - - setEdited(parent); - browserCols.get(parent).setFocus(); - } - } - }); - } - } -} diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FileIconNameLabelProvider.java b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FileIconNameLabelProvider.java deleted file mode 100644 index d3fc1c903..000000000 --- a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FileIconNameLabelProvider.java +++ /dev/null @@ -1,84 +0,0 @@ -package org.argeo.eclipse.ui.fs; - -import java.nio.file.Files; -import java.nio.file.Path; - -import org.eclipse.jface.resource.ImageDescriptor; -import org.eclipse.jface.viewers.ColumnLabelProvider; -import org.eclipse.swt.graphics.Image; - -/** Basic label provider with icon for NIO file viewers */ -public class FileIconNameLabelProvider extends ColumnLabelProvider { - private static final long serialVersionUID = 8187902187946523148L; - - private Image folderIcon; - private Image fileIcon; - - public FileIconNameLabelProvider() { - // if (!PlatformUI.isWorkbenchRunning()) { - folderIcon = ImageDescriptor.createFromFile(getClass(), "folder.png").createImage(); - fileIcon = ImageDescriptor.createFromFile(getClass(), "file.png").createImage(); - // } - } - - @Override - public void dispose() { - if (folderIcon != null) - folderIcon.dispose(); - if (fileIcon != null) - fileIcon.dispose(); - super.dispose(); - } - - @Override - public String getText(Object element) { - if (element instanceof Path) { - Path curr = ((Path) element); - Path name = curr.getFileName(); - if (name == null) - return "[No name]"; - else - return name.toString(); - } else if (element instanceof ParentDir) { - return ".."; - } - return null; - } - - @Override - public Image getImage(Object element) { - if (element instanceof Path) { - Path curr = ((Path) element); - if (Files.isDirectory(curr)) - // if (folderIcon != null) - return folderIcon; - // else - // return - // PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FOLDER); - // else if (fileIcon != null) - return fileIcon; - // else - // return - // PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_OBJ_FILE); - } else if (element instanceof ParentDir) { - return folderIcon; - } - return null; - } - - @Override - public String getToolTipText(Object element) { - if (element instanceof Path) { - Path curr = ((Path) element); - Path name = curr.getFileName(); - if (name == null) - return "[No name]"; - else - return name.toAbsolutePath().toString(); - } else if (element instanceof ParentDir) { - return ((ParentDir) element).getPath().toAbsolutePath().toString(); - } - return null; - } - -} \ No newline at end of file diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTableViewer.java b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTableViewer.java deleted file mode 100644 index 3b126e90b..000000000 --- a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTableViewer.java +++ /dev/null @@ -1,141 +0,0 @@ -package org.argeo.eclipse.ui.fs; - -import java.nio.file.Path; -import java.util.List; - -import org.argeo.eclipse.ui.ColumnDefinition; -import org.eclipse.jface.viewers.CellLabelProvider; -import org.eclipse.jface.viewers.ILazyContentProvider; -import org.eclipse.jface.viewers.TableViewer; -import org.eclipse.jface.viewers.TableViewerColumn; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Table; -import org.eclipse.swt.widgets.TableColumn; - -/** - * Canonical implementation of a JFace table viewer to display the content of a - * file folder - */ -public class FsTableViewer extends TableViewer { - private static final long serialVersionUID = -5632407542678477234L; - - private boolean showHiddenItems = false; - private boolean folderFirst = true; - private boolean reverseOrder = false; - private String orderProperty = FsUiConstants.PROPERTY_NAME; - - private Path initialPath = null; - - public FsTableViewer(Composite parent, int style) { - super(parent, style | SWT.VIRTUAL); - } - - public Table configureDefaultSingleColumnTable(int tableWidthHint) { - - return configureDefaultSingleColumnTable(tableWidthHint, new FileIconNameLabelProvider()); - } - - public Table configureDefaultSingleColumnTable(int tableWidthHint, CellLabelProvider labelProvider) { - Table table = this.getTable(); - table.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); - table.setLinesVisible(false); - table.setHeaderVisible(false); - // CmsUtils.markup(table); - // CmsUtils.style(table, MaintenanceStyles.BROWSER_COLUMN); - - TableViewerColumn column = new TableViewerColumn(this, SWT.NONE); - TableColumn tcol = column.getColumn(); - tcol.setWidth(tableWidthHint); - column.setLabelProvider(labelProvider); - this.setContentProvider(new MyLazyCP()); - return table; - } - - public Table configureDefaultTable(List columns) { - this.setContentProvider(new MyLazyCP()); - Table table = this.getTable(); - table.setLinesVisible(true); - table.setHeaderVisible(true); - // CmsUtils.markup(table); - // CmsUtils.style(table, MaintenanceStyles.BROWSER_COLUMN); - for (ColumnDefinition colDef : columns) { - TableViewerColumn column = new TableViewerColumn(this, SWT.NONE); - column.setLabelProvider(colDef.getLabelProvider()); - TableColumn tcol = column.getColumn(); - tcol.setResizable(true); - tcol.setText(colDef.getLabel()); - tcol.setWidth(colDef.getMinWidth()); - } - return table; - } - - public void setInput(Path dir, String filter) { - Path[] rows = FsUiUtils.getChildren(dir, filter, showHiddenItems, folderFirst, orderProperty, reverseOrder); - if (rows == null) { - this.setInput(null); - this.setItemCount(0); - return; - } - boolean isRoot; - try { - isRoot = dir.getRoot().equals(dir); - } catch (Exception e) { - // FIXME Workaround for JCR root node access - isRoot = dir.toString().equals("/"); - } - final Object[] res; - if (isRoot) - res = rows; - else if (initialPath != null && initialPath.equals(dir)) - res = rows; - else { - res = new Object[rows.length + 1]; - res[0] = new ParentDir(dir.getParent()); - for (int i = 1; i < res.length; i++) { - res[i] = rows[i - 1]; - } - } - this.setInput(res); - int length = res.length; - this.setItemCount(length); - this.refresh(); - } - - /** Directly displays bookmarks **/ - public void setPathsInput(Path... paths) { - this.setInput((Object[]) paths); - this.setItemCount(paths.length); - this.refresh(); - } - - /** - * A path which is to be considered as root (and thus provide no link to a - * parent directory) - */ - public void setInitialPath(Path initialPath) { - this.initialPath = initialPath; - } - - private class MyLazyCP implements ILazyContentProvider { - private static final long serialVersionUID = 9096550041395433128L; - private Object[] elements; - - public void dispose() { - } - - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - // IMPORTANT: don't forget this: an exception will be thrown if - // a selected object is not part of the results anymore. - viewer.setSelection(null); - this.elements = (Object[]) newInput; - } - - public void updateElement(int index) { - if (index < elements.length) - FsTableViewer.this.replace(elements[index], index); - } - } -} diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTreeViewer.java b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTreeViewer.java deleted file mode 100644 index f55ead718..000000000 --- a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsTreeViewer.java +++ /dev/null @@ -1,144 +0,0 @@ -package org.argeo.eclipse.ui.fs; - -import java.io.IOException; -import java.nio.file.DirectoryIteratorException; -import java.nio.file.DirectoryStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; - -import org.argeo.eclipse.ui.ColumnDefinition; -import org.eclipse.jface.viewers.ITreeContentProvider; -import org.eclipse.jface.viewers.TreeViewer; -import org.eclipse.jface.viewers.TreeViewerColumn; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Tree; -import org.eclipse.swt.widgets.TreeColumn; - -/** - * Canonical implementation of a JFace TreeViewer to display the content of a - * repository - */ -public class FsTreeViewer extends TreeViewer { - private static final long serialVersionUID = -5632407542678477234L; - - private boolean showHiddenItems = false; - private boolean showDirectoryFirst = true; - private String orderingProperty = FsUiConstants.PROPERTY_NAME; - - public FsTreeViewer(Composite parent, int style) { - super(parent, style | SWT.VIRTUAL); - } - - public Tree configureDefaultSingleColumnTable(int tableWidthHint) { - Tree tree = this.getTree(); - tree.setLayoutData(new GridData(SWT.FILL, SWT.FILL, false, false)); - tree.setLinesVisible(true); - tree.setHeaderVisible(false); -// CmsUtils.markup(tree); - - TreeViewerColumn column = new TreeViewerColumn(this, SWT.NONE); - TreeColumn tcol = column.getColumn(); - tcol.setWidth(tableWidthHint); - column.setLabelProvider(new FileIconNameLabelProvider()); - - this.setContentProvider(new MyCP()); - return tree; - } - - public Tree configureDefaultTable(List columns) { - this.setContentProvider(new MyCP()); - Tree tree = this.getTree(); - tree.setLinesVisible(true); - tree.setHeaderVisible(true); -// CmsUtils.markup(tree); -// CmsUtils.style(tree, MaintenanceStyles.BROWSER_COLUMN); - for (ColumnDefinition colDef : columns) { - TreeViewerColumn column = new TreeViewerColumn(this, SWT.NONE); - column.setLabelProvider(colDef.getLabelProvider()); - TreeColumn tcol = column.getColumn(); - tcol.setResizable(true); - tcol.setText(colDef.getLabel()); - tcol.setWidth(colDef.getMinWidth()); - } - return tree; - } - - public void setInput(Path dir, String filter) { - try (DirectoryStream stream = Files.newDirectoryStream(dir, filter)) { - // TODO make this lazy - List paths = new ArrayList<>(); - for (Path entry : stream) { - paths.add(entry); - } - Object[] rows = paths.toArray(new Object[0]); - this.setInput(rows); - // this.setItemCount(rows.length); - this.refresh(); - } catch (IOException | DirectoryIteratorException e) { - throw new FsUiException("Unable to filter " + dir + " children with filter " + filter, e); - } - } - - /** Directly displays bookmarks **/ - public void setPathsInput(Path... paths) { - this.setInput((Object[]) paths); - // this.setItemCount(paths.length); - this.refresh(); - } - - private class MyCP implements ITreeContentProvider { - private static final long serialVersionUID = 9096550041395433128L; - private Object[] elements; - - public void dispose() { - } - - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - // IMPORTANT: don't forget this: an exception will be thrown if - // a selected object is not part of the results anymore. - viewer.setSelection(null); - this.elements = (Object[]) newInput; - } - - @Override - public Object[] getElements(Object inputElement) { - return elements; - } - - @Override - public Object[] getChildren(Object parentElement) { - Path path = (Path) parentElement; - if (!Files.isDirectory(path)) - return null; - else - return FsUiUtils.getChildren(path, "*", showHiddenItems, showDirectoryFirst, orderingProperty, false); - } - - @Override - public Object getParent(Object element) { - Path path = (Path) element; - return path.getParent(); - } - - @Override - public boolean hasChildren(Object element) { - Path path = (Path) element; - try { - if (!Files.isDirectory(path)) - return false; - else - try (DirectoryStream children = Files.newDirectoryStream(path, "*")) { - return children.iterator().hasNext(); - } - } catch (IOException e) { - throw new FsUiException("Unable to check child existence on " + path, e); - } - } - - } -} diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiConstants.java b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiConstants.java deleted file mode 100644 index 2b51e71a2..000000000 --- a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiConstants.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.argeo.eclipse.ui.fs; - -/** Centralize constants used by the Nio FS UI parts */ -public interface FsUiConstants { - - // TODO use standard properties - String PROPERTY_NAME = "name"; - String PROPERTY_SIZE = "size"; - String PROPERTY_LAST_MODIFIED = "last-modified"; - String PROPERTY_TYPE = "type"; -} diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiException.java b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiException.java deleted file mode 100644 index 422b0e1ad..000000000 --- a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiException.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.argeo.eclipse.ui.fs; - -/** Files specific exception */ -public class FsUiException extends RuntimeException { - private static final long serialVersionUID = 1L; - - public FsUiException(String message) { - super(message); - } - - public FsUiException(String message, Throwable e) { - super(message, e); - } -} diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiUtils.java b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiUtils.java deleted file mode 100644 index 956d96bb5..000000000 --- a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/FsUiUtils.java +++ /dev/null @@ -1,132 +0,0 @@ -package org.argeo.eclipse.ui.fs; - -import java.io.IOException; -import java.nio.file.DirectoryIteratorException; -import java.nio.file.DirectoryStream; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -/** Centralise additional utilitary methods to manage Java7 NIO files */ -public class FsUiUtils { - - /** - * thanks to - * http://programming.guide/java/formatting-byte-size-to-human-readable-format.html - */ - public static String humanReadableByteCount(long bytes, boolean si) { - int unit = si ? 1000 : 1024; - if (bytes < unit) - return bytes + " B"; - int exp = (int) (Math.log(bytes) / Math.log(unit)); - String pre = (si ? "kMGTPE" : "KMGTPE").charAt(exp - 1) + (si ? "" : "i"); - return String.format("%.1f %sB", bytes / Math.pow(unit, exp), pre); - } - - public static Path[] getChildren(Path parent, String filter, boolean showHiddenItems, boolean folderFirst, - String orderProperty, boolean reverseOrder) { - if (!Files.isDirectory(parent)) - return null; - List pairs = new ArrayList<>(); - try (DirectoryStream stream = Files.newDirectoryStream(parent, filter)) { - loop: for (Path entry : stream) { - if (!showHiddenItems) - if (Files.isHidden(entry)) - continue loop; - switch (orderProperty) { - case FsUiConstants.PROPERTY_SIZE: - if (folderFirst) - pairs.add(new LPair(entry, Files.size(entry), Files.isDirectory(entry))); - else - pairs.add(new LPair(entry, Files.size(entry))); - break; - case FsUiConstants.PROPERTY_LAST_MODIFIED: - if (folderFirst) - pairs.add(new LPair(entry, Files.getLastModifiedTime(entry).toMillis(), - Files.isDirectory(entry))); - else - pairs.add(new LPair(entry, Files.getLastModifiedTime(entry).toMillis())); - break; - case FsUiConstants.PROPERTY_NAME: - if (folderFirst) - pairs.add(new SPair(entry, entry.getFileName().toString(), Files.isDirectory(entry))); - else - pairs.add(new SPair(entry, entry.getFileName().toString())); - break; - default: - throw new FsUiException("Unable to prepare sort for property " + orderProperty); - } - } - Pair[] rows = pairs.toArray(new Pair[0]); - Arrays.sort(rows); - Path[] results = new Path[rows.length]; - if (reverseOrder) { - int j = rows.length - 1; - for (int i = 0; i < rows.length; i++) - results[i] = rows[j - i].p; - } else - for (int i = 0; i < rows.length; i++) - results[i] = rows[i].p; - return results; - } catch (IOException | DirectoryIteratorException e) { - throw new FsUiException("Unable to filter " + parent + " children with filter " + filter, e); - } - } - - static abstract class Pair implements Comparable { - Path p; - Boolean i; - }; - - static class LPair extends Pair { - long v; - - public LPair(Path path, long propValue) { - p = path; - v = propValue; - } - - public LPair(Path path, long propValue, boolean isDir) { - p = path; - v = propValue; - i = isDir; - } - - public int compareTo(Object o) { - if (i != null) { - Boolean j = ((LPair) o).i; - if (i.booleanValue() != j.booleanValue()) - return i.booleanValue() ? -1 : 1; - } - long u = ((LPair) o).v; - return v < u ? -1 : v == u ? 0 : 1; - } - }; - - static class SPair extends Pair { - String v; - - public SPair(Path path, String propValue) { - p = path; - v = propValue; - } - - public SPair(Path path, String propValue, boolean isDir) { - p = path; - v = propValue; - i = isDir; - } - - public int compareTo(Object o) { - if (i != null) { - Boolean j = ((SPair) o).i; - if (i.booleanValue() != j.booleanValue()) - return i.booleanValue() ? -1 : 1; - } - String u = ((SPair) o).v; - return v.compareTo(u); - } - }; -} diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/NioFileLabelProvider.java b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/NioFileLabelProvider.java deleted file mode 100644 index 2bb65eed0..000000000 --- a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/NioFileLabelProvider.java +++ /dev/null @@ -1,78 +0,0 @@ -package org.argeo.eclipse.ui.fs; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.attribute.FileTime; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.Date; - -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.eclipse.jface.viewers.ColumnLabelProvider; - -/** Expect a {@link Path} as input element */ -public class NioFileLabelProvider extends ColumnLabelProvider { - private final static FileTime EPOCH = FileTime.fromMillis(0); - private static final long serialVersionUID = 2160026425187796930L; - private final String propName; - private DateFormat dateFormat = new SimpleDateFormat("YYYY-MM-dd HH:mm"); - - // TODO use new formatting - // DateTimeFormatter formatter = - // DateTimeFormatter.ofLocalizedDateTime( FormatStyle.SHORT ) - // .withLocale( Locale.UK ) - // .withZone( ZoneId.systemDefault() ); - public NioFileLabelProvider(String propName) { - this.propName = propName; - } - - @Override - public String getText(Object element) { - try { - Path path; - if (element instanceof ParentDir) { -// switch (propName) { -// case FsUiConstants.PROPERTY_SIZE: -// return "-"; -// case FsUiConstants.PROPERTY_LAST_MODIFIED: -// return "-"; -// // return Files.getLastModifiedTime(((ParentDir) element).getPath()).toString(); -// case FsUiConstants.PROPERTY_TYPE: -// return "Folder"; -// } - path = ((ParentDir) element).getPath(); - } else - path = (Path) element; - switch (propName) { - case FsUiConstants.PROPERTY_SIZE: - if (Files.isDirectory(path)) - return "-"; - else - return FsUiUtils.humanReadableByteCount(Files.size(path), false); - case FsUiConstants.PROPERTY_LAST_MODIFIED: - if (Files.isDirectory(path)) - return "-"; - FileTime time = Files.getLastModifiedTime(path); - if (time.equals(EPOCH)) - return "-"; - else - return dateFormat.format(new Date(time.toMillis())); - case FsUiConstants.PROPERTY_TYPE: - if (Files.isDirectory(path)) - return "Folder"; - else { - String mimeType = Files.probeContentType(path); - if (EclipseUiUtils.isEmpty(mimeType)) - return "Unknown"; - else - return mimeType; - } - default: - throw new IllegalArgumentException("Unsupported property " + propName); - } - } catch (IOException ioe) { - throw new FsUiException("Cannot get property " + propName + " on " + element); - } - } -} diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/ParentDir.java b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/ParentDir.java deleted file mode 100644 index 6f09c2905..000000000 --- a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/ParentDir.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.argeo.eclipse.ui.fs; - -import java.nio.file.Path; - -/** A parent directory (..) reference. */ -public class ParentDir { - Path path; - - public ParentDir(Path path) { - super(); - this.path = path; - } - - public Path getPath() { - return path; - } - - @Override - public int hashCode() { - return path.hashCode(); - } - - @Override - public String toString() { - return "Parent dir " + path; - } - -} diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsBrowser.java b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsBrowser.java deleted file mode 100644 index 2e3d6b405..000000000 --- a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsBrowser.java +++ /dev/null @@ -1,211 +0,0 @@ -package org.argeo.eclipse.ui.fs; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; - -import org.argeo.api.cms.CmsLog; -import org.argeo.eclipse.ui.ColumnDefinition; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.eclipse.jface.viewers.DoubleClickEvent; -import org.eclipse.jface.viewers.IDoubleClickListener; -import org.eclipse.jface.viewers.ISelectionChangedListener; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.SelectionChangedEvent; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.SashForm; -import org.eclipse.swt.events.KeyEvent; -import org.eclipse.swt.events.KeyListener; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Table; - -/** - * Experimental UI upon Java 7 nio files api: SashForm layout with bookmarks on - * the left hand side and a simple table on the right hand side. - */ -public class SimpleFsBrowser extends Composite { - private final static CmsLog log = CmsLog.getLog(SimpleFsBrowser.class); - private static final long serialVersionUID = -40347919096946585L; - - private Path currSelected; - private FsTableViewer bookmarksViewer; - private FsTableViewer directoryDisplayViewer; - - public SimpleFsBrowser(Composite parent, int style) { - super(parent, style); - createContent(this); - // parent.layout(true, true); - } - - public Viewer getViewer() { - return directoryDisplayViewer; - } - - private void createContent(Composite parent) { - parent.setLayout(EclipseUiUtils.noSpaceGridLayout()); - - SashForm form = new SashForm(parent, SWT.HORIZONTAL); - Composite leftCmp = new Composite(form, SWT.NONE); - populateBookmarks(leftCmp); - - Composite rightCmp = new Composite(form, SWT.BORDER); - populateDisplay(rightCmp); - form.setLayoutData(EclipseUiUtils.fillAll()); - form.setWeights(new int[] { 1, 3 }); - } - - public void setInput(Path... paths) { - bookmarksViewer.setPathsInput(paths); - bookmarksViewer.getTable().getParent().layout(true, true); - } - - private void populateBookmarks(final Composite parent) { - // GridLayout layout = EclipseUiUtils.noSpaceGridLayout(); - // layout.verticalSpacing = 5; - parent.setLayout(new GridLayout()); - - ISelectionChangedListener selList = new MySelectionChangedListener(); - - appendTitle(parent, "My bookmarks"); - bookmarksViewer = new FsTableViewer(parent, SWT.MULTI | SWT.NO_SCROLL); - Table table = bookmarksViewer.configureDefaultSingleColumnTable(500); - GridData gd = EclipseUiUtils.fillWidth(); - gd.horizontalIndent = 10; - table.setLayoutData(gd); - bookmarksViewer.addSelectionChangedListener(selList); - - appendTitle(parent, "Jcr + File"); - - FsTableViewer jcrFilesViewers = new FsTableViewer(parent, SWT.MULTI | SWT.NO_SCROLL); - table = jcrFilesViewers.configureDefaultSingleColumnTable(500); - gd = EclipseUiUtils.fillWidth(); - gd.horizontalIndent = 10; - table.setLayoutData(gd); - jcrFilesViewers.addSelectionChangedListener(selList); - - // FileSystemProvider fsProvider = new JackrabbitMemoryFsProvider(); - // try { - // Path testPath = fsProvider.getPath(new URI("jcr+memory:/")); - // jcrFilesViewers.setPathsInput(testPath); - // } catch (URISyntaxException e) { - // // TODO Auto-generated catch block - // e.printStackTrace(); - // } - } - - private Label appendTitle(Composite parent, String value) { - Label titleLbl = new Label(parent, SWT.NONE); - titleLbl.setText(value); - titleLbl.setFont(EclipseUiUtils.getBoldFont(parent)); - GridData gd = EclipseUiUtils.fillWidth(); - gd.horizontalIndent = 5; - gd.verticalIndent = 5; - titleLbl.setLayoutData(gd); - return titleLbl; - } - - private class MySelectionChangedListener implements ISelectionChangedListener { - @Override - public void selectionChanged(SelectionChangedEvent event) { - IStructuredSelection selection = (IStructuredSelection) bookmarksViewer.getSelection(); - if (selection.isEmpty()) - return; - else { - Path newSelected = (Path) selection.getFirstElement(); - if (newSelected.equals(currSelected)) - return; - currSelected = newSelected; - directoryDisplayViewer.setInput(currSelected, "*"); - } - } - } - - private void populateDisplay(final Composite parent) { - parent.setLayout(EclipseUiUtils.noSpaceGridLayout()); - directoryDisplayViewer = new FsTableViewer(parent, SWT.MULTI); - List colDefs = new ArrayList<>(); - colDefs.add(new ColumnDefinition(new FileIconNameLabelProvider(), "Name", 200)); - colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_SIZE), "Size", 100)); - colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_TYPE), "Type", 250)); - colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_LAST_MODIFIED), - "Last modified", 200)); - Table table = directoryDisplayViewer.configureDefaultTable(colDefs); - table.setLayoutData(EclipseUiUtils.fillAll()); - - table.addKeyListener(new KeyListener() { - private static final long serialVersionUID = -8083424284436715709L; - - @Override - public void keyReleased(KeyEvent e) { - } - - @Override - public void keyPressed(KeyEvent e) { - log.debug("Key event received: " + e.keyCode); - IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection(); - Path selected = null; - if (!selection.isEmpty()) - selected = ((Path) selection.getFirstElement()); - if (e.keyCode == SWT.CR) { - if (!Files.isDirectory(selected)) - return; - if (selected != null) { - currSelected = selected; - directoryDisplayViewer.setInput(currSelected, "*"); - } - } else if (e.keyCode == SWT.BS) { - currSelected = currSelected.getParent(); - directoryDisplayViewer.setInput(currSelected, "*"); - directoryDisplayViewer.getTable().setFocus(); - } - } - }); - -// directoryDisplayViewer.addDoubleClickListener(new IDoubleClickListener() { -// @Override -// public void doubleClick(DoubleClickEvent event) { -// IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection(); -// Path selected = null; -// if (!selection.isEmpty()) { -// Object obj = selection.getFirstElement(); -// if (obj instanceof Path) -// selected = (Path) obj; -// else if (obj instanceof ParentDir) -// selected = ((ParentDir) obj).getPath(); -// } -// if (selected != null) { -// if (!Files.isDirectory(selected)) -// return; -// currSelected = selected; -// directoryDisplayViewer.setInput(currSelected, "*"); -// } -// } -// }); - - directoryDisplayViewer.addDoubleClickListener(new IDoubleClickListener() { - @Override - public void doubleClick(DoubleClickEvent event) { - IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection(); - Path selected = null; - if (!selection.isEmpty()) { - Object obj = selection.getFirstElement(); - if (obj instanceof Path) - selected = (Path) obj; - else if (obj instanceof ParentDir) - selected = ((ParentDir) obj).getPath(); - } - if (selected != null) { - if (!Files.isDirectory(selected)) - return; - currSelected = selected; - directoryDisplayViewer.setInput(currSelected, "*"); - } - } - }); - } -} diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsTreeBrowser.java b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsTreeBrowser.java deleted file mode 100644 index 401e5cb5e..000000000 --- a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/SimpleFsTreeBrowser.java +++ /dev/null @@ -1,128 +0,0 @@ -package org.argeo.eclipse.ui.fs; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.ArrayList; -import java.util.List; - -import org.argeo.api.cms.CmsLog; -import org.argeo.eclipse.ui.ColumnDefinition; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.eclipse.jface.viewers.ISelectionChangedListener; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.SelectionChangedEvent; -import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.SashForm; -import org.eclipse.swt.events.KeyEvent; -import org.eclipse.swt.events.KeyListener; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Table; -import org.eclipse.swt.widgets.Tree; - -/** A simple Java 7 nio files browser with a tree */ -public class SimpleFsTreeBrowser extends Composite { - private final static CmsLog log = CmsLog.getLog(SimpleFsTreeBrowser.class); - private static final long serialVersionUID = -40347919096946585L; - - private Path currSelected; - private FsTreeViewer treeViewer; - private FsTableViewer directoryDisplayViewer; - - public SimpleFsTreeBrowser(Composite parent, int style) { - super(parent, style); - createContent(this); - // parent.layout(true, true); - } - - private void createContent(Composite parent) { - parent.setLayout(EclipseUiUtils.noSpaceGridLayout()); - SashForm form = new SashForm(parent, SWT.HORIZONTAL); - Composite child1 = new Composite(form, SWT.NONE); - populateTree(child1); - Composite child2 = new Composite(form, SWT.BORDER); - populateDisplay(child2); - form.setLayoutData(EclipseUiUtils.fillAll()); - form.setWeights(new int[] { 1, 3 }); - } - - public void setInput(Path... paths) { - treeViewer.setPathsInput(paths); - treeViewer.getControl().getParent().layout(true, true); - } - - private void populateTree(final Composite parent) { - // GridLayout layout = EclipseUiUtils.noSpaceGridLayout(); - // layout.verticalSpacing = 5; - parent.setLayout(new GridLayout()); - - ISelectionChangedListener selList = new MySelectionChangedListener(); - - treeViewer = new FsTreeViewer(parent, SWT.MULTI); - Tree tree = treeViewer.configureDefaultSingleColumnTable(500); - GridData gd = EclipseUiUtils.fillAll(); - // gd.horizontalIndent = 10; - tree.setLayoutData(gd); - treeViewer.addSelectionChangedListener(selList); - } - - private class MySelectionChangedListener implements ISelectionChangedListener { - @Override - public void selectionChanged(SelectionChangedEvent event) { - IStructuredSelection selection = (IStructuredSelection) treeViewer.getSelection(); - if (selection.isEmpty()) - return; - else { - Path newSelected = (Path) selection.getFirstElement(); - if (newSelected.equals(currSelected)) - return; - currSelected = newSelected; - if (Files.isDirectory(currSelected)) - directoryDisplayViewer.setInput(currSelected, "*"); - } - } - } - - private void populateDisplay(final Composite parent) { - parent.setLayout(EclipseUiUtils.noSpaceGridLayout()); - directoryDisplayViewer = new FsTableViewer(parent, SWT.MULTI); - List colDefs = new ArrayList<>(); - colDefs.add(new ColumnDefinition(new FileIconNameLabelProvider(), "Name", 200, 200)); - colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_SIZE), "Size", 100, 100)); - colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_TYPE), "Type", 300, 300)); - colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_LAST_MODIFIED), - "Last modified", 100, 100)); - Table table = directoryDisplayViewer.configureDefaultTable(colDefs); - table.setLayoutData(EclipseUiUtils.fillAll()); - - table.addKeyListener(new KeyListener() { - private static final long serialVersionUID = -8083424284436715709L; - - @Override - public void keyReleased(KeyEvent e) { - } - - @Override - public void keyPressed(KeyEvent e) { - log.debug("Key event received: " + e.keyCode); - IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection(); - Path selected = null; - if (!selection.isEmpty()) - selected = ((Path) selection.getFirstElement()); - if (e.keyCode == SWT.CR) { - if (!Files.isDirectory(selected)) - return; - if (selected != null) { - currSelected = selected; - directoryDisplayViewer.setInput(currSelected, "*"); - } - } else if (e.keyCode == SWT.BS) { - currSelected = currSelected.getParent(); - directoryDisplayViewer.setInput(currSelected, "*"); - directoryDisplayViewer.getTable().setFocus(); - } - } - }); - } -} diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/file.png b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/file.png deleted file mode 100644 index ce2f2a8f4..000000000 Binary files a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/file.png and /dev/null differ diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/folder.png b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/folder.png deleted file mode 100644 index c31f37e07..000000000 Binary files a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/folder.png and /dev/null differ diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/package-info.java b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/package-info.java deleted file mode 100644 index d7f83c3e1..000000000 --- a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/fs/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Generic SWT/JFace file system utilities. */ -package org.argeo.eclipse.ui.fs; \ No newline at end of file diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/package-info.java b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/package-info.java deleted file mode 100644 index 0d245db07..000000000 --- a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Generic SWT/JFace utilities. */ -package org.argeo.eclipse.ui; \ No newline at end of file diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/parts/LdifUsersTable.java b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/parts/LdifUsersTable.java deleted file mode 100644 index 57139056c..000000000 --- a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/parts/LdifUsersTable.java +++ /dev/null @@ -1,402 +0,0 @@ -package org.argeo.eclipse.ui.parts; - -import java.util.ArrayList; -import java.util.List; - -import org.argeo.eclipse.ui.ColumnDefinition; -import org.argeo.eclipse.ui.EclipseUiException; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.argeo.eclipse.ui.util.ViewerUtils; -import org.eclipse.jface.layout.TableColumnLayout; -import org.eclipse.jface.viewers.CheckboxTableViewer; -import org.eclipse.jface.viewers.ColumnLabelProvider; -import org.eclipse.jface.viewers.ColumnWeightData; -import org.eclipse.jface.viewers.IStructuredContentProvider; -import org.eclipse.jface.viewers.TableViewer; -import org.eclipse.jface.viewers.TableViewerColumn; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.ModifyEvent; -import org.eclipse.swt.events.ModifyListener; -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.Composite; -import org.eclipse.swt.widgets.Link; -import org.eclipse.swt.widgets.Table; -import org.eclipse.swt.widgets.TableColumn; -import org.eclipse.swt.widgets.Text; -import org.osgi.service.useradmin.User; - -/** - * Generic composite that display a filter and a table viewer to display users - * (can also be groups) - * - * Warning: this class does not extends TableViewer. Use the - * getTableViewer method to access it. - * - */ -public abstract class LdifUsersTable extends Composite { - private static final long serialVersionUID = -7385959046279360420L; - - // Context - // private UserAdmin userAdmin; - - // Configuration - private List columnDefs = new ArrayList(); - private boolean hasFilter; - private boolean preventTableLayout = false; - private boolean hasSelectionColumn; - private int tableStyle; - - // Local UI Objects - private TableViewer usersViewer; - private Text filterTxt; - - /* EXPOSED METHODS */ - - /** - * @param parent - * @param style - */ - public LdifUsersTable(Composite parent, int style) { - super(parent, SWT.NO_FOCUS); - this.tableStyle = style; - } - - // TODO workaround the bug of the table layout in the Form - public LdifUsersTable(Composite parent, int style, boolean preventTableLayout) { - super(parent, SWT.NO_FOCUS); - this.tableStyle = style; - this.preventTableLayout = preventTableLayout; - } - - /** This must be called before the call to populate method */ - public void setColumnDefinitions(List columnDefinitions) { - this.columnDefs = columnDefinitions; - } - - /** - * - * @param addFilter - * choose to add a field to filter results or not - * @param addSelection - * choose to add a column to select some of the displayed results or - * not - */ - public void populate(boolean addFilter, boolean addSelection) { - // initialization - Composite parent = this; - hasFilter = addFilter; - hasSelectionColumn = addSelection; - - // Main Layout - GridLayout layout = EclipseUiUtils.noSpaceGridLayout(); - layout.verticalSpacing = 5; - this.setLayout(layout); - if (hasFilter) - createFilterPart(parent); - - Composite tableComp = new Composite(parent, SWT.NO_FOCUS); - tableComp.setLayoutData(EclipseUiUtils.fillAll()); - usersViewer = createTableViewer(tableComp); - usersViewer.setContentProvider(new UsersContentProvider()); - } - - /** - * - * @param showMore - * display static filters on creation - * @param addSelection - * choose to add a column to select some of the displayed results or - * not - */ - public void populateWithStaticFilters(boolean showMore, boolean addSelection) { - // initialization - Composite parent = this; - hasFilter = true; - hasSelectionColumn = addSelection; - - // Main Layout - GridLayout layout = EclipseUiUtils.noSpaceGridLayout(); - layout.verticalSpacing = 5; - this.setLayout(layout); - createStaticFilterPart(parent, showMore); - - Composite tableComp = new Composite(parent, SWT.NO_FOCUS); - tableComp.setLayoutData(EclipseUiUtils.fillAll()); - usersViewer = createTableViewer(tableComp); - usersViewer.setContentProvider(new UsersContentProvider()); - } - - /** Enable access to the selected users or groups */ - public List getSelectedUsers() { - if (hasSelectionColumn) { - Object[] elements = ((CheckboxTableViewer) usersViewer).getCheckedElements(); - - List result = new ArrayList(); - for (Object obj : elements) { - result.add((User) obj); - } - return result; - } else - throw new EclipseUiException( - "Unvalid request: no selection column " + "has been created for the current table"); - } - - /** Returns the User table viewer, typically to add doubleclick listener */ - public TableViewer getTableViewer() { - return usersViewer; - } - - /** - * Force the refresh of the underlying table using the current filter string if - * relevant - */ - public void refresh() { - String filter = hasFilter ? filterTxt.getText().trim() : null; - if ("".equals(filter)) - filter = null; - refreshFilteredList(filter); - } - - /** Effective repository request: caller must implement this method */ - abstract protected List listFilteredElements(String filter); - - // protected List listFilteredElements(String filter) { - // List users = new ArrayList(); - // try { - // Role[] roles = userAdmin.getRoles(filter); - // // Display all users and groups - // for (Role role : roles) - // users.add((User) role); - // } catch (InvalidSyntaxException e) { - // throw new EclipseUiException("Unable to get roles with filter: " - // + filter, e); - // } - // return users; - // } - - /* GENERIC COMPOSITE METHODS */ - @Override - public boolean setFocus() { - if (hasFilter) - return filterTxt.setFocus(); - else - return usersViewer.getTable().setFocus(); - } - - @Override - public void dispose() { - super.dispose(); - } - - /* LOCAL CLASSES AND METHODS */ - // Will be usefull to rather use a virtual table viewer - private void refreshFilteredList(String filter) { - List users = listFilteredElements(filter); - usersViewer.setInput(users.toArray()); - } - - private class UsersContentProvider implements IStructuredContentProvider { - private static final long serialVersionUID = 1L; - - public Object[] getElements(Object inputElement) { - return (Object[]) inputElement; - } - - public void dispose() { - } - - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - } - } - - /* MANAGE FILTER */ - private void createFilterPart(Composite parent) { - // Text Area for the filter - filterTxt = new Text(parent, SWT.BORDER | SWT.SEARCH | SWT.ICON_SEARCH | SWT.ICON_CANCEL); - filterTxt.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, false)); - filterTxt.addModifyListener(new ModifyListener() { - private static final long serialVersionUID = 1L; - - public void modifyText(ModifyEvent event) { - refreshFilteredList(filterTxt.getText()); - } - }); - } - - private void createStaticFilterPart(Composite parent, boolean showMore) { - Composite filterComp = new Composite(parent, SWT.NO_FOCUS); - filterComp.setLayout(new GridLayout(2, false)); - filterComp.setLayoutData(EclipseUiUtils.fillWidth()); - // generic search - filterTxt = new Text(filterComp, SWT.BORDER | SWT.SEARCH | SWT.ICON_SEARCH | SWT.ICON_CANCEL); - filterTxt.setLayoutData(new GridData(GridData.FILL, GridData.FILL, true, false)); - // filterTxt.setLayoutData(new GridData(GridData.GRAB_HORIZONTAL | - // GridData.HORIZONTAL_ALIGN_FILL)); - filterTxt.addModifyListener(new ModifyListener() { - private static final long serialVersionUID = 1L; - - public void modifyText(ModifyEvent event) { - refreshFilteredList(filterTxt.getText()); - } - }); - - // add static filter abilities - Link moreLk = new Link(filterComp, SWT.NONE); - Composite staticFilterCmp = new Composite(filterComp, SWT.NO_FOCUS); - staticFilterCmp.setLayoutData(EclipseUiUtils.fillWidth(2)); - populateStaticFilters(staticFilterCmp); - - MoreLinkListener listener = new MoreLinkListener(moreLk, staticFilterCmp, showMore); - // initialise the layout - listener.refresh(); - moreLk.addSelectionListener(listener); - } - - /** Overwrite to add static filters */ - protected void populateStaticFilters(Composite staticFilterCmp) { - } - - // private void addMoreSL(final Link more) { - // more.addSelectionListener( } - - private class MoreLinkListener extends SelectionAdapter { - private static final long serialVersionUID = -524987616510893463L; - private boolean isShown; - private final Composite staticFilterCmp; - private final Link moreLk; - - public MoreLinkListener(Link moreLk, Composite staticFilterCmp, boolean isShown) { - this.moreLk = moreLk; - this.staticFilterCmp = staticFilterCmp; - this.isShown = isShown; - } - - @Override - public void widgetSelected(SelectionEvent e) { - isShown = !isShown; - refresh(); - } - - public void refresh() { - GridData gd = (GridData) staticFilterCmp.getLayoutData(); - if (isShown) { - moreLk.setText(" Less... "); - gd.heightHint = SWT.DEFAULT; - } else { - moreLk.setText(" More... "); - gd.heightHint = 0; - } - forceLayout(); - } - } - - private void forceLayout() { - LdifUsersTable.this.getParent().layout(true, true); - } - - private TableViewer createTableViewer(final Composite parent) { - - int style = tableStyle | SWT.H_SCROLL | SWT.V_SCROLL; - if (hasSelectionColumn) - style = style | SWT.CHECK; - Table table = new Table(parent, style); - TableColumnLayout layout = new TableColumnLayout(); - - // TODO the table layout does not works with the scrolled form - - if (preventTableLayout) { - parent.setLayout(EclipseUiUtils.noSpaceGridLayout()); - table.setLayoutData(EclipseUiUtils.fillAll()); - } else - parent.setLayout(layout); - - TableViewer viewer; - if (hasSelectionColumn) - viewer = new CheckboxTableViewer(table); - else - viewer = new TableViewer(table); - table.setLinesVisible(true); - table.setHeaderVisible(true); - - TableViewerColumn column; - // int offset = 0; - if (hasSelectionColumn) { - // offset = 1; - column = ViewerUtils.createTableViewerColumn(viewer, "", SWT.NONE, 25); - column.setLabelProvider(new ColumnLabelProvider() { - private static final long serialVersionUID = 1L; - - @Override - public String getText(Object element) { - return null; - } - }); - layout.setColumnData(column.getColumn(), new ColumnWeightData(25, 25, false)); - - SelectionAdapter selectionAdapter = new SelectionAdapter() { - private static final long serialVersionUID = 1L; - - boolean allSelected = false; - - @Override - public void widgetSelected(SelectionEvent e) { - allSelected = !allSelected; - ((CheckboxTableViewer) usersViewer).setAllChecked(allSelected); - } - }; - column.getColumn().addSelectionListener(selectionAdapter); - } - - // NodeViewerComparator comparator = new NodeViewerComparator(); - // TODO enable the sort by click on the header - // int i = offset; - for (ColumnDefinition colDef : columnDefs) - createTableColumn(viewer, layout, colDef); - - // column = ViewerUtils.createTableViewerColumn(viewer, - // colDef.getHeaderLabel(), SWT.NONE, colDef.getColumnSize()); - // column.setLabelProvider(new CLProvider(colDef.getPropertyName())); - // column.getColumn().addSelectionListener( - // JcrUiUtils.getNodeSelectionAdapter(i, - // colDef.getPropertyType(), colDef.getPropertyName(), - // comparator, viewer)); - // i++; - // } - - // IMPORTANT: initialize comparator before setting it - // JcrColumnDefinition firstCol = colDefs.get(0); - // comparator.setColumn(firstCol.getPropertyType(), - // firstCol.getPropertyName()); - // viewer.setComparator(comparator); - - return viewer; - } - - /** Default creation of a column for a user table */ - private TableViewerColumn createTableColumn(TableViewer tableViewer, TableColumnLayout layout, - ColumnDefinition columnDef) { - - boolean resizable = true; - TableViewerColumn tvc = new TableViewerColumn(tableViewer, SWT.NONE); - TableColumn column = tvc.getColumn(); - - column.setText(columnDef.getLabel()); - column.setWidth(columnDef.getMinWidth()); - column.setResizable(resizable); - - ColumnLabelProvider lp = columnDef.getLabelProvider(); - // add a reference to the display to enable font management - // if (lp instanceof UserAdminAbstractLP) - // ((UserAdminAbstractLP) lp).setDisplay(tableViewer.getTable() - // .getDisplay()); - tvc.setLabelProvider(lp); - - layout.setColumnData(column, new ColumnWeightData(columnDef.getWeight(), columnDef.getMinWidth(), resizable)); - - return tvc; - } -} diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/parts/package-info.java b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/parts/package-info.java deleted file mode 100644 index 9e93b1106..000000000 --- a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/parts/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Generic SWT/JFace composites. */ -package org.argeo.eclipse.ui.parts; \ No newline at end of file diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/util/ViewerUtils.java b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/util/ViewerUtils.java deleted file mode 100644 index 8f4df1799..000000000 --- a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/util/ViewerUtils.java +++ /dev/null @@ -1,58 +0,0 @@ -package org.argeo.eclipse.ui.util; - -import org.eclipse.jface.viewers.TableViewer; -import org.eclipse.jface.viewers.TableViewerColumn; -import org.eclipse.jface.viewers.TreeViewer; -import org.eclipse.jface.viewers.TreeViewerColumn; -import org.eclipse.swt.widgets.Table; -import org.eclipse.swt.widgets.TableColumn; -import org.eclipse.swt.widgets.TreeColumn; - -/** - * Centralise useful methods to manage JFace Table, Tree and TreeColumn viewers. - */ -public class ViewerUtils { - - /** - * Creates a basic column for the given table. For the time being, we do not - * support movable columns. - */ - public static TableColumn createColumn(Table parent, String name, int style, int width) { - TableColumn result = new TableColumn(parent, style); - result.setText(name); - result.setWidth(width); - result.setResizable(true); - return result; - } - - /** - * Creates a TableViewerColumn for the given viewer. For the time being, we do - * not support movable columns. - */ - public static TableViewerColumn createTableViewerColumn(TableViewer parent, String name, int style, int width) { - TableViewerColumn tvc = new TableViewerColumn(parent, style); - TableColumn column = tvc.getColumn(); - column.setText(name); - column.setWidth(width); - column.setResizable(true); - return tvc; - } - - // public static TableViewerColumn createTableViewerColumn(TableViewer parent, - // Localized name, int style, int width) { - // return createTableViewerColumn(parent, name.lead(), style, width); - // } - - /** - * Creates a TreeViewerColumn for the given viewer. For the time being, we do - * not support movable columns. - */ - public static TreeViewerColumn createTreeViewerColumn(TreeViewer parent, String name, int style, int width) { - TreeViewerColumn tvc = new TreeViewerColumn(parent, style); - TreeColumn column = tvc.getColumn(); - column.setText(name); - column.setWidth(width); - column.setResizable(true); - return tvc; - } -} diff --git a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/util/package-info.java b/org.argeo.cms.swt/src/org/argeo/eclipse/ui/util/package-info.java deleted file mode 100644 index 798d17482..000000000 --- a/org.argeo.cms.swt/src/org/argeo/eclipse/ui/util/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Generic SWT/JFace JCR helpers. */ -package org.argeo.eclipse.ui.util; \ No newline at end of file diff --git a/org.argeo.cms.ui/.classpath b/org.argeo.cms.ui/.classpath deleted file mode 100644 index e801ebfb4..000000000 --- a/org.argeo.cms.ui/.classpath +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/org.argeo.cms.ui/.project b/org.argeo.cms.ui/.project deleted file mode 100644 index e52eb8ef7..000000000 --- a/org.argeo.cms.ui/.project +++ /dev/null @@ -1,28 +0,0 @@ - - - org.argeo.cms.ui - - - - - - org.eclipse.jdt.core.javabuilder - - - - - org.eclipse.pde.ManifestBuilder - - - - - org.eclipse.pde.SchemaBuilder - - - - - - org.eclipse.pde.PluginNature - org.eclipse.jdt.core.javanature - - diff --git a/org.argeo.cms.ui/META-INF/.gitignore b/org.argeo.cms.ui/META-INF/.gitignore deleted file mode 100644 index 4854a41b9..000000000 --- a/org.argeo.cms.ui/META-INF/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/MANIFEST.MF diff --git a/org.argeo.cms.ui/bnd.bnd b/org.argeo.cms.ui/bnd.bnd deleted file mode 100644 index ed842413b..000000000 --- a/org.argeo.cms.ui/bnd.bnd +++ /dev/null @@ -1,22 +0,0 @@ -Bundle-Activator: org.argeo.cms.ui.internal.Activator -Bundle-ActivationPolicy: lazy - -Import-Package: org.eclipse.swt,\ -org.eclipse.jface.window,\ -org.eclipse.core.commands,\ -javax.jcr.security,\ -org.eclipse.rap.fileupload;version="[2.1,4)",\ -org.eclipse.rap.rwt;version="[2.1,4)",\ -org.eclipse.rap.rwt.application;version="[2.1,4)",\ -org.eclipse.rap.rwt.client;version="[2.1,4)",\ -org.eclipse.rap.rwt.client.service;version="[2.1,4)",\ -org.eclipse.rap.rwt.service;version="[2.1,4)",\ -org.eclipse.rap.rwt.widgets;version="[2.1,4)",\ -org.osgi.*;version=0.0.0,\ -* - -## TODO: in order to enable single sourcing, we have introduced dummy RAP packages -# in the RCP specific bundle org.argeo.eclipse.ui.rap. -# this was working with RAP 2.x but since we upgrade Rap to 3.1.x, -# there is a version range issue cause org.argeo.eclipse.ui.rap bundle is still in 2.x -# We enable large RAP version range as a workaround that must be fixed \ No newline at end of file diff --git a/org.argeo.cms.ui/build.properties b/org.argeo.cms.ui/build.properties deleted file mode 100644 index c6baffa00..000000000 --- a/org.argeo.cms.ui/build.properties +++ /dev/null @@ -1,5 +0,0 @@ -source.. = src/ -output.. = bin/ -bin.includes = META-INF/,\ - .,\ - icons/ diff --git a/org.argeo.cms.ui/icons/loading.gif b/org.argeo.cms.ui/icons/loading.gif deleted file mode 100644 index 3288d1035..000000000 Binary files a/org.argeo.cms.ui/icons/loading.gif and /dev/null differ diff --git a/org.argeo.cms.ui/icons/noPic-goldenRatio-640px.png b/org.argeo.cms.ui/icons/noPic-goldenRatio-640px.png deleted file mode 100644 index 039650638..000000000 Binary files a/org.argeo.cms.ui/icons/noPic-goldenRatio-640px.png and /dev/null differ diff --git a/org.argeo.cms.ui/icons/noPic-square-640px.png b/org.argeo.cms.ui/icons/noPic-square-640px.png deleted file mode 100644 index 8e3abb518..000000000 Binary files a/org.argeo.cms.ui/icons/noPic-square-640px.png and /dev/null differ diff --git a/org.argeo.cms.ui/pom.xml b/org.argeo.cms.ui/pom.xml deleted file mode 100644 index b68cb9acc..000000000 --- a/org.argeo.cms.ui/pom.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - 4.0.0 - - org.argeo.commons - argeo-commons - 2.3-SNAPSHOT - .. - - org.argeo.cms.ui - CMS UI - jar - - - org.argeo.commons - org.argeo.cms - 2.3-SNAPSHOT - - - org.argeo.commons - org.argeo.cms.swt - 2.3-SNAPSHOT - - - org.argeo.commons - org.argeo.cms.jcr - 2.3-SNAPSHOT - - - - - org.argeo.commons.rap - org.argeo.swt.specific.rap - 2.3-SNAPSHOT - provided - - - - - org.argeo.tp.rap.e4 - org.eclipse.rap.rwt - provided - - - org.argeo.tp.rap.e4 - org.eclipse.core.commands - provided - - - org.argeo.tp.rap.e4 - org.eclipse.rap.jface - provided - - - - - org.argeo.tp.rap.e4 - org.eclipse.rap.filedialog - provided - - - org.argeo.tp.rap.e4 - org.eclipse.rap.fileupload - provided - - - - \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsEditionEvent.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsEditionEvent.java deleted file mode 100644 index 872142bca..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsEditionEvent.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.argeo.cms.ui; - -import java.util.EventObject; - -/** Notify of the edition lifecycle */ -public class CmsEditionEvent extends EventObject { - private static final long serialVersionUID = 950914736016693110L; - - public final static Integer START_EDITING = 0; - public final static Integer STOP_EDITING = 1; - - private final Integer type; - - public CmsEditionEvent(Object source, Integer type) { - super(source); - this.type = type; - } - - public Integer getType() { - return type; - } - -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsUiConstants.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsUiConstants.java deleted file mode 100644 index 9df61dcca..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsUiConstants.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.argeo.cms.ui; - -import org.argeo.api.cms.Cms2DSize; - -/** Commons constants */ -@Deprecated -public interface CmsUiConstants { - // DATAKEYS -// public final static String STYLE = EclipseUiConstants.CSS_CLASS; -// public final static String MARKUP = EclipseUiConstants.MARKUP_SUPPORT; - @Deprecated - /* RWT.CUSTOM_ITEM_HEIGHT */ - public final static String ITEM_HEIGHT = "org.eclipse.rap.rwt.customItemHeight"; - - // EVENT DETAILS - @Deprecated - /* RWT.HYPERLINK */ - public final static int HYPERLINK = 1 << 26; - - // STANDARD RESOURCES - public final static String LOADING_IMAGE = "icons/loading.gif"; - - public final static String NO_IMAGE = "icons/noPic-square-640px.png"; - public final static Cms2DSize NO_IMAGE_SIZE = new Cms2DSize(320, 320); - public final static Float NO_IMAGE_RATIO = 1f; - // MISCEALLENEOUS - String DATE_TIME_FORMAT = "dd/MM/yyyy, HH:mm"; -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsUiProvider.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsUiProvider.java deleted file mode 100644 index ec76321fe..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/CmsUiProvider.java +++ /dev/null @@ -1,30 +0,0 @@ -package org.argeo.cms.ui; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.api.cms.MvcProvider; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; - -/** Stateless factory building an SWT user interface given a JCR context. */ -@FunctionalInterface -public interface CmsUiProvider extends MvcProvider { - /** - * Initialises a user interface. - * - * @param parent the parent composite - * @param context a context node (holding the JCR underlying session), or null - */ - Control createUi(Composite parent, Node context) throws RepositoryException; - - @Override - default Control createUiPart(Composite parent, Node context) { - try { - return createUi(parent, context); - } catch (RepositoryException e) { - throw new IllegalStateException("Cannot create UI for context " + context, e); - } - } - -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/LifeCycleUiProvider.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/LifeCycleUiProvider.java deleted file mode 100644 index 5d77c156c..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/LifeCycleUiProvider.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.argeo.cms.ui; - -import javax.jcr.RepositoryException; -import javax.jcr.Session; - -/** CmsUiProvider notified of initialisation with a system session. */ -public interface LifeCycleUiProvider extends CmsUiProvider { - public void init(Session adminSession) throws RepositoryException; - - public void destroy(); -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/AbstractFormPart.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/AbstractFormPart.java deleted file mode 100644 index 4ce468826..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/AbstractFormPart.java +++ /dev/null @@ -1,108 +0,0 @@ -package org.argeo.cms.ui.eclipse.forms; -/** - * AbstractFormPart implements IFormPart interface and can be used as a - * convenient base class for concrete form parts. If a method contains - * code that must be called, look for instructions to call 'super' - * when overriding. - * - * @see org.eclipse.ui.forms.widgets.Section - * @since 1.0 - */ -public abstract class AbstractFormPart implements IFormPart { - private IManagedForm managedForm; - private boolean dirty = false; - private boolean stale = true; - /** - * @see org.eclipse.ui.forms.IFormPart#initialize(org.eclipse.ui.forms.IManagedForm) - */ - public void initialize(IManagedForm form) { - this.managedForm = form; - } - /** - * Returns the form that manages this part. - * - * @return the managed form - */ - public IManagedForm getManagedForm() { - return managedForm; - } - /** - * Disposes the part. Subclasses should override to release any system - * resources. - */ - public void dispose() { - } - /** - * Commits the part. Subclasses should call 'super' when overriding. - * - * @param onSave - * true if the request to commit has arrived as a - * result of the 'save' action. - */ - public void commit(boolean onSave) { - dirty = false; - } - /** - * Sets the overall form input. Subclases may elect to override the method - * and adjust according to the form input. - * - * @param input - * the form input object - * @return false - */ - public boolean setFormInput(Object input) { - return false; - } - /** - * Instructs the part to grab keyboard focus. - */ - public void setFocus() { - } - /** - * Refreshes the section after becoming stale (falling behind data in the - * model). Subclasses must call 'super' when overriding this method. - */ - public void refresh() { - stale = false; - // since we have refreshed, any changes we had in the - // part are gone and we are not dirty - dirty = false; - } - /** - * Marks the part dirty. Subclasses should call this method as a result of - * user interaction with the widgets in the section. - */ - public void markDirty() { - dirty = true; - managedForm.dirtyStateChanged(); - } - /** - * Tests whether the part is dirty i.e. its widgets have state that is - * newer than the data in the model. - * - * @return true if the part is dirty, false - * otherwise. - */ - public boolean isDirty() { - return dirty; - } - /** - * Tests whether the part is stale i.e. its widgets have state that is - * older than the data in the model. - * - * @return true if the part is stale, false - * otherwise. - */ - public boolean isStale() { - return stale; - } - /** - * Marks the part stale. Subclasses should call this method as a result of - * model notification that indicates that the content of the section is no - * longer in sync with the model. - */ - public void markStale() { - stale = true; - managedForm.staleStateChanged(); - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormColors.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormColors.java deleted file mode 100644 index 32b031b86..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormColors.java +++ /dev/null @@ -1,730 +0,0 @@ -package org.argeo.cms.ui.eclipse.forms; - -import java.util.HashMap; -import java.util.Map; - -import org.eclipse.jface.resource.JFaceResources; -import org.eclipse.jface.resource.LocalResourceManager; -import org.eclipse.swt.SWT; -import org.eclipse.swt.graphics.Color; -import org.eclipse.swt.graphics.RGB; -//import org.eclipse.swt.internal.graphics.Graphics; -import org.eclipse.swt.widgets.Display; - -/** - * Manages colors that will be applied to forms and form widgets. The colors are - * chosen to make the widgets look correct in the editor area. If a different - * set of colors is needed, subclass this class and override 'initialize' and/or - * 'initializeColors'. - * - * @since 1.0 - */ -public class FormColors { - /** - * Key for the form title foreground color. - * - * @deprecated use IFormColors.TITLE. - */ - public static final String TITLE = IFormColors.TITLE; - - /** - * Key for the tree/table border color. - * - * @deprecated use IFormColors.BORDER - */ - public static final String BORDER = IFormColors.BORDER; - - /** - * Key for the section separator color. - * - * @deprecated use IFormColors.SEPARATOR. - */ - public static final String SEPARATOR = IFormColors.SEPARATOR; - - /** - * Key for the section title bar background. - * - * @deprecated use IFormColors.TB_BG - */ - public static final String TB_BG = IFormColors.TB_BG; - - /** - * Key for the section title bar foreground. - * - * @deprecated use IFormColors.TB_FG - */ - public static final String TB_FG = IFormColors.TB_FG; - - /** - * Key for the section title bar gradient. - * - * @deprecated use IFormColors.TB_GBG - */ - public static final String TB_GBG = IFormColors.TB_GBG; - - /** - * Key for the section title bar border. - * - * @deprecated use IFormColors.TB_BORDER. - */ - public static final String TB_BORDER = IFormColors.TB_BORDER; - - /** - * Key for the section toggle color. Since 3.1, this color is used for all - * section styles. - * - * @deprecated use IFormColors.TB_TOGGLE. - */ - public static final String TB_TOGGLE = IFormColors.TB_TOGGLE; - - /** - * Key for the section toggle hover color. - * - * @deprecated use IFormColors.TB_TOGGLE_HOVER. - */ - public static final String TB_TOGGLE_HOVER = IFormColors.TB_TOGGLE_HOVER; - - protected Map colorRegistry = new HashMap(10); - - private LocalResourceManager resources; - - protected Color background; - - protected Color foreground; - - private boolean shared; - - protected Display display; - - protected Color border; - - /** - * Creates form colors using the provided display. - * - * @param display - * the display to use - */ - public FormColors(Display display) { - this.display = display; - initialize(); - } - - /** - * Returns the display used to create colors. - * - * @return the display - */ - public Display getDisplay() { - return display; - } - - /** - * Initializes the colors. Subclasses can override this method to change the - * way colors are created. Alternatively, only the color table can be - * modified by overriding initializeColorTable(). - * - * @see #initializeColorTable - */ - protected void initialize() { - background = display.getSystemColor(SWT.COLOR_LIST_BACKGROUND); - foreground = display.getSystemColor(SWT.COLOR_LIST_FOREGROUND); - initializeColorTable(); - updateBorderColor(); - } - - /** - * Allocates colors for the following keys: BORDER, SEPARATOR and - * TITLE. Subclasses can override to allocate these colors differently. - */ - protected void initializeColorTable() { - createTitleColor(); - createColor(IFormColors.SEPARATOR, getColor(IFormColors.TITLE).getRGB()); - RGB black = getSystemColor(SWT.COLOR_BLACK); - RGB borderRGB = getSystemColor(SWT.COLOR_TITLE_INACTIVE_BACKGROUND_GRADIENT); - createColor(IFormColors.BORDER, blend(borderRGB, black, 80)); - } - - /** - * Allocates colors for the section tool bar (all the keys that start with - * TB). Since these colors are only needed when TITLE_BAR style is used with - * the Section widget, they are not needed all the time and are allocated on - * demand. Consequently, this method will do nothing if the colors have been - * already initialized. Call this method prior to using colors with the TB - * keys to ensure they are available. - */ - public void initializeSectionToolBarColors() { - if (colorRegistry.containsKey(IFormColors.TB_BG)) - return; - createTitleBarGradientColors(); - createTitleBarOutlineColors(); - createTwistieColors(); - } - - /** - * Allocates additional colors for the form header, namely background - * gradients, bottom separator keylines and DND highlights. Since these - * colors are only needed for clients that want to use these particular - * style of header rendering, they are not needed all the time and are - * allocated on demand. Consequently, this method will do nothing if the - * colors have been already initialized. Call this method prior to using - * color keys with the H_ prefix to ensure they are available. - */ - protected void initializeFormHeaderColors() { - if (colorRegistry.containsKey(IFormColors.H_BOTTOM_KEYLINE2)) - return; - createFormHeaderColors(); - } - - /** - * Returns the RGB value of the system color represented by the code - * argument, as defined in SWT class. - * - * @param code - * the system color constant as defined in SWT - * class. - * @return the RGB value of the system color - */ - public RGB getSystemColor(int code) { - return getDisplay().getSystemColor(code).getRGB(); - } - - /** - * Creates the color for the specified key using the provided RGB object. - * The color object will be returned and also put into the registry. When - * the class is disposed, the color will be disposed with it. - * - * @param key - * the unique color key - * @param rgb - * the RGB object - * @return the allocated color object - */ - public Color createColor(String key, RGB rgb) { - // RAP [rh] changes due to missing Color constructor -// Color c = getResourceManager().createColor(rgb); -// Color prevC = (Color) colorRegistry.get(key); -// if (prevC != null && !prevC.isDisposed()) -// getResourceManager().destroyColor(prevC.getRGB()); -// Color c = Graphics.getColor(rgb); - Color c = new Color(display, rgb); - colorRegistry.put(key, c); - return c; - } - - /** - * Creates a color that can be used for areas of the form that is inactive. - * These areas can contain images, links, controls and other content but are - * considered auxilliary to the main content area. - * - *

- * The color should not be disposed because it is managed by this class. - * - * @return the inactive form color - */ - public Color getInactiveBackground() { - String key = "__ncbg__"; //$NON-NLS-1$ - Color color = getColor(key); - if (color == null) { - RGB sel = getSystemColor(SWT.COLOR_LIST_SELECTION); - // a blend of 95% white and 5% list selection system color - RGB ncbg = blend(sel, getSystemColor(SWT.COLOR_WHITE), 5); - color = createColor(key, ncbg); - } - return color; - } - - /** - * Creates the color for the specified key using the provided RGB values. - * The color object will be returned and also put into the registry. If - * there is already another color object under the same key in the registry, - * the existing object will be disposed. When the class is disposed, the - * color will be disposed with it. - * - * @param key - * the unique color key - * @param r - * red value - * @param g - * green value - * @param b - * blue value - * @return the allocated color object - */ - public Color createColor(String key, int r, int g, int b) { - return createColor(key, new RGB(r,g,b)); - } - - /** - * Computes the border color relative to the background. Allocated border - * color is designed to work well with white. Otherwise, stanard widget - * background color will be used. - */ - protected void updateBorderColor() { - if (isWhiteBackground()) - border = getColor(IFormColors.BORDER); - else { - border = display.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND); - Color bg = getImpliedBackground(); - if (border.getRed() == bg.getRed() - && border.getGreen() == bg.getGreen() - && border.getBlue() == bg.getBlue()) - border = display.getSystemColor(SWT.COLOR_WIDGET_DARK_SHADOW); - } - } - - /** - * Sets the background color. All the toolkits that use this class will - * share the same background. - * - * @param bg - * background color - */ - public void setBackground(Color bg) { - this.background = bg; - updateBorderColor(); - updateFormHeaderColors(); - } - - /** - * Sets the foreground color. All the toolkits that use this class will - * share the same foreground. - * - * @param fg - * foreground color - */ - public void setForeground(Color fg) { - this.foreground = fg; - } - - /** - * Returns the current background color. - * - * @return the background color - */ - public Color getBackground() { - return background; - } - - /** - * Returns the current foreground color. - * - * @return the foreground color - */ - public Color getForeground() { - return foreground; - } - - /** - * Returns the computed border color. Border color depends on the background - * and is recomputed whenever the background changes. - * - * @return the current border color - */ - public Color getBorderColor() { - return border; - } - - /** - * Tests if the background is white. White background has RGB value - * 255,255,255. - * - * @return true if background is white, false - * otherwise. - */ - public boolean isWhiteBackground() { - Color bg = getImpliedBackground(); - return bg.getRed() == 255 && bg.getGreen() == 255 - && bg.getBlue() == 255; - } - - /** - * Returns the color object for the provided key or null if - * not in the registry. - * - * @param key - * the color key - * @return color object if found, or null if not. - */ - public Color getColor(String key) { - if (key.startsWith(IFormColors.TB_PREFIX)) - initializeSectionToolBarColors(); - else if (key.startsWith(IFormColors.H_PREFIX)) - initializeFormHeaderColors(); - return (Color) colorRegistry.get(key); - } - - /** - * Disposes all the colors in the registry. - */ - public void dispose() { - if (resources != null) - resources.dispose(); - resources = null; - colorRegistry = null; - } - - /** - * Marks the colors shared. This prevents toolkits that share this object - * from disposing it. - */ - public void markShared() { - this.shared = true; - } - - /** - * Tests if the colors are shared. - * - * @return true if shared, false otherwise. - */ - public boolean isShared() { - return shared; - } - - /** - * Blends c1 and c2 based in the provided ratio. - * - * @param c1 - * first color - * @param c2 - * second color - * @param ratio - * percentage of the first color in the blend (0-100) - * @return the RGB value of the blended color - */ - public static RGB blend(RGB c1, RGB c2, int ratio) { - int r = blend(c1.red, c2.red, ratio); - int g = blend(c1.green, c2.green, ratio); - int b = blend(c1.blue, c2.blue, ratio); - return new RGB(r, g, b); - } - - /** - * Tests the source RGB for range. - * - * @param rgb - * the tested RGB - * @param from - * range start (excluding the value itself) - * @param to - * range end (excluding the value itself) - * @return true if at least one of the primary colors in the - * source RGB are within the provided range, false - * otherwise. - */ - public static boolean testAnyPrimaryColor(RGB rgb, int from, int to) { - if (testPrimaryColor(rgb.red, from, to)) - return true; - if (testPrimaryColor(rgb.green, from, to)) - return true; - if (testPrimaryColor(rgb.blue, from, to)) - return true; - return false; - } - - /** - * Tests the source RGB for range. - * - * @param rgb - * the tested RGB - * @param from - * range start (excluding the value itself) - * @param to - * tange end (excluding the value itself) - * @return true if at least two of the primary colors in the - * source RGB are within the provided range, false - * otherwise. - */ - public static boolean testTwoPrimaryColors(RGB rgb, int from, int to) { - int total = 0; - if (testPrimaryColor(rgb.red, from, to)) - total++; - if (testPrimaryColor(rgb.green, from, to)) - total++; - if (testPrimaryColor(rgb.blue, from, to)) - total++; - return total >= 2; - } - - /** - * Blends two primary color components based on the provided ratio. - * - * @param v1 - * first component - * @param v2 - * second component - * @param ratio - * percentage of the first component in the blend - * @return - */ - private static int blend(int v1, int v2, int ratio) { - int b = (ratio * v1 + (100 - ratio) * v2) / 100; - return Math.min(255, b); - } - - private Color getImpliedBackground() { - if (getBackground() != null) - return getBackground(); - return getDisplay().getSystemColor(SWT.COLOR_WIDGET_BACKGROUND); - } - - private static boolean testPrimaryColor(int value, int from, int to) { - return value > from && value < to; - } - - private void createTitleColor() { - /* - * RGB rgb = getSystemColor(SWT.COLOR_LIST_SELECTION); // test too light - * if (testTwoPrimaryColors(rgb, 120, 151)) rgb = blend(rgb, BLACK, 80); - * else if (testTwoPrimaryColors(rgb, 150, 256)) rgb = blend(rgb, BLACK, - * 50); createColor(TITLE, rgb); - */ - RGB bg = getImpliedBackground().getRGB(); - RGB listSelection = getSystemColor(SWT.COLOR_LIST_SELECTION); - RGB listForeground = getSystemColor(SWT.COLOR_LIST_FOREGROUND); - RGB rgb = listSelection; - - // Group 1 - // Rule: If at least 2 of the LIST_SELECTION RGB values are equal to or - // between 0 and 120, then use 100% LIST_SELECTION as it is (no - // additions) - // Examples: XP Default, Win Classic Standard, Win High Con White, Win - // Classic Marine - if (testTwoPrimaryColors(listSelection, -1, 121)) - rgb = listSelection; - // Group 2 - // When LIST_BACKGROUND = white (255, 255, 255) or not black, text - // colour = LIST_SELECTION @ 100% Opacity + 50% LIST_FOREGROUND over - // LIST_BACKGROUND - // Rule: If at least 2 of the LIST_SELECTION RGB values are equal to or - // between 121 and 255, then add 50% LIST_FOREGROUND to LIST_SELECTION - // foreground colour - // Examples: Win Vista, XP Silver, XP Olive , Win Classic Plum, OSX - // Aqua, OSX Graphite, Linux GTK - else if (testTwoPrimaryColors(listSelection, 120, 256) - || (bg.red == 0 && bg.green == 0 && bg.blue == 0)) - rgb = blend(listSelection, listForeground, 50); - // Group 3 - // When LIST_BACKGROUND = black (0, 0, 0), text colour = LIST_SELECTION - // @ 100% Opacity + 50% LIST_FOREGROUND over LIST_BACKGROUND - // Rule: If LIST_BACKGROUND = 0, 0, 0, then add 50% LIST_FOREGROUND to - // LIST_SELECTION foreground colour - // Examples: Win High Con Black, Win High Con #1, Win High Con #2 - // (covered in the second part of the OR clause above) - createColor(IFormColors.TITLE, rgb); - } - - private void createTwistieColors() { - RGB rgb = getColor(IFormColors.TITLE).getRGB(); - RGB white = getSystemColor(SWT.COLOR_WHITE); - createColor(TB_TOGGLE, rgb); - rgb = blend(rgb, white, 60); - createColor(TB_TOGGLE_HOVER, rgb); - } - - private void createTitleBarGradientColors() { - RGB tbBg = getSystemColor(SWT.COLOR_TITLE_BACKGROUND); - RGB bg = getImpliedBackground().getRGB(); - - // Group 1 - // Rule: If at least 2 of the RGB values are equal to or between 180 and - // 255, then apply specified opacity for Group 1 - // Examples: Vista, XP Silver, Wn High Con #2 - // Gradient Bottom = TITLE_BACKGROUND @ 30% Opacity over LIST_BACKGROUND - // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND - if (testTwoPrimaryColors(tbBg, 179, 256)) - tbBg = blend(tbBg, bg, 30); - - // Group 2 - // Rule: If at least 2 of the RGB values are equal to or between 121 and - // 179, then apply specified opacity for Group 2 - // Examples: XP Olive, OSX Graphite, Linux GTK, Wn High Con Black - // Gradient Bottom = TITLE_BACKGROUND @ 20% Opacity over LIST_BACKGROUND - // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND - else if (testTwoPrimaryColors(tbBg, 120, 180)) - tbBg = blend(tbBg, bg, 20); - - // Group 3 - // Rule: Everything else - // Examples: XP Default, Wn Classic Standard, Wn Marine, Wn Plum, OSX - // Aqua, Wn High Con White, Wn High Con #1 - // Gradient Bottom = TITLE_BACKGROUND @ 10% Opacity over LIST_BACKGROUND - // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND - else { - tbBg = blend(tbBg, bg, 10); - } - - createColor(IFormColors.TB_BG, tbBg); - - // for backward compatibility - createColor(TB_GBG, tbBg); - } - - private void createTitleBarOutlineColors() { - // title bar outline - border color - RGB tbBorder = getSystemColor(SWT.COLOR_TITLE_BACKGROUND); - RGB bg = getImpliedBackground().getRGB(); - // Group 1 - // Rule: If at least 2 of the RGB values are equal to or between 180 and - // 255, then apply specified opacity for Group 1 - // Examples: Vista, XP Silver, Wn High Con #2 - // Keyline = TITLE_BACKGROUND @ 70% Opacity over LIST_BACKGROUND - if (testTwoPrimaryColors(tbBorder, 179, 256)) - tbBorder = blend(tbBorder, bg, 70); - - // Group 2 - // Rule: If at least 2 of the RGB values are equal to or between 121 and - // 179, then apply specified opacity for Group 2 - // Examples: XP Olive, OSX Graphite, Linux GTK, Wn High Con Black - - // Keyline = TITLE_BACKGROUND @ 50% Opacity over LIST_BACKGROUND - else if (testTwoPrimaryColors(tbBorder, 120, 180)) - tbBorder = blend(tbBorder, bg, 50); - - // Group 3 - // Rule: Everything else - // Examples: XP Default, Wn Classic Standard, Wn Marine, Wn Plum, OSX - // Aqua, Wn High Con White, Wn High Con #1 - - // Keyline = TITLE_BACKGROUND @ 30% Opacity over LIST_BACKGROUND - else { - tbBorder = blend(tbBorder, bg, 30); - } - createColor(FormColors.TB_BORDER, tbBorder); - } - - private void updateFormHeaderColors() { - if (colorRegistry.containsKey(IFormColors.H_GRADIENT_END)) { - disposeIfFound(IFormColors.H_GRADIENT_END); - disposeIfFound(IFormColors.H_GRADIENT_START); - disposeIfFound(IFormColors.H_BOTTOM_KEYLINE1); - disposeIfFound(IFormColors.H_BOTTOM_KEYLINE2); - disposeIfFound(IFormColors.H_HOVER_LIGHT); - disposeIfFound(IFormColors.H_HOVER_FULL); - initializeFormHeaderColors(); - } - } - - private void disposeIfFound(String key) { - Color color = getColor(key); - if (color != null) { - colorRegistry.remove(key); - // RAP [rh] changes due to missing Color#dispose() -// color.dispose(); - } - } - - private void createFormHeaderColors() { - createFormHeaderGradientColors(); - createFormHeaderKeylineColors(); - createFormHeaderDNDColors(); - } - - private void createFormHeaderGradientColors() { - RGB titleBg = getSystemColor(SWT.COLOR_TITLE_BACKGROUND); - Color bgColor = getImpliedBackground(); - RGB bg = bgColor.getRGB(); - RGB bottom, top; - // Group 1 - // Rule: If at least 2 of the RGB values are equal to or between 180 and - // 255, then apply specified opacity for Group 1 - // Examples: Vista, XP Silver, Wn High Con #2 - // Gradient Bottom = TITLE_BACKGROUND @ 30% Opacity over LIST_BACKGROUND - // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND - if (testTwoPrimaryColors(titleBg, 179, 256)) { - bottom = blend(titleBg, bg, 30); - top = bg; - } - - // Group 2 - // Rule: If at least 2 of the RGB values are equal to or between 121 and - // 179, then apply specified opacity for Group 2 - // Examples: XP Olive, OSX Graphite, Linux GTK, Wn High Con Black - // Gradient Bottom = TITLE_BACKGROUND @ 20% Opacity over LIST_BACKGROUND - // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND - else if (testTwoPrimaryColors(titleBg, 120, 180)) { - bottom = blend(titleBg, bg, 20); - top = bg; - } - - // Group 3 - // Rule: If at least 2 of the RGB values are equal to or between 0 and - // 120, then apply specified opacity for Group 3 - // Examples: XP Default, Wn Classic Standard, Wn Marine, Wn Plum, OSX - // Aqua, Wn High Con White, Wn High Con #1 - // Gradient Bottom = TITLE_BACKGROUND @ 10% Opacity over LIST_BACKGROUND - // Gradient Top = TITLE BACKGROUND @ 0% Opacity over LIST_BACKGROUND - else { - bottom = blend(titleBg, bg, 10); - top = bg; - } - createColor(IFormColors.H_GRADIENT_END, top); - createColor(IFormColors.H_GRADIENT_START, bottom); - } - - private void createFormHeaderKeylineColors() { - RGB titleBg = getSystemColor(SWT.COLOR_TITLE_BACKGROUND); - Color bgColor = getImpliedBackground(); - RGB bg = bgColor.getRGB(); - RGB keyline2; - // H_BOTTOM_KEYLINE1 - createColor(IFormColors.H_BOTTOM_KEYLINE1, new RGB(255, 255, 255)); - - // H_BOTTOM_KEYLINE2 - // Group 1 - // Rule: If at least 2 of the RGB values are equal to or between 180 and - // 255, then apply specified opacity for Group 1 - // Examples: Vista, XP Silver, Wn High Con #2 - // Keyline = TITLE_BACKGROUND @ 70% Opacity over LIST_BACKGROUND - if (testTwoPrimaryColors(titleBg, 179, 256)) - keyline2 = blend(titleBg, bg, 70); - - // Group 2 - // Rule: If at least 2 of the RGB values are equal to or between 121 and - // 179, then apply specified opacity for Group 2 - // Examples: XP Olive, OSX Graphite, Linux GTK, Wn High Con Black - // Keyline = TITLE_BACKGROUND @ 50% Opacity over LIST_BACKGROUND - else if (testTwoPrimaryColors(titleBg, 120, 180)) - keyline2 = blend(titleBg, bg, 50); - - // Group 3 - // Rule: If at least 2 of the RGB values are equal to or between 0 and - // 120, then apply specified opacity for Group 3 - // Examples: XP Default, Wn Classic Standard, Wn Marine, Wn Plum, OSX - // Aqua, Wn High Con White, Wn High Con #1 - - // Keyline = TITLE_BACKGROUND @ 30% Opacity over LIST_BACKGROUND - else - keyline2 = blend(titleBg, bg, 30); - // H_BOTTOM_KEYLINE2 - createColor(IFormColors.H_BOTTOM_KEYLINE2, keyline2); - } - - private void createFormHeaderDNDColors() { - RGB titleBg = getSystemColor(SWT.COLOR_TITLE_BACKGROUND_GRADIENT); - Color bgColor = getImpliedBackground(); - RGB bg = bgColor.getRGB(); - RGB light, full; - // ALL Themes - // - // Light Highlight - // When *near* the 'hot' area - // Rule: If near the title in the 'hot' area, show background highlight - // TITLE_BACKGROUND_GRADIENT @ 40% - light = blend(titleBg, bg, 40); - // Full Highlight - // When *on* the title area (regions 1 and 2) - // Rule: If near the title in the 'hot' area, show background highlight - // TITLE_BACKGROUND_GRADIENT @ 60% - full = blend(titleBg, bg, 60); - // H_DND_LIGHT - // H_DND_FULL - createColor(IFormColors.H_HOVER_LIGHT, light); - createColor(IFormColors.H_HOVER_FULL, full); - } - - private LocalResourceManager getResourceManager() { - if (resources == null) - resources = new LocalResourceManager(JFaceResources.getResources()); - return resources; - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormFonts.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormFonts.java deleted file mode 100644 index 9e931ba50..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormFonts.java +++ /dev/null @@ -1,122 +0,0 @@ -package org.argeo.cms.ui.eclipse.forms; - -import java.util.HashMap; - -import org.eclipse.jface.resource.DeviceResourceException; -import org.eclipse.jface.resource.FontDescriptor; -import org.eclipse.jface.resource.JFaceResources; -import org.eclipse.jface.resource.LocalResourceManager; -import org.eclipse.swt.SWT; -import org.eclipse.swt.graphics.Device; -import org.eclipse.swt.graphics.Font; -import org.eclipse.swt.graphics.FontData; -//import org.eclipse.swt.internal.graphics.Graphics; -import org.eclipse.swt.widgets.Display; - -public class FormFonts { - private static FormFonts instance; - - public static FormFonts getInstance() { - if (instance == null) - instance = new FormFonts(); - return instance; - } - - private LocalResourceManager resources; - private HashMap descriptors; - - private FormFonts() { - } - - private class BoldFontDescriptor extends FontDescriptor { - private FontData[] fFontData; - - BoldFontDescriptor(Font font) { - // RAP [if] Changes due to different way of creating fonts - // fFontData = font.getFontData(); - // for (int i = 0; i < fFontData.length; i++) { - // fFontData[i].setStyle(fFontData[i].getStyle() | SWT.BOLD); - // } - FontData fontData = font.getFontData()[0]; - // Font boldFont = Graphics.getFont( fontData.getName(), - // fontData.getHeight(), - // fontData.getStyle() | SWT.BOLD ); - Font boldFont = new Font(Display.getCurrent(), fontData.getName(), fontData.getHeight(), - fontData.getStyle() | SWT.BOLD); - fFontData = boldFont.getFontData(); - } - - public boolean equals(Object obj) { - if (obj instanceof BoldFontDescriptor) { - BoldFontDescriptor desc = (BoldFontDescriptor) obj; - if (desc.fFontData.length != fFontData.length) - return false; - for (int i = 0; i < fFontData.length; i++) - if (!fFontData[i].equals(desc.fFontData[i])) - return false; - return true; - } - return false; - } - - public int hashCode() { - int hash = 0; - for (int i = 0; i < fFontData.length; i++) - hash = hash * 7 + fFontData[i].hashCode(); - return hash; - } - - public Font createFont(Device device) throws DeviceResourceException { - // RAP [if] Changes due to different way of creating fonts - return new Font(device, fFontData[0]); - // return Graphics.getFont( fFontData[ 0 ] ); - } - - public void destroyFont(Font previouslyCreatedFont) { - // RAP [if] unnecessary - // previouslyCreatedFont.dispose(); - } - } - - public Font getBoldFont(Display display, Font font) { - checkHashMaps(); - BoldFontDescriptor desc = new BoldFontDescriptor(font); - Font result = getResourceManager().createFont(desc); - descriptors.put(result, desc); - return result; - } - - public boolean markFinished(Font boldFont) { - checkHashMaps(); - BoldFontDescriptor desc = (BoldFontDescriptor) descriptors.get(boldFont); - if (desc != null) { - getResourceManager().destroyFont(desc); - if (getResourceManager().find(desc) == null) { - descriptors.remove(boldFont); - validateHashMaps(); - } - return true; - - } - // if the image was not found, dispose of it for the caller - // RAP [if] unnecessary - // boldFont.dispose(); - return false; - } - - private LocalResourceManager getResourceManager() { - if (resources == null) - resources = new LocalResourceManager(JFaceResources.getResources()); - return resources; - } - - private void checkHashMaps() { - if (descriptors == null) - descriptors = new HashMap(); - } - - private void validateHashMaps() { - if (descriptors.size() == 0) - descriptors = null; - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormToolkit.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormToolkit.java deleted file mode 100644 index 99271046a..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormToolkit.java +++ /dev/null @@ -1,913 +0,0 @@ -package org.argeo.cms.ui.eclipse.forms; - -import org.eclipse.jface.resource.JFaceResources; -import org.eclipse.jface.window.Window; -import org.eclipse.swt.SWT; -//import org.eclipse.swt.custom.CCombo; -import org.eclipse.swt.custom.ScrolledComposite; -import org.eclipse.swt.events.FocusAdapter; -import org.eclipse.swt.events.FocusEvent; -import org.eclipse.swt.events.KeyAdapter; -import org.eclipse.swt.events.KeyEvent; -import org.eclipse.swt.events.MouseAdapter; -import org.eclipse.swt.events.MouseEvent; -// RAP [rh] Paint events missing -//import org.eclipse.swt.events.PaintEvent; -//import org.eclipse.swt.events.PaintListener; -import org.eclipse.swt.graphics.Color; -import org.eclipse.swt.graphics.Font; -//RAP [rh] GC missing -//import org.eclipse.swt.graphics.GC; -import org.eclipse.swt.graphics.Point; -//import org.eclipse.swt.graphics.RGB; -//import org.eclipse.swt.graphics.Rectangle; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -//import org.eclipse.swt.widgets.Event; -import org.eclipse.swt.widgets.Label; -//import org.eclipse.swt.widgets.Listener; -import org.eclipse.swt.widgets.Table; -import org.eclipse.swt.widgets.Text; -import org.eclipse.swt.widgets.Tree; -import org.eclipse.swt.widgets.Widget; -//import org.eclipse.ui.forms.FormColors; -//import org.eclipse.ui.forms.HyperlinkGroup; -//import org.eclipse.ui.forms.IFormColors; -//import org.eclipse.ui.internal.forms.widgets.FormFonts; -//import org.eclipse.ui.internal.forms.widgets.FormUtil; - -/** - * The toolkit is responsible for creating SWT controls adapted to work in - * Eclipse forms. In addition to changing their presentation properties (fonts, - * colors etc.), various listeners are attached to make them behave correctly in - * the form context. - *

- * In addition to being the control factory, the toolkit is also responsible for - * painting flat borders for select controls, managing hyperlink groups and - * control colors. - *

- * The toolkit creates some of the most common controls used to populate Eclipse - * forms. Controls that must be created using their constructors, - * adapt() method is available to change its properties in the - * same way as with the supported toolkit controls. - *

- * Typically, one toolkit object is created per workbench part (for example, an - * editor or a form wizard). The toolkit is disposed when the part is disposed. - * To conserve resources, it is possible to create one color object for the - * entire plug-in and share it between several toolkits. The plug-in is - * responsible for disposing the colors (disposing the toolkit that uses shared - * color object will not dispose the colors). - *

- * FormToolkit is normally instantiated, but can also be subclassed if some of - * the methods needs to be modified. In those cases, super must - * be called to preserve normal behaviour. - * - * @since 1.0 - */ -public class FormToolkit { - public static final String KEY_DRAW_BORDER = "FormWidgetFactory.drawBorder"; //$NON-NLS-1$ - - public static final String TREE_BORDER = "treeBorder"; //$NON-NLS-1$ - - public static final String TEXT_BORDER = "textBorder"; //$NON-NLS-1$ - - private int borderStyle = SWT.NULL; - - private FormColors colors; - - private int orientation = Window.getDefaultOrientation(); - - // private KeyListener deleteListener; - // RAP [rh] Paint events missing -// private BorderPainter borderPainter; - - private BoldFontHolder boldFontHolder; - -// private HyperlinkGroup hyperlinkGroup; - - private boolean isDisposed = false; - - /* default */ - VisibilityHandler visibilityHandler; - - /* default */ - KeyboardHandler keyboardHandler; - - // RAP [rh] Paint events missing -// private class BorderPainter implements PaintListener { -// public void paintControl(PaintEvent event) { -// Composite composite = (Composite) event.widget; -// Control[] children = composite.getChildren(); -// for (int i = 0; i < children.length; i++) { -// Control c = children[i]; -// boolean inactiveBorder = false; -// boolean textBorder = false; -// if (!c.isVisible()) -// continue; -// /* -// * if (c.getEnabled() == false && !(c instanceof CCombo)) -// * continue; -// */ -// if (c instanceof Hyperlink) -// continue; -// Object flag = c.getData(KEY_DRAW_BORDER); -// if (flag != null) { -// if (flag.equals(Boolean.FALSE)) -// continue; -// if (flag.equals(TREE_BORDER)) -// inactiveBorder = true; -// else if (flag.equals(TEXT_BORDER)) -// textBorder = true; -// } -// if (getBorderStyle() == SWT.BORDER) { -// if (!inactiveBorder && !textBorder) { -// continue; -// } -// if (c instanceof Text || c instanceof Table -// || c instanceof Tree) -// continue; -// } -// if (!inactiveBorder -// && (c instanceof Text || c instanceof CCombo || textBorder)) { -// Rectangle b = c.getBounds(); -// GC gc = event.gc; -// gc.setForeground(c.getBackground()); -// gc.drawRectangle(b.x - 1, b.y - 1, b.width + 1, -// b.height + 1); -// // gc.setForeground(getBorderStyle() == SWT.BORDER ? colors -// // .getBorderColor() : colors.getForeground()); -// gc.setForeground(colors.getBorderColor()); -// if (c instanceof CCombo) -// gc.drawRectangle(b.x - 1, b.y - 1, b.width + 1, -// b.height + 1); -// else -// gc.drawRectangle(b.x - 1, b.y - 2, b.width + 1, -// b.height + 3); -// } else if (inactiveBorder || c instanceof Table -// || c instanceof Tree) { -// Rectangle b = c.getBounds(); -// GC gc = event.gc; -// gc.setForeground(colors.getBorderColor()); -// gc.drawRectangle(b.x - 1, b.y - 1, b.width + 1, -// b.height + 1); -// } -// } -// } -// } - - private static class VisibilityHandler extends FocusAdapter { - public void focusGained(FocusEvent e) { - Widget w = e.widget; - if (w instanceof Control) { - FormUtil.ensureVisible((Control) w); - } - } - } - - private static class KeyboardHandler extends KeyAdapter { - public void keyPressed(KeyEvent e) { - Widget w = e.widget; - if (w instanceof Control) { - if (e.doit) - FormUtil.processKey(e.keyCode, (Control) w); - } - } - } - - private class BoldFontHolder { - private Font normalFont; - - private Font boldFont; - - public BoldFontHolder() { - } - - public Font getBoldFont(Font font) { - createBoldFont(font); - return boldFont; - } - - private void createBoldFont(Font font) { - if (normalFont == null || !normalFont.equals(font)) { - normalFont = font; - dispose(); - } - if (boldFont == null) { - boldFont = FormFonts.getInstance().getBoldFont(colors.getDisplay(), - normalFont); - } - } - - public void dispose() { - if (boldFont != null) { - FormFonts.getInstance().markFinished(boldFont); - boldFont = null; - } - } - } - - /** - * Creates a toolkit that is self-sufficient (will manage its own colors). - *

- * Clients that call this method must call {@link #dispose()} when they - * are finished using the toolkit. - * - */ - public FormToolkit(Display display) { - this(new FormColors(display)); - } - - /** - * Creates a toolkit that will use the provided (shared) colors. The toolkit - * will dispose the colors if and only if they are not marked as - * shared via the markShared() method. - *

- * Clients that call this method must call {@link #dispose()} when they - * are finished using the toolkit. - * - * @param colors - * the shared colors - */ - public FormToolkit(FormColors colors) { - this.colors = colors; - initialize(); - } - - /** - * Creates a button as a part of the form. - * - * @param parent - * the button parent - * @param text - * an optional text for the button (can be null) - * @param style - * the button style (for example, SWT.PUSH) - * @return the button widget - */ - public Button createButton(Composite parent, String text, int style) { - Button button = new Button(parent, style | SWT.FLAT | orientation); - if (text != null) - button.setText(text); - adapt(button, true, true); - return button; - } - - /** - * Creates the composite as a part of the form. - * - * @param parent - * the composite parent - * @return the composite widget - */ - public Composite createComposite(Composite parent) { - return createComposite(parent, SWT.NULL); - } - - /** - * Creates the composite as part of the form using the provided style. - * - * @param parent - * the composite parent - * @param style - * the composite style - * @return the composite widget - */ - public Composite createComposite(Composite parent, int style) { -// Composite composite = new LayoutComposite(parent, style | orientation); - Composite composite = new Composite(parent, style | orientation); - adapt(composite); - return composite; - } - - /** - * Creats the composite that can server as a separator between various parts - * of a form. Separator height should be controlled by setting the height - * hint on the layout data for the composite. - * - * @param parent - * the separator parent - * @return the separator widget - */ -// RAP [rh] createCompositeSeparator: currently no useful implementation possible, delete? - public Composite createCompositeSeparator(Composite parent) { - final Composite composite = new Composite(parent, orientation); -// RAP [rh] GC and paint events missing -// composite.addListener(SWT.Paint, new Listener() { -// public void handleEvent(Event e) { -// if (composite.isDisposed()) -// return; -// Rectangle bounds = composite.getBounds(); -// GC gc = e.gc; -// gc.setForeground(colors.getColor(IFormColors.SEPARATOR)); -// if (colors.getBackground() != null) -// gc.setBackground(colors.getBackground()); -// gc.fillGradientRectangle(0, 0, bounds.width, bounds.height, -// false); -// } -// }); -// if (parent instanceof Section) -// ((Section) parent).setSeparatorControl(composite); - return composite; - } - - /** - * Creates a label as a part of the form. - * - * @param parent - * the label parent - * @param text - * the label text - * @return the label widget - */ - public Label createLabel(Composite parent, String text) { - return createLabel(parent, text, SWT.NONE); - } - - /** - * Creates a label as a part of the form. - * - * @param parent - * the label parent - * @param text - * the label text - * @param style - * the label style - * @return the label widget - */ - public Label createLabel(Composite parent, String text, int style) { - Label label = new Label(parent, style | orientation); - if (text != null) - label.setText(text); - adapt(label, false, false); - return label; - } - - /** - * Creates a hyperlink as a part of the form. The hyperlink will be added to - * the hyperlink group that belongs to this toolkit. - * - * @param parent - * the hyperlink parent - * @param text - * the text of the hyperlink - * @param style - * the hyperlink style - * @return the hyperlink widget - */ -// public Hyperlink createHyperlink(Composite parent, String text, int style) { -// Hyperlink hyperlink = new Hyperlink(parent, style | orientation); -// if (text != null) -// hyperlink.setText(text); -// hyperlink.addFocusListener(visibilityHandler); -// hyperlink.addKeyListener(keyboardHandler); -// hyperlinkGroup.add(hyperlink); -// return hyperlink; -// } - - /** - * Creates an image hyperlink as a part of the form. The hyperlink will be - * added to the hyperlink group that belongs to this toolkit. - * - * @param parent - * the hyperlink parent - * @param style - * the hyperlink style - * @return the image hyperlink widget - */ -// public ImageHyperlink createImageHyperlink(Composite parent, int style) { -// ImageHyperlink hyperlink = new ImageHyperlink(parent, style -// | orientation); -// hyperlink.addFocusListener(visibilityHandler); -// hyperlink.addKeyListener(keyboardHandler); -// hyperlinkGroup.add(hyperlink); -// return hyperlink; -// } - - /** - * Creates a rich text as a part of the form. - * - * @param parent - * the rich text parent - * @param trackFocus - * if true, the toolkit will monitor focus - * transfers to ensure that the hyperlink in focus is visible in - * the form. - * @return the rich text widget - * @since 1.2 - */ -// public FormText createFormText(Composite parent, boolean trackFocus) { -// FormText engine = new FormText(parent, SWT.WRAP | orientation); -// engine.marginWidth = 1; -// engine.marginHeight = 0; -// engine.setHyperlinkSettings(getHyperlinkGroup()); -// adapt(engine, trackFocus, true); -// engine.setMenu(parent.getMenu()); -// return engine; -// } - - /** - * Adapts a control to be used in a form that is associated with this - * toolkit. This involves adjusting colors and optionally adding handlers to - * ensure focus tracking and keyboard management. - * - * @param control - * a control to adapt - * @param trackFocus - * if true, form will be scrolled horizontally - * and/or vertically if needed to ensure that the control is - * visible when it gains focus. Set it to false if - * the control is not capable of gaining focus. - * @param trackKeyboard - * if true, the control that is capable of - * gaining focus will be tracked for certain keys that are - * important to the underlying form (for example, PageUp, - * PageDown, ScrollUp, ScrollDown etc.). Set it to - * false if the control is not capable of gaining - * focus or these particular key event are already used by the - * control. - */ - public void adapt(Control control, boolean trackFocus, boolean trackKeyboard) { - control.setBackground(colors.getBackground()); - control.setForeground(colors.getForeground()); -// if (control instanceof ExpandableComposite) { -// ExpandableComposite ec = (ExpandableComposite) control; -// if (ec.toggle != null) { -// if (trackFocus) -// ec.toggle.addFocusListener(visibilityHandler); -// if (trackKeyboard) -// ec.toggle.addKeyListener(keyboardHandler); -// } -// if (ec.textLabel != null) { -// if (trackFocus) -// ec.textLabel.addFocusListener(visibilityHandler); -// if (trackKeyboard) -// ec.textLabel.addKeyListener(keyboardHandler); -// } -// return; -// } - if (trackFocus) - control.addFocusListener(visibilityHandler); - if (trackKeyboard) - control.addKeyListener(keyboardHandler); - } - - /** - * Adapts a composite to be used in a form associated with this toolkit. - * - * @param composite - * the composite to adapt - */ - public void adapt(Composite composite) { - composite.setBackground(colors.getBackground()); - composite.addMouseListener(new MouseAdapter() { - public void mouseDown(MouseEvent e) { - ((Control) e.widget).setFocus(); - } - }); - if (composite.getParent() != null) - composite.setMenu(composite.getParent().getMenu()); - } - - /** - * A helper method that ensures the provided control is visible when - * ScrolledComposite is somewhere in the parent chain. If scroll bars are - * visible and the control is clipped, the client of the scrolled composite - * will be scrolled to reveal the control. - * - * @param c - * the control to reveal - */ - public static void ensureVisible(Control c) { - FormUtil.ensureVisible(c); - } - - /** - * Creates a section as a part of the form. - * - * @param parent - * the section parent - * @param sectionStyle - * the section style - * @return the section widget - */ -// public Section createSection(Composite parent, int sectionStyle) { -// Section section = new Section(parent, orientation, sectionStyle); -// section.setMenu(parent.getMenu()); -// adapt(section, true, true); -// if (section.toggle != null) { -// section.toggle.setHoverDecorationColor(colors -// .getColor(IFormColors.TB_TOGGLE_HOVER)); -// section.toggle.setDecorationColor(colors -// .getColor(IFormColors.TB_TOGGLE)); -// } -// section.setFont(boldFontHolder.getBoldFont(parent.getFont())); -// if ((sectionStyle & Section.TITLE_BAR) != 0 -// || (sectionStyle & Section.SHORT_TITLE_BAR) != 0) { -// colors.initializeSectionToolBarColors(); -// section.setTitleBarBackground(colors.getColor(IFormColors.TB_BG)); -// section.setTitleBarBorderColor(colors -// .getColor(IFormColors.TB_BORDER)); -// } -// // call setTitleBarForeground regardless as it also sets the label color -// section.setTitleBarForeground(colors -// .getColor(IFormColors.TB_TOGGLE)); -// return section; -// } - - /** - * Creates an expandable composite as a part of the form. - * - * @param parent - * the expandable composite parent - * @param expansionStyle - * the expandable composite style - * @return the expandable composite widget - */ -// public ExpandableComposite createExpandableComposite(Composite parent, -// int expansionStyle) { -// ExpandableComposite ec = new ExpandableComposite(parent, orientation, -// expansionStyle); -// ec.setMenu(parent.getMenu()); -// adapt(ec, true, true); -// ec.setFont(boldFontHolder.getBoldFont(ec.getFont())); -// return ec; -// } - - /** - * Creates a separator label as a part of the form. - * - * @param parent - * the separator parent - * @param style - * the separator style - * @return the separator label - */ - public Label createSeparator(Composite parent, int style) { - Label label = new Label(parent, SWT.SEPARATOR | style | orientation); - label.setBackground(colors.getBackground()); - label.setForeground(colors.getBorderColor()); - return label; - } - - /** - * Creates a table as a part of the form. - * - * @param parent - * the table parent - * @param style - * the table style - * @return the table widget - */ - public Table createTable(Composite parent, int style) { - Table table = new Table(parent, style | borderStyle | orientation); - adapt(table, false, false); - // hookDeleteListener(table); - return table; - } - - /** - * Creates a text as a part of the form. - * - * @param parent - * the text parent - * @param value - * the text initial value - * @return the text widget - */ - public Text createText(Composite parent, String value) { - return createText(parent, value, SWT.SINGLE); - } - - /** - * Creates a text as a part of the form. - * - * @param parent - * the text parent - * @param value - * the text initial value - * @param style - * the text style - * @return the text widget - */ - public Text createText(Composite parent, String value, int style) { - Text text = new Text(parent, borderStyle | style | orientation); - if (value != null) - text.setText(value); - text.setForeground(colors.getForeground()); - text.setBackground(colors.getBackground()); - text.addFocusListener(visibilityHandler); - return text; - } - - /** - * Creates a tree widget as a part of the form. - * - * @param parent - * the tree parent - * @param style - * the tree style - * @return the tree widget - */ - public Tree createTree(Composite parent, int style) { - Tree tree = new Tree(parent, borderStyle | style | orientation); - adapt(tree, false, false); - // hookDeleteListener(tree); - return tree; - } - - /** - * Creates a scrolled form widget in the provided parent. If you do not - * require scrolling because there is already a scrolled composite up the - * parent chain, use 'createForm' instead. - * - * @param parent - * the scrolled form parent - * @return the form that can scroll itself - * @see #createForm - */ - public ScrolledComposite createScrolledForm(Composite parent) { - ScrolledComposite form = new ScrolledComposite(parent, SWT.V_SCROLL - | SWT.H_SCROLL | orientation); - form.setExpandHorizontal(true); - form.setExpandVertical(true); - form.setBackground(colors.getBackground()); - form.setForeground(colors.getColor(IFormColors.TITLE)); - form.setFont(JFaceResources.getHeaderFont()); - return form; - } - - /** - * Creates a form widget in the provided parent. Note that this widget does - * not scroll its content, so make sure there is a scrolled composite up the - * parent chain. If you require scrolling, use 'createScrolledForm' instead. - * - * @param parent - * the form parent - * @return the form that does not scroll - * @see #createScrolledForm - */ -// public Form createForm(Composite parent) { -// Form formContent = new Form(parent, orientation); -// formContent.setBackground(colors.getBackground()); -// formContent.setForeground(colors.getColor(IFormColors.TITLE)); -// formContent.setFont(JFaceResources.getHeaderFont()); -// return formContent; -// } - - /** - * Takes advantage of the gradients and other capabilities to decorate the - * form heading using colors computed based on the current skin and - * operating system. - * - * @param form - * the form to decorate - */ - -// public void decorateFormHeading(Form form) { -// Color top = colors.getColor(IFormColors.H_GRADIENT_END); -// Color bot = colors.getColor(IFormColors.H_GRADIENT_START); -// form.setTextBackground(new Color[] { top, bot }, new int[] { 100 }, -// true); -// form.setHeadColor(IFormColors.H_BOTTOM_KEYLINE1, colors -// .getColor(IFormColors.H_BOTTOM_KEYLINE1)); -// form.setHeadColor(IFormColors.H_BOTTOM_KEYLINE2, colors -// .getColor(IFormColors.H_BOTTOM_KEYLINE2)); -// form.setHeadColor(IFormColors.H_HOVER_LIGHT, colors -// .getColor(IFormColors.H_HOVER_LIGHT)); -// form.setHeadColor(IFormColors.H_HOVER_FULL, colors -// .getColor(IFormColors.H_HOVER_FULL)); -// form.setHeadColor(IFormColors.TB_TOGGLE, colors -// .getColor(IFormColors.TB_TOGGLE)); -// form.setHeadColor(IFormColors.TB_TOGGLE_HOVER, colors -// .getColor(IFormColors.TB_TOGGLE_HOVER)); -// form.setSeparatorVisible(true); -// } - - /** - * Creates a scrolled page book widget as a part of the form. - * - * @param parent - * the page book parent - * @param style - * the text style - * @return the scrolled page book widget - */ -// public ScrolledPageBook createPageBook(Composite parent, int style) { -// ScrolledPageBook book = new ScrolledPageBook(parent, style -// | orientation); -// adapt(book, true, true); -// book.setMenu(parent.getMenu()); -// return book; -// } - - /** - * Disposes the toolkit. - */ - public void dispose() { - if (isDisposed) { - return; - } - isDisposed = true; - if (colors.isShared() == false) { - colors.dispose(); - colors = null; - } - boldFontHolder.dispose(); - } - - /** - * Returns the hyperlink group that manages hyperlinks for this toolkit. - * - * @return the hyperlink group - */ -// public HyperlinkGroup getHyperlinkGroup() { -// return hyperlinkGroup; -// } - - /** - * Sets the background color for the entire toolkit. The method delegates - * the call to the FormColors object and also updates the hyperlink group so - * that hyperlinks and other objects are in sync. - * - * @param bg - * the new background color - */ - public void setBackground(Color bg) { -// hyperlinkGroup.setBackground(bg); - colors.setBackground(bg); - } - - /** - * Refreshes the hyperlink colors by loading from JFace settings. - */ -// public void refreshHyperlinkColors() { -// hyperlinkGroup.initializeDefaultForegrounds(colors.getDisplay()); -// } - -// RAP [rh] paintBordersFor not useful as no GC to actually paint borders -// /** -// * Paints flat borders for widgets created by this toolkit within the -// * provided parent. Borders will not be painted if the global border style -// * is SWT.BORDER (i.e. if native borders are used). Call this method during -// * creation of a form composite to get the borders of its children painted. -// * Care should be taken when selection layout margins. At least one pixel -// * pargin width and height must be chosen to allow the toolkit to paint the -// * border on the parent around the widgets. -// *

-// * Borders are painted for some controls that are selected by the toolkit by -// * default. If a control needs a border but is not on its list, it is -// * possible to force border in the following way: -// * -// *

-//	 *
-//	 *
-//	 *
-//	 *             widget.setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TREE_BORDER);
-//	 *
-//	 *             or
-//	 *
-//	 *             widget.setData(FormToolkit.KEY_DRAW_BORDER, FormToolkit.TEXT_BORDER);
-//	 *
-//	 *
-//	 *
-//	 * 
-// * -// * @param parent -// * the parent that owns the children for which the border needs -// * to be painted. -// */ -// public void paintBordersFor(Composite parent) { -// // if (borderStyle == SWT.BORDER) -// // return; -// if (borderPainter == null) -// borderPainter = new BorderPainter(); -// parent.addPaintListener(borderPainter); -// } - - /** - * Returns the colors used by this toolkit. - * - * @return the color object - */ - public FormColors getColors() { - return colors; - } - - /** - * Returns the border style used for various widgets created by this - * toolkit. The intent of the toolkit is to create controls with styles that - * yield a 'flat' appearance. On systems where the native borders are - * already flat, we set the style to SWT.BORDER and don't paint the borders - * ourselves. Otherwise, the style is set to SWT.NULL, and borders are - * painted by the toolkit. - * - * @return the global border style - */ - public int getBorderStyle() { - return borderStyle; - } - - /** - * Returns the margin required around the children whose border is being - * painted by the toolkit using {@link #paintBordersFor(Composite)}. Since - * the border is painted around the controls on the parent, a number of - * pixels needs to be reserved for this border. For windowing systems where - * the native border is used, this margin is 0. - * - * @return the margin in the parent when children have their border painted - */ - public int getBorderMargin() { - return getBorderStyle() == SWT.BORDER ? 0 : 2; - } - - /** - * Sets the border style to be used when creating widgets. The toolkit - * chooses the correct style based on the platform but this value can be - * changed using this method. - * - * @param style - * SWT.BORDER or SWT.NULL - * @see #getBorderStyle - */ - public void setBorderStyle(int style) { - this.borderStyle = style; - } - - /** - * A utility method that ensures that the control is visible in the scrolled - * composite. The prerequisite for this method is that the control has a - * class that extends ScrolledComposite somewhere in the parent chain. If - * the control is partially or fully clipped, the composite is scrolled to - * set by setting the origin to the control origin. - * - * @param c - * the control to make visible - * @param verticalOnly - * if true, the scrolled composite will be - * scrolled only vertically if needed. Otherwise, the scrolled - * composite origin will be set to the control origin. - */ - public static void setControlVisible(Control c, boolean verticalOnly) { - ScrolledComposite scomp = FormUtil.getScrolledComposite(c); - if (scomp == null) - return; - Point location = FormUtil.getControlLocation(scomp, c); - scomp.setOrigin(location); - } - - private void initialize() { - initializeBorderStyle(); -// hyperlinkGroup = new HyperlinkGroup(colors.getDisplay()); -// hyperlinkGroup.setBackground(colors.getBackground()); - visibilityHandler = new VisibilityHandler(); - keyboardHandler = new KeyboardHandler(); - boldFontHolder = new BoldFontHolder(); - } - -// RAP [rh] revise detection of border style: can't ask OS here - private void initializeBorderStyle() { -// String osname = System.getProperty("os.name"); //$NON-NLS-1$ -// String osversion = System.getProperty("os.version"); //$NON-NLS-1$ -// if (osname.startsWith("Windows") && "5.1".compareTo(osversion) <= 0) { //$NON-NLS-1$ //$NON-NLS-2$ -// // Skinned widgets used on newer Windows (e.g. XP (5.1), Vista -// // (6.0)) -// // Check for Windows Classic. If not used, set the style to BORDER -// RGB rgb = colors.getSystemColor(SWT.COLOR_WIDGET_BACKGROUND); -// if (rgb.red != 212 || rgb.green != 208 || rgb.blue != 200) -// borderStyle = SWT.BORDER; -// } else if (osname.startsWith("Mac")) //$NON-NLS-1$ -// borderStyle = SWT.BORDER; - - borderStyle = SWT.BORDER; - } - - /** - * Returns the orientation that all the widgets created by this toolkit will - * inherit, if set. Can be SWT.NULL, - * SWT.LEFT_TO_RIGHT and SWT.RIGHT_TO_LEFT. - * - * @return orientation style for this toolkit, or SWT.NULL if - * not set. The default orientation is inherited from the Window - * default orientation. - * @see org.eclipse.jface.window.Window#getDefaultOrientation() - */ - - public int getOrientation() { - return orientation; - } - - /** - * Sets the orientation that all the widgets created by this toolkit will - * inherit. Can be SWT.NULL, SWT.LEFT_TO_RIGHT - * and SWT.RIGHT_TO_LEFT. - * - * @param orientation - * style for this toolkit. - */ - - public void setOrientation(int orientation) { - this.orientation = orientation; - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormUtil.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormUtil.java deleted file mode 100644 index 76e3f1104..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/FormUtil.java +++ /dev/null @@ -1,522 +0,0 @@ -package org.argeo.cms.ui.eclipse.forms; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.ScrolledComposite; -import org.eclipse.swt.events.MouseEvent; -//import org.eclipse.swt.graphics.Device; -import org.eclipse.swt.graphics.FontMetrics; -import org.eclipse.swt.graphics.GC; -//import org.eclipse.swt.graphics.Image; -//import org.eclipse.swt.graphics.ImageData; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.graphics.Rectangle; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Combo; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Layout; -//import org.eclipse.swt.widgets.ScrollBar; -import org.eclipse.swt.widgets.Text; -//import org.eclipse.ui.forms.widgets.ColumnLayout; -//import org.eclipse.ui.forms.widgets.Form; -//import org.eclipse.ui.forms.widgets.FormText; -//import org.eclipse.ui.forms.widgets.FormToolkit; -//import org.eclipse.ui.forms.widgets.ILayoutExtension; -// -//import com.ibm.icu.text.BreakIterator; - -public class FormUtil { - public static final String PLUGIN_ID = "org.eclipse.ui.forms"; //$NON-NLS-1$ - - static final int H_SCROLL_INCREMENT = 5; - - static final int V_SCROLL_INCREMENT = 64; - - public static final String DEBUG = PLUGIN_ID + "/debug"; //$NON-NLS-1$ - - public static final String DEBUG_TEXT = DEBUG + "/text"; //$NON-NLS-1$ - public static final String DEBUG_TEXTSIZE = DEBUG + "/textsize"; //$NON-NLS-1$ - - public static final String DEBUG_FOCUS = DEBUG + "/focus"; //$NON-NLS-1$ - - public static final String FOCUS_SCROLLING = "focusScrolling"; //$NON-NLS-1$ - - public static final String IGNORE_BODY = "__ignore_body__"; //$NON-NLS-1$ - - public static Text createText(Composite parent, String label, - FormToolkit factory) { - return createText(parent, label, factory, 1); - } - - public static Text createText(Composite parent, String label, - FormToolkit factory, int span) { - factory.createLabel(parent, label); - Text text = factory.createText(parent, ""); //$NON-NLS-1$ - int hfill = span == 1 ? GridData.FILL_HORIZONTAL - : GridData.HORIZONTAL_ALIGN_FILL; - GridData gd = new GridData(hfill | GridData.VERTICAL_ALIGN_CENTER); - gd.horizontalSpan = span; - text.setLayoutData(gd); - return text; - } - - public static Text createText(Composite parent, String label, - FormToolkit factory, int span, int style) { - Label l = factory.createLabel(parent, label); - if ((style & SWT.MULTI) != 0) { - GridData gd = new GridData(GridData.VERTICAL_ALIGN_BEGINNING); - l.setLayoutData(gd); - } - Text text = factory.createText(parent, "", style); //$NON-NLS-1$ - int hfill = span == 1 ? GridData.FILL_HORIZONTAL - : GridData.HORIZONTAL_ALIGN_FILL; - GridData gd = new GridData(hfill | GridData.VERTICAL_ALIGN_CENTER); - gd.horizontalSpan = span; - text.setLayoutData(gd); - return text; - } - - public static Text createText(Composite parent, FormToolkit factory, - int span) { - Text text = factory.createText(parent, ""); //$NON-NLS-1$ - int hfill = span == 1 ? GridData.FILL_HORIZONTAL - : GridData.HORIZONTAL_ALIGN_FILL; - GridData gd = new GridData(hfill | GridData.VERTICAL_ALIGN_CENTER); - gd.horizontalSpan = span; - text.setLayoutData(gd); - return text; - } - - public static int computeMinimumWidth(GC gc, String text) { -// BreakIterator wb = BreakIterator.getWordInstance(); -// wb.setText(text); -// int last = 0; -// -// int width = 0; -// -// for (int loc = wb.first(); loc != BreakIterator.DONE; loc = wb.next()) { -// String word = text.substring(last, loc); -// Point extent = gc.textExtent(word); -// width = Math.max(width, extent.x); -// last = loc; -// } -// String lastWord = text.substring(last); -// Point extent = gc.textExtent(lastWord); -// width = Math.max(width, extent.x); -// return width; - return 0; - } - - public static Point computeWrapSize(GC gc, String text, int wHint) { -// BreakIterator wb = BreakIterator.getWordInstance(); -// wb.setText(text); - FontMetrics fm = gc.getFontMetrics(); - int lineHeight = fm.getHeight(); - - int saved = 0; - int last = 0; - int height = lineHeight; - int maxWidth = 0; -// for (int loc = wb.first(); loc != BreakIterator.DONE; loc = wb.next()) { -// String word = text.substring(saved, loc); -// Point extent = gc.textExtent(word); -// if (extent.x > wHint) { -// // overflow -// saved = last; -// height += extent.y; -// // switch to current word so maxWidth will accommodate very long single words -// word = text.substring(last, loc); -// extent = gc.textExtent(word); -// } -// maxWidth = Math.max(maxWidth, extent.x); -// last = loc; -// } - /* - * Correct the height attribute in case it was calculated wrong due to wHint being less than maxWidth. - * The recursive call proved to be the only thing that worked in all cases. Some attempts can be made - * to estimate the height, but the algorithm needs to be run again to be sure. - */ - if (maxWidth > wHint) - return computeWrapSize(gc, text, maxWidth); - return new Point(maxWidth, height); - } - -// RAP [rh] paintWrapText unnecessary -// public static void paintWrapText(GC gc, String text, Rectangle bounds) { -// paintWrapText(gc, text, bounds, false); -// } - -// RAP [rh] paintWrapText unnecessary -// public static void paintWrapText(GC gc, String text, Rectangle bounds, -// boolean underline) { -// BreakIterator wb = BreakIterator.getWordInstance(); -// wb.setText(text); -// FontMetrics fm = gc.getFontMetrics(); -// int lineHeight = fm.getHeight(); -// int descent = fm.getDescent(); -// -// int saved = 0; -// int last = 0; -// int y = bounds.y; -// int width = bounds.width; -// -// for (int loc = wb.first(); loc != BreakIterator.DONE; loc = wb.next()) { -// String line = text.substring(saved, loc); -// Point extent = gc.textExtent(line); -// -// if (extent.x > width) { -// // overflow -// String prevLine = text.substring(saved, last); -// gc.drawText(prevLine, bounds.x, y, true); -// if (underline) { -// Point prevExtent = gc.textExtent(prevLine); -// int lineY = y + lineHeight - descent + 1; -// gc -// .drawLine(bounds.x, lineY, bounds.x + prevExtent.x, -// lineY); -// } -// -// saved = last; -// y += lineHeight; -// } -// last = loc; -// } -// // paint the last line -// String lastLine = text.substring(saved, last); -// gc.drawText(lastLine, bounds.x, y, true); -// if (underline) { -// int lineY = y + lineHeight - descent + 1; -// Point lastExtent = gc.textExtent(lastLine); -// gc.drawLine(bounds.x, lineY, bounds.x + lastExtent.x, lineY); -// } -// } - - public static ScrolledComposite getScrolledComposite(Control c) { - Composite parent = c.getParent(); - - while (parent != null) { - if (parent instanceof ScrolledComposite) { - return (ScrolledComposite) parent; - } - parent = parent.getParent(); - } - return null; - } - - public static void ensureVisible(Control c) { - ScrolledComposite scomp = getScrolledComposite(c); - if (scomp != null) { - Object data = scomp.getData(FOCUS_SCROLLING); - if (data == null || !data.equals(Boolean.FALSE)) - FormUtil.ensureVisible(scomp, c); - } - } - - public static void ensureVisible(ScrolledComposite scomp, Control control) { - // if the control is a FormText we do not need to scroll since it will - // ensure visibility of its segments as necessary -// if (control instanceof FormText) -// return; - Point controlSize = control.getSize(); - Point controlOrigin = getControlLocation(scomp, control); - ensureVisible(scomp, controlOrigin, controlSize); - } - - public static void ensureVisible(ScrolledComposite scomp, - Point controlOrigin, Point controlSize) { - Rectangle area = scomp.getClientArea(); - Point scompOrigin = scomp.getOrigin(); - - int x = scompOrigin.x; - int y = scompOrigin.y; - - // horizontal right, but only if the control is smaller - // than the client area - if (controlSize.x < area.width - && (controlOrigin.x + controlSize.x > scompOrigin.x - + area.width)) { - x = controlOrigin.x + controlSize.x - area.width; - } - // horizontal left - make sure the left edge of - // the control is showing - if (controlOrigin.x < x) { - if (controlSize.x < area.width) - x = controlOrigin.x + controlSize.x - area.width; - else - x = controlOrigin.x; - } - // vertical bottom - if (controlSize.y < area.height - && (controlOrigin.y + controlSize.y > scompOrigin.y - + area.height)) { - y = controlOrigin.y + controlSize.y - area.height; - } - // vertical top - make sure the top of - // the control is showing - if (controlOrigin.y < y) { - if (controlSize.y < area.height) - y = controlOrigin.y + controlSize.y - area.height; - else - y = controlOrigin.y; - } - - if (scompOrigin.x != x || scompOrigin.y != y) { - // scroll to reveal - scomp.setOrigin(x, y); - } - } - - public static void ensureVisible(ScrolledComposite scomp, Control control, - MouseEvent e) { - Point controlOrigin = getControlLocation(scomp, control); - int rX = controlOrigin.x + e.x; - int rY = controlOrigin.y + e.y; - Rectangle area = scomp.getClientArea(); - Point scompOrigin = scomp.getOrigin(); - - int x = scompOrigin.x; - int y = scompOrigin.y; - // System.out.println("Ensure: area="+area+", origin="+scompOrigin+", - // cloc="+controlOrigin+", csize="+controlSize+", x="+x+", y="+y); - - // horizontal right - if (rX > scompOrigin.x + area.width) { - x = rX - area.width; - } - // horizontal left - else if (rX < x) { - x = rX; - } - // vertical bottom - if (rY > scompOrigin.y + area.height) { - y = rY - area.height; - } - // vertical top - else if (rY < y) { - y = rY; - } - - if (scompOrigin.x != x || scompOrigin.y != y) { - // scroll to reveal - scomp.setOrigin(x, y); - } - } - - public static Point getControlLocation(ScrolledComposite scomp, - Control control) { - int x = 0; - int y = 0; - Control content = scomp.getContent(); - Control currentControl = control; - for (;;) { - if (currentControl == content) - break; - Point location = currentControl.getLocation(); - // if (location.x > 0) - // x += location.x; - // if (location.y > 0) - // y += location.y; - x += location.x; - y += location.y; - currentControl = currentControl.getParent(); - } - return new Point(x, y); - } - - static void scrollVertical(ScrolledComposite scomp, boolean up) { - scroll(scomp, 0, up ? -V_SCROLL_INCREMENT : V_SCROLL_INCREMENT); - } - - static void scrollHorizontal(ScrolledComposite scomp, boolean left) { - scroll(scomp, left ? -H_SCROLL_INCREMENT : H_SCROLL_INCREMENT, 0); - } - - static void scrollPage(ScrolledComposite scomp, boolean up) { - Rectangle clientArea = scomp.getClientArea(); - int increment = up ? -clientArea.height : clientArea.height; - scroll(scomp, 0, increment); - } - - static void scroll(ScrolledComposite scomp, int xoffset, int yoffset) { - Point origin = scomp.getOrigin(); - Point contentSize = scomp.getContent().getSize(); - int xorigin = origin.x + xoffset; - int yorigin = origin.y + yoffset; - xorigin = Math.max(xorigin, 0); - xorigin = Math.min(xorigin, contentSize.x - 1); - yorigin = Math.max(yorigin, 0); - yorigin = Math.min(yorigin, contentSize.y - 1); - scomp.setOrigin(xorigin, yorigin); - } - -// RAP [rh] FormUtil#updatePageIncrement: empty implementation - public static void updatePageIncrement(ScrolledComposite scomp) { -// ScrollBar vbar = scomp.getVerticalBar(); -// if (vbar != null) { -// Rectangle clientArea = scomp.getClientArea(); -// int increment = clientArea.height - 5; -// vbar.setPageIncrement(increment); -// } -// ScrollBar hbar = scomp.getHorizontalBar(); -// if (hbar != null) { -// Rectangle clientArea = scomp.getClientArea(); -// int increment = clientArea.width - 5; -// hbar.setPageIncrement(increment); -// } - } - - public static void processKey(int keyCode, Control c) { - if (c.isDisposed()) { - return; - } - ScrolledComposite scomp = FormUtil.getScrolledComposite(c); - if (scomp != null) { - if (c instanceof Combo) - return; - switch (keyCode) { - case SWT.ARROW_DOWN: - if (scomp.getData("novarrows") == null) //$NON-NLS-1$ - FormUtil.scrollVertical(scomp, false); - break; - case SWT.ARROW_UP: - if (scomp.getData("novarrows") == null) //$NON-NLS-1$ - FormUtil.scrollVertical(scomp, true); - break; - case SWT.ARROW_LEFT: - FormUtil.scrollHorizontal(scomp, true); - break; - case SWT.ARROW_RIGHT: - FormUtil.scrollHorizontal(scomp, false); - break; - case SWT.PAGE_UP: - FormUtil.scrollPage(scomp, true); - break; - case SWT.PAGE_DOWN: - FormUtil.scrollPage(scomp, false); - break; - } - } - } - - public static boolean isWrapControl(Control c) { - if ((c.getStyle() & SWT.WRAP) != 0) - return true; - if (c instanceof Composite) { - return false; -// return ((Composite) c).getLayout() instanceof ILayoutExtension; - } - return false; - } - - public static int getWidthHint(int wHint, Control c) { - boolean wrap = isWrapControl(c); - return wrap ? wHint : SWT.DEFAULT; - } - - public static int getHeightHint(int hHint, Control c) { - if (c instanceof Composite) { - Layout layout = ((Composite) c).getLayout(); -// if (layout instanceof ColumnLayout) -// return hHint; - } - return SWT.DEFAULT; - } - - public static int computeMinimumWidth(Control c, boolean changed) { - if (c instanceof Composite) { - Layout layout = ((Composite) c).getLayout(); -// if (layout instanceof ILayoutExtension) -// return ((ILayoutExtension) layout).computeMinimumWidth( -// (Composite) c, changed); - } - return c.computeSize(FormUtil.getWidthHint(5, c), SWT.DEFAULT, changed).x; - } - - public static int computeMaximumWidth(Control c, boolean changed) { - if (c instanceof Composite) { - Layout layout = ((Composite) c).getLayout(); -// if (layout instanceof ILayoutExtension) -// return ((ILayoutExtension) layout).computeMaximumWidth( -// (Composite) c, changed); - } - return c.computeSize(SWT.DEFAULT, SWT.DEFAULT, changed).x; - } - -// public static Form getForm(Control c) { -// Composite parent = c.getParent(); -// while (parent != null) { -// if (parent instanceof Form) { -// return (Form) parent; -// } -// parent = parent.getParent(); -// } -// return null; -// } - -// RAP [rh] FormUtil#createAlphaMashImage unnecessary -// public static Image createAlphaMashImage(Device device, Image srcImage) { -// Rectangle bounds = srcImage.getBounds(); -// int alpha = 0; -// int calpha = 0; -// ImageData data = srcImage.getImageData(); -// // Create a new image with alpha values alternating -// // between fully transparent (0) and fully opaque (255). -// // This image will show the background through the -// // transparent pixels. -// for (int i = 0; i < bounds.height; i++) { -// // scan line -// alpha = calpha; -// for (int j = 0; j < bounds.width; j++) { -// // column -// data.setAlpha(j, i, alpha); -// alpha = alpha == 255 ? 0 : 255; -// } -// calpha = calpha == 255 ? 0 : 255; -// } -// return new Image(device, data); -// } - - public static boolean mnemonicMatch(String text, char key) { - char mnemonic = findMnemonic(text); - if (mnemonic == '\0') - return false; - return Character.toUpperCase(key) == Character.toUpperCase(mnemonic); - } - - private static char findMnemonic(String string) { - int index = 0; - int length = string.length(); - do { - while (index < length && string.charAt(index) != '&') - index++; - if (++index >= length) - return '\0'; - if (string.charAt(index) != '&') - return string.charAt(index); - index++; - } while (index < length); - return '\0'; - } - - public static void setFocusScrollingEnabled(Control c, boolean enabled) { - ScrolledComposite scomp = null; - - if (c instanceof ScrolledComposite) - scomp = (ScrolledComposite)c; - else - scomp = getScrolledComposite(c); - if (scomp!=null) - scomp.setData(FormUtil.FOCUS_SCROLLING, enabled?null:Boolean.FALSE); - } - - // RAP [rh] FormUtil#setAntialias unnecessary -// public static void setAntialias(GC gc, int style) { -// if (!gc.getAdvanced()) { -// gc.setAdvanced(true); -// if (!gc.getAdvanced()) -// return; -// } -// gc.setAntialias(style); -// } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IFormColors.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IFormColors.java deleted file mode 100644 index cf0e5d35e..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IFormColors.java +++ /dev/null @@ -1,102 +0,0 @@ -package org.argeo.cms.ui.eclipse.forms; - -/** - * A place to hold all the color constants used in the forms package. - * - * @since 1.0 - */ - -public interface IFormColors { - /** - * A prefix for all the keys. - */ - String PREFIX = "org.eclipse.ui.forms."; //$NON-NLS-1$ - /** - * Key for the form title foreground color. - */ - String TITLE = PREFIX + "TITLE"; //$NON-NLS-1$ - - /** - * A prefix for the header color constants. - */ - String H_PREFIX = PREFIX + "H_"; //$NON-NLS-1$ - /* - * A prefix for the section title bar color constants. - */ - String TB_PREFIX = PREFIX + "TB_"; //$NON-NLS-1$ - /** - * Key for the form header background gradient ending color. - */ - String H_GRADIENT_END = H_PREFIX + "GRADIENT_END"; //$NON-NLS-1$ - - /** - * Key for the form header background gradient starting color. - * - */ - String H_GRADIENT_START = H_PREFIX + "GRADIENT_START"; //$NON-NLS-1$ - /** - * Key for the form header bottom keyline 1 color. - * - */ - String H_BOTTOM_KEYLINE1 = H_PREFIX + "BOTTOM_KEYLINE1"; //$NON-NLS-1$ - /** - * Key for the form header bottom keyline 2 color. - * - */ - String H_BOTTOM_KEYLINE2 = H_PREFIX + "BOTTOM_KEYLINE2"; //$NON-NLS-1$ - /** - * Key for the form header light hover color. - * - */ - String H_HOVER_LIGHT = H_PREFIX + "H_HOVER_LIGHT"; //$NON-NLS-1$ - /** - * Key for the form header full hover color. - * - */ - String H_HOVER_FULL = H_PREFIX + "H_HOVER_FULL"; //$NON-NLS-1$ - - /** - * Key for the tree/table border color. - */ - String BORDER = PREFIX + "BORDER"; //$NON-NLS-1$ - - /** - * Key for the section separator color. - */ - String SEPARATOR = PREFIX + "SEPARATOR"; //$NON-NLS-1$ - - /** - * Key for the section title bar background. - */ - String TB_BG = TB_PREFIX + "BG"; //$NON-NLS-1$ - - /** - * Key for the section title bar foreground. - */ - String TB_FG = TB_PREFIX + "FG"; //$NON-NLS-1$ - - /** - * Key for the section title bar gradient. - * @deprecated Since 3.3, this color is not used any more. The - * tool bar gradient is created starting from {@link #TB_BG} to - * the section background color. - */ - String TB_GBG = TB_BG; - - /** - * Key for the section title bar border. - */ - String TB_BORDER = TB_PREFIX + "BORDER"; //$NON-NLS-1$ - - /** - * Key for the section toggle color. Since 3.1, this color is used for all - * section styles. - */ - String TB_TOGGLE = TB_PREFIX + "TOGGLE"; //$NON-NLS-1$ - - /** - * Key for the section toggle hover color. - * - */ - String TB_TOGGLE_HOVER = TB_PREFIX + "TOGGLE_HOVER"; //$NON-NLS-1$ -} \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IFormPart.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IFormPart.java deleted file mode 100644 index 954cc0372..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IFormPart.java +++ /dev/null @@ -1,108 +0,0 @@ -package org.argeo.cms.ui.eclipse.forms; - -/** - * Classes that implement this interface can be added to the managed form and - * take part in the form life cycle. The part is initialized with the form and - * will be asked to accept focus. The part can receive form input and can elect - * to do something according to it (for example, select an object that matches - * the input). - *

- * The form part has two 'out of sync' states in respect to the model(s) that - * feed the form: dirty and stale. When a part is dirty, it - * means that the user interacted with it and now its widgets contain state that - * is newer than the model. In order to sync up with the model, 'commit' needs - * to be called. In contrast, the model can change 'under' the form (as a result - * of some actions outside the form), resulting in data in the model being - * 'newer' than the content presented in the form. A 'stale' form part is - * brought in sync with the model by calling 'refresh'. The part is responsible - * for notifying the form when one of these states change in the part. The form - * reserves the right to handle this notification in the most appropriate way - * for the situation (for example, if the form is in a page of the multi-page - * editor, it may do nothing for stale parts if the page is currently not - * showing). - *

- * When the form is disposed, each registered part is disposed as well. Parts - * are responsible for releasing any system resources they created and for - * removing themselves as listeners from all event providers. - * - * @see IManagedForm - * @since 1.0 - * - */ -public interface IFormPart { - /** - * Initializes the part. - * - * @param form - * the managed form that manages the part - */ - void initialize(IManagedForm form); - - /** - * Disposes the part allowing it to release allocated resources. - */ - void dispose(); - - /** - * Returns true if the part has been modified with respect to the data - * loaded from the model. - * - * @return true if the part has been modified with respect to the data - * loaded from the model - */ - boolean isDirty(); - - /** - * If part is displaying information loaded from a model, this method - * instructs it to commit the new (modified) data back into the model. - * - * @param onSave - * indicates if commit is called during 'save' operation or for - * some other reason (for example, if form is contained in a - * wizard or a multi-page editor and the user is about to leave - * the page). - */ - void commit(boolean onSave); - - /** - * Notifies the part that an object has been set as overall form's input. - * The part can elect to react by revealing or selecting the object, or do - * nothing if not applicable. - * - * @return true if the part has selected and revealed the - * input object, false otherwise. - */ - boolean setFormInput(Object input); - - /** - * Instructs form part to transfer focus to the widget that should has focus - * in that part. The method can do nothing (if it has no widgets capable of - * accepting focus). - */ - void setFocus(); - - /** - * Tests whether the form part is stale and needs refreshing. Parts can - * receive notification from models that will make their content stale, but - * may need to delay refreshing to improve performance (for example, there - * is no need to immediately refresh a part on a form that is current on a - * hidden page). - *

- * It is important to differentiate 'stale' and 'dirty' states. Part is - * 'dirty' if user interacted with its editable widgets and changed the - * values. In contrast, part is 'stale' when the data it presents in the - * widgets has been changed in the model without direct user interaction. - * - * @return true if the part needs refreshing, - * false otherwise. - */ - boolean isStale(); - - /** - * Refreshes the part completely from the information freshly obtained from - * the model. The method will not be called if the part is not stale. - * Otherwise, the part is responsible for clearing the 'stale' flag after - * refreshing itself. - */ - void refresh(); -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IManagedForm.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IManagedForm.java deleted file mode 100644 index 490d3a303..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IManagedForm.java +++ /dev/null @@ -1,175 +0,0 @@ -package org.argeo.cms.ui.eclipse.forms; - -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.swt.custom.ScrolledComposite; -//import org.eclipse.ui.forms.widgets.FormToolkit; -//import org.eclipse.ui.forms.widgets.ScrolledForm; - -/** - * Managed form wraps a form widget and adds life cycle methods for form parts. - * A form part is a portion of the form that participates in form life cycle - * events. - *

- * There is no 1/1 mapping between widgets and form parts. A widget like Section - * can be a part by itself, but a number of widgets can gather around one form - * part. - *

- * This interface should not be extended or implemented. New form instances - * should be created using ManagedForm. - * - * @see ManagedForm - * @since 1.0 - * @noimplement This interface is not intended to be implemented by clients. - * @noextend This interface is not intended to be extended by clients. - */ -public interface IManagedForm { - /** - * Initializes the form by looping through the managed parts and - * initializing them. Has no effect if already called once. - */ - public void initialize(); - - /** - * Returns the toolkit used by this form. - * - * @return the toolkit - */ - public FormToolkit getToolkit(); - - /** - * Returns the form widget managed by this form. - * - * @return the form widget - */ - public ScrolledComposite getForm(); - - /** - * Reflows the form as a result of the layout change. - * - * @param changed - * if true, discard cached layout information - */ - public void reflow(boolean changed); - - /** - * A part can use this method to notify other parts that implement - * IPartSelectionListener about selection changes. - * - * @param part - * the part that broadcasts the selection - * @param selection - * the selection in the part - */ - public void fireSelectionChanged(IFormPart part, ISelection selection); - - /** - * Returns all the parts currently managed by this form. - * - * @return the managed parts - */ - IFormPart[] getParts(); - - /** - * Adds the new part to the form. - * - * @param part - * the part to add - */ - void addPart(IFormPart part); - - /** - * Removes the part from the form. - * - * @param part - * the part to remove - */ - void removePart(IFormPart part); - - /** - * Sets the input of this page to the provided object. - * - * @param input - * the new page input - * @return true if the form contains this object, - * false otherwise. - */ - boolean setInput(Object input); - - /** - * Returns the current page input. - * - * @return page input object or null if not applicable. - */ - Object getInput(); - - /** - * Tests if form is dirty. A managed form is dirty if at least one managed - * part is dirty. - * - * @return true if at least one managed part is dirty, - * false otherwise. - */ - boolean isDirty(); - - /** - * Notifies the form that the dirty state of one of its parts has changed. - * The global dirty state of the form can be obtained by calling 'isDirty'. - * - * @see #isDirty - */ - void dirtyStateChanged(); - - /** - * Commits the dirty form. All pending changes in the widgets are flushed - * into the model. - * - * @param onSave - */ - void commit(boolean onSave); - - /** - * Tests if form is stale. A managed form is stale if at least one managed - * part is stale. This can happen when the underlying model changes, - * resulting in the presentation of the part being out of sync with the - * model and needing refreshing. - * - * @return true if the form is stale, false - * otherwise. - */ - boolean isStale(); - - /** - * Notifies the form that the stale state of one of its parts has changed. - * The global stale state of the form can be obtained by calling 'isStale'. - */ - void staleStateChanged(); - - /** - * Refreshes the form by refreshing every part that is stale. - */ - void refresh(); - - /** - * Sets the container that owns this form. Depending on the context, the - * container may be wizard, editor page, editor etc. - * - * @param container - * the container of this form - */ - void setContainer(Object container); - - /** - * Returns the container of this form. - * - * @return the form container - */ - Object getContainer(); - - /** - * Returns the message manager that will keep track of messages in this - * form. - * - * @return the message manager instance - */ -// IMessageManager getMessageManager(); -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IPartSelectionListener.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IPartSelectionListener.java deleted file mode 100644 index 0f557d41f..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/IPartSelectionListener.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.argeo.cms.ui.eclipse.forms; - -import org.eclipse.jface.viewers.ISelection; - -/** - * Form parts can implement this interface if they want to be - * notified when another part on the same form changes selection - * state. - * - * @see IFormPart - * @since 1.0 - */ -public interface IPartSelectionListener { - /** - * Called when the provided part has changed selection state. - * - * @param part - * the selection source - * @param selection - * the new selection - */ - public void selectionChanged(IFormPart part, ISelection selection); -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/ManagedForm.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/ManagedForm.java deleted file mode 100644 index 4140465a1..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/ManagedForm.java +++ /dev/null @@ -1,323 +0,0 @@ -package org.argeo.cms.ui.eclipse.forms; - -import java.util.Vector; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.swt.custom.ScrolledComposite; -import org.eclipse.swt.widgets.Composite; -//import org.eclipse.ui.forms.widgets.FormToolkit; -//import org.eclipse.ui.forms.widgets.ScrolledForm; - -/** - * Managed form wraps a form widget and adds life cycle methods for form parts. - * A form part is a portion of the form that participates in form life cycle - * events. - *

- * There is requirement for 1/1 mapping between widgets and form parts. A widget - * like Section can be a part by itself, but a number of widgets can join around - * one form part. - *

- * Note to developers: this class is left public to allow its use beyond the - * original intention (inside a multi-page editor's page). You should limit the - * use of this class to make new instances inside a form container (wizard page, - * dialog etc.). Clients that need access to the class should not do it - * directly. Instead, they should do it through IManagedForm interface as much - * as possible. - * - * @since 1.0 - */ -public class ManagedForm implements IManagedForm { - private Object input; - - private ScrolledComposite form; - - private FormToolkit toolkit; - - private Object container; - - private boolean ownsToolkit; - - private boolean initialized; - - private Vector parts = new Vector(); - - /** - * Creates a managed form in the provided parent. Form toolkit and widget - * will be created and owned by this object. - * - * @param parent - * the parent widget - */ - public ManagedForm(Composite parent) { - toolkit = new FormToolkit(parent.getDisplay()); - ownsToolkit = true; - form = toolkit.createScrolledForm(parent); - - } - - /** - * Creates a managed form that will use the provided toolkit and - * - * @param toolkit - * @param form - */ - public ManagedForm(FormToolkit toolkit, ScrolledComposite form) { - this.form = form; - this.toolkit = toolkit; - } - - /* - * (non-Javadoc) - * - * @see org.eclipse.ui.forms.IManagedForm#addPart(org.eclipse.ui.forms.IFormPart) - */ - public void addPart(IFormPart part) { - parts.add(part); - part.initialize(this); - } - - /* - * (non-Javadoc) - * - * @see org.eclipse.ui.forms.IManagedForm#removePart(org.eclipse.ui.forms.IFormPart) - */ - public void removePart(IFormPart part) { - parts.remove(part); - } - - /* - * (non-Javadoc) - * - * @see org.eclipse.ui.forms.IManagedForm#getParts() - */ - public IFormPart[] getParts() { - return (IFormPart[]) parts.toArray(new IFormPart[parts.size()]); - } - - /* - * (non-Javadoc) - * - * @see org.eclipse.ui.forms.IManagedForm#getToolkit() - */ - public FormToolkit getToolkit() { - return toolkit; - } - - /* - * (non-Javadoc) - * - * @see org.eclipse.ui.forms.IManagedForm#getForm() - */ - public ScrolledComposite getForm() { - return form; - } - - /* - * (non-Javadoc) - * - * @see org.eclipse.ui.forms.IManagedForm#reflow(boolean) - */ - public void reflow(boolean changed) { -// form.reflow(changed); - } - - /** - * A part can use this method to notify other parts that implement - * IPartSelectionListener about selection changes. - * - * @param part - * the part that broadcasts the selection - * @param selection - * the selection in the part - * @see IPartSelectionListener - */ - public void fireSelectionChanged(IFormPart part, ISelection selection) { - for (int i = 0; i < parts.size(); i++) { - IFormPart cpart = (IFormPart) parts.get(i); - if (part.equals(cpart)) - continue; -// if (cpart instanceof IPartSelectionListener) { -// ((IPartSelectionListener) cpart).selectionChanged(part, -// selection); -// } - } - } - - /** - * Initializes the form by looping through the managed parts and - * initializing them. Has no effect if already called once. - */ - public void initialize() { - if (initialized) - return; - for (int i = 0; i < parts.size(); i++) { - IFormPart part = (IFormPart) parts.get(i); - part.initialize(this); - } - initialized = true; - } - - /** - * Disposes all the parts in this form. - */ - public void dispose() { - for (int i = 0; i < parts.size(); i++) { - IFormPart part = (IFormPart) parts.get(i); - part.dispose(); - } - if (ownsToolkit) { - toolkit.dispose(); - } - } - - /** - * Refreshes the form by refreshes all the stale parts. Since 3.1, this - * method is performed on a UI thread when called from another thread so it - * is not needed to wrap the call in Display.syncExec or - * asyncExec. - */ - public void refresh() { - Thread t = Thread.currentThread(); - Thread dt = toolkit.getColors().getDisplay().getThread(); - if (t.equals(dt)) - doRefresh(); - else { - toolkit.getColors().getDisplay().asyncExec(new Runnable() { - public void run() { - doRefresh(); - } - }); - } - } - - private void doRefresh() { - int nrefreshed = 0; - for (int i = 0; i < parts.size(); i++) { - IFormPart part = (IFormPart) parts.get(i); - if (part.isStale()) { - part.refresh(); - nrefreshed++; - } - } -// if (nrefreshed > 0) -// form.reflow(true); - } - - /* - * (non-Javadoc) - * - * @see org.eclipse.ui.forms.IManagedForm#commit(boolean) - */ - public void commit(boolean onSave) { - for (int i = 0; i < parts.size(); i++) { - IFormPart part = (IFormPart) parts.get(i); - if (part.isDirty()) - part.commit(onSave); - } - } - - /* - * (non-Javadoc) - * - * @see org.eclipse.ui.forms.IManagedForm#setInput(java.lang.Object) - */ - public boolean setInput(Object input) { - boolean pageResult = false; - - this.input = input; - for (int i = 0; i < parts.size(); i++) { - IFormPart part = (IFormPart) parts.get(i); - boolean result = part.setFormInput(input); - if (result) - pageResult = true; - } - return pageResult; - } - - /* - * (non-Javadoc) - * - * @see org.eclipse.ui.forms.IManagedForm#getInput() - */ - public Object getInput() { - return input; - } - - /** - * Transfers the focus to the first form part. - */ - public void setFocus() { - if (parts.size() > 0) { - IFormPart part = (IFormPart) parts.get(0); - part.setFocus(); - } - } - - /* - * (non-Javadoc) - * - * @see org.eclipse.ui.forms.IManagedForm#isDirty() - */ - public boolean isDirty() { - for (int i = 0; i < parts.size(); i++) { - IFormPart part = (IFormPart) parts.get(i); - if (part.isDirty()) - return true; - } - return false; - } - - /* - * (non-Javadoc) - * - * @see org.eclipse.ui.forms.IManagedForm#isStale() - */ - public boolean isStale() { - for (int i = 0; i < parts.size(); i++) { - IFormPart part = (IFormPart) parts.get(i); - if (part.isStale()) - return true; - } - return false; - } - - /* - * (non-Javadoc) - * - * @see org.eclipse.ui.forms.IManagedForm#dirtyStateChanged() - */ - public void dirtyStateChanged() { - } - - /* - * (non-Javadoc) - * - * @see org.eclipse.ui.forms.IManagedForm#staleStateChanged() - */ - public void staleStateChanged() { - } - - /* - * (non-Javadoc) - * - * @see org.eclipse.ui.forms.IManagedForm#getContainer() - */ - public Object getContainer() { - return container; - } - - /* - * (non-Javadoc) - * - * @see org.eclipse.ui.forms.IManagedForm#setContainer(java.lang.Object) - */ - public void setContainer(Object container) { - this.container = container; - } - - /* (non-Javadoc) - * @see org.eclipse.ui.forms.IManagedForm#getMessageManager() - */ -// public IMessageManager getMessageManager() { -// return form.getMessageManager(); -// } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/FormEditor.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/FormEditor.java deleted file mode 100644 index 7fa00d9c2..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/FormEditor.java +++ /dev/null @@ -1,89 +0,0 @@ -package org.argeo.cms.ui.eclipse.forms.editor; - -import org.argeo.cms.ui.eclipse.forms.FormToolkit; -import org.eclipse.core.runtime.ListenerList; -import org.eclipse.jface.dialogs.IPageChangeProvider; -import org.eclipse.jface.dialogs.IPageChangedListener; -import org.eclipse.jface.dialogs.PageChangedEvent; -import org.eclipse.jface.util.SafeRunnable; - -/** - * This class forms a base of multi-page form editors that typically use one or - * more pages with forms and one page for raw source of the editor input. - *

- * Pages are added 'lazily' i.e. adding a page reserves a tab for it but does - * not cause the page control to be created. Page control is created when an - * attempt is made to select the page in question. This allows editors with - * several tabs and complex pages to open quickly. - *

- * Subclasses should extend this class and implement addPages - * method. One of the two addPage methods should be called to - * contribute pages to the editor. One adds complete (standalone) editors as - * nested tabs. These editors will be created right away and will be hooked so - * that key bindings, selection service etc. is compatible with the one for the - * standalone case. The other method adds classes that implement - * IFormPage interface. These pages will be created lazily and - * they will share the common key binding and selection service. Since 3.1, - * FormEditor is a page change provider. It allows listeners to attach to it and - * get notified when pages are changed. This new API in JFace allows dynamic - * help to update on page changes. - * - * @since 1.0 - */ -// RAP [if] As RAP is still using workbench 3.4, the implementation of -// IPageChangeProvider is missing from MultiPageEditorPart. Remove this code -// with the adoption of workbench > 3.5 -//public abstract class FormEditor extends MultiPageEditorPart { -public abstract class FormEditor implements - IPageChangeProvider { - private FormToolkit formToolkit; - - -public FormToolkit getToolkit() { - return formToolkit; - } - -public void editorDirtyStateChanged() { - -} - -public FormPage getActivePageInstance() { - return null; -} - - // RAP [if] As RAP is still using workbench 3.4, the implementation of -// IPageChangeProvider is missing from MultiPageEditorPart. Remove this code -// with the adoption of workbench > 3.5 - private ListenerList pageListeners = new ListenerList(); - - /* - * (non-Javadoc) - * - * @see org.eclipse.jface.dialogs.IPageChangeProvider#addPageChangedListener(org.eclipse.jface.dialogs.IPageChangedListener) - */ - public void addPageChangedListener(IPageChangedListener listener) { - pageListeners.add(listener); - } - - /* - * (non-Javadoc) - * - * @see org.eclipse.jface.dialogs.IPageChangeProvider#removePageChangedListener(org.eclipse.jface.dialogs.IPageChangedListener) - */ - public void removePageChangedListener(IPageChangedListener listener) { - pageListeners.remove(listener); - } - - private void firePageChanged(final PageChangedEvent event) { - Object[] listeners = pageListeners.getListeners(); - for (int i = 0; i < listeners.length; ++i) { - final IPageChangedListener l = (IPageChangedListener) listeners[i]; - SafeRunnable.run(new SafeRunnable() { - public void run() { - l.pageChanged(event); - } - }); - } - } -// RAPEND [if] -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/FormPage.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/FormPage.java deleted file mode 100644 index 1511cf38c..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/FormPage.java +++ /dev/null @@ -1,277 +0,0 @@ -package org.argeo.cms.ui.eclipse.forms.editor; -import org.argeo.cms.ui.eclipse.forms.IManagedForm; -import org.argeo.cms.ui.eclipse.forms.ManagedForm; -import org.eclipse.core.runtime.IProgressMonitor; -import org.eclipse.swt.custom.BusyIndicator; -import org.eclipse.swt.custom.ScrolledComposite; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -/** - * A base class that all pages that should be added to FormEditor must subclass. - * Form page has an instance of PageForm that extends managed form. Subclasses - * should override method 'createFormContent(ManagedForm)' to fill the form with - * content. Note that page itself can be loaded lazily (on first open). - * Consequently, the call to create the form content can come after the editor - * has been opened for a while (in fact, it is possible to open and close the - * editor and never create the form because no attempt has been made to show the - * page). - * - * @since 1.0 - */ -public class FormPage implements IFormPage { - private FormEditor editor; - private PageForm mform; - private int index; - private String id; - - private String partName; - - - - public void setPartName(String partName) { - this.partName = partName; - } - private static class PageForm extends ManagedForm { - public PageForm(FormPage page, ScrolledComposite form) { - super(page.getEditor().getToolkit(), form); - setContainer(page); - } - - public FormPage getPage() { - return (FormPage)getContainer(); - } - public void dirtyStateChanged() { - getPage().getEditor().editorDirtyStateChanged(); - } - public void staleStateChanged() { - if (getPage().isActive()) - refresh(); - } - } - /** - * A constructor that creates the page and initializes it with the editor. - * - * @param editor - * the parent editor - * @param id - * the unique identifier - * @param title - * the page title - */ - public FormPage(FormEditor editor, String id, String title) { - this(id, title); - initialize(editor); - } - /** - * The constructor. The parent editor need to be passed in the - * initialize method if this constructor is used. - * - * @param id - * a unique page identifier - * @param title - * a user-friendly page title - */ - public FormPage(String id, String title) { - this.id = id; - setPartName(title); - } - /** - * Initializes the form page. - * - * @see IEditorPart#init - */ -// public void init(IEditorSite site, IEditorInput input) { -// setSite(site); -// setInput(input); -// } - /** - * Primes the form page with the parent editor instance. - * - * @param editor - * the parent editor - */ - public void initialize(FormEditor editor) { - this.editor = editor; - } - /** - * Returns the parent editor. - * - * @return parent editor instance - */ - public FormEditor getEditor() { - return editor; - } - /** - * Returns the managed form owned by this page. - * - * @return the managed form - */ - public IManagedForm getManagedForm() { - return mform; - } - /** - * Implements the required method by refreshing the form when set active. - * Subclasses must call super when overriding this method. - */ - public void setActive(boolean active) { - if (active) { - // We are switching to this page - refresh it - // if needed. - if (mform != null) - mform.refresh(); - } - } - /** - * Tests if the page is active by asking the parent editor if this page is - * the currently active page. - * - * @return true if the page is currently active, - * false otherwise. - */ - public boolean isActive() { - return this.equals(editor.getActivePageInstance()); - } - /** - * Creates the part control by creating the managed form using the parent - * editor's toolkit. Subclasses should override - * createFormContent(IManagedForm) to populate the form with - * content. - * - * @param parent - * the page parent composite - */ - public void createPartControl(Composite parent) { - ScrolledComposite form = editor.getToolkit().createScrolledForm(parent); - mform = new PageForm(this, form); - BusyIndicator.showWhile(parent.getDisplay(), new Runnable() { - public void run() { - createFormContent(mform); - } - }); - } - /** - * Subclasses should override this method to create content in the form - * hosted in this page. - * - * @param managedForm - * the form hosted in this page. - */ - protected void createFormContent(IManagedForm managedForm) { - } - /** - * Returns the form page control. - * - * @return managed form's control - */ - public Control getPartControl() { - return mform != null ? mform.getForm() : null; - } - /** - * Disposes the managed form. - */ - public void dispose() { - if (mform != null) - mform.dispose(); - } - /** - * Returns the unique identifier that can be used to reference this page. - * - * @return the unique page identifier - */ - public String getId() { - return id; - } - /** - * Returns null- form page has no title image. Subclasses - * may override. - * - * @return null - */ - public Image getTitleImage() { - return null; - } - /** - * Sets the focus by delegating to the managed form. - */ - public void setFocus() { - if (mform != null) - mform.setFocus(); - } - /** - * @see org.eclipse.ui.ISaveablePart#doSave(org.eclipse.core.runtime.IProgressMonitor) - */ - public void doSave(IProgressMonitor monitor) { - if (mform != null) - mform.commit(true); - } - /** - * @see org.eclipse.ui.ISaveablePart#doSaveAs() - */ - public void doSaveAs() { - } - /** - * @see org.eclipse.ui.ISaveablePart#isSaveAsAllowed() - */ - public boolean isSaveAsAllowed() { - return false; - } - /** - * Implemented by testing if the managed form is dirty. - * - * @return true if the managed form is dirty, - * false otherwise. - * - * @see org.eclipse.ui.ISaveablePart#isDirty() - */ - public boolean isDirty() { - return mform != null ? mform.isDirty() : false; - } - /** - * Preserves the page index. - * - * @param index - * the assigned page index - */ - public void setIndex(int index) { - this.index = index; - } - /** - * Returns the saved page index. - * - * @return the page index - */ - public int getIndex() { - return index; - } - /** - * Form pages are not editors. - * - * @return false - */ - public boolean isEditor() { - return false; - } - /** - * Attempts to select and reveal the given object by passing the request to - * the managed form. - * - * @param object - * the object to select and reveal in the page if possible. - * @return true if the page has been successfully selected - * and revealed by one of the managed form parts, false - * otherwise. - */ - public boolean selectReveal(Object object) { - if (mform != null) - return mform.setInput(object); - return false; - } - /** - * By default, editor will be allowed to flip the page. - * @return true - */ - public boolean canLeaveThePage() { - return true; - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/IFormPage.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/IFormPage.java deleted file mode 100644 index eb08cb59d..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/eclipse/forms/editor/IFormPage.java +++ /dev/null @@ -1,119 +0,0 @@ -package org.argeo.cms.ui.eclipse.forms.editor; -import org.argeo.cms.ui.eclipse.forms.IManagedForm; -import org.eclipse.swt.widgets.Control; -/** - * Interface that all GUI pages need to implement in order - * to be added to FormEditor part. The interface makes - * several assumptions: - *

    - *
  • The form page has a managed form
  • - *
  • The form page has a unique id
  • - *
  • The form page can be GUI but can also wrap a complete - * editor class (in that case, it should return true - * from isEditor() method).
  • - *
  • The form page is lazy i.e. understands that - * its part control will be created at the last possible - * moment.
  • . - *
- *

Existing editors can be wrapped by implementing - * this interface. In this case, 'isEditor' should return true. - * A common editor to wrap in TextEditor that is - * often added to show the raw source code of the file open into - * the multi-page editor. - * - * @since 1.0 - */ -public interface IFormPage { - /** - * @param editor - * the form editor that this page belongs to - */ - void initialize(FormEditor editor); - /** - * Returns the editor this page belongs to. - * - * @return the form editor - */ - FormEditor getEditor(); - /** - * Returns the managed form of this page, unless this is a source page. - * - * @return the managed form or null if this is a source page. - */ - IManagedForm getManagedForm(); - /** - * Indicates whether the page has become the active in the editor. Classes - * that implement this interface may use this method to commit the page (on - * false) or lazily create and/or populate the content on - * true. - * - * @param active - * true if page should be visible, false - * otherwise. - */ - void setActive(boolean active); - /** - * Returns true if page is currently active, false if not. - * - * @return true for active page. - */ - boolean isActive(); - /** - * Tests if the content of the page is in a state that allows the - * editor to flip to another page. Typically, pages that contain - * raw source with syntax errors should not allow editors to - * leave them until errors are corrected. - * @return true if the editor can flip to another page, - * false otherwise. - */ - boolean canLeaveThePage(); - /** - * Returns the control associated with this page. - * - * @return the control of this page if created or null if the - * page has not been shown yet. - */ - Control getPartControl(); - /** - * Page must have a unique id that can be used to show it without knowing - * its relative position in the editor. - * - * @return the unique page identifier - */ - String getId(); - /** - * Returns the position of the page in the editor. - * - * @return the zero-based index of the page in the editor. - */ - int getIndex(); - /** - * Sets the position of the page in the editor. - * - * @param index - * the zero-based index of the page in the editor. - */ - void setIndex(int index); - /** - * Tests whether this page wraps a complete editor that - * can be registered on its own, or represents a page - * that cannot exist outside the multi-page editor context. - * - * @return true if the page wraps an editor, - * false if this is a form page. - */ - boolean isEditor(); - /** - * A hint to bring the provided object into focus. If the object is in a - * tree or table control, select it. If it is shown on a scrollable page, - * ensure that it is visible. If the object is not presented in - * the page, false should be returned to allow another - * page to try. - * - * @param object - * object to select and reveal - * @return true if the request was successful, false - * otherwise. - */ - boolean selectReveal(Object object); -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditableLink.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditableLink.java deleted file mode 100644 index e74de5ee8..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditableLink.java +++ /dev/null @@ -1,75 +0,0 @@ -package org.argeo.cms.ui.forms; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.cms.ui.viewers.EditablePart; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; - -/** Editable String that displays a browsable link when read-only */ -public class EditableLink extends EditablePropertyString implements - EditablePart { - private static final long serialVersionUID = 5055000749992803591L; - - private String type; - private String message; - private boolean readOnly; - - public EditableLink(Composite parent, int style, Node node, - String propertyName, String type, String message) - throws RepositoryException { - super(parent, style, node, propertyName, message); - this.message = message; - this.type = type; - - readOnly = SWT.READ_ONLY == (style & SWT.READ_ONLY); - if (node.hasProperty(propertyName)) { - this.setStyle(FormStyle.propertyText.style()); - this.setText(node.getProperty(propertyName).getString()); - } else { - this.setStyle(FormStyle.propertyMessage.style()); - this.setText(""); - } - } - - public void setText(String text) { - Control child = getControl(); - if (child instanceof Label) { - Label lbl = (Label) child; - if (EclipseUiUtils.isEmpty(text)) - lbl.setText(message); - else if (readOnly) - setLinkValue(lbl, text); - else - // if canEdit() we put only the value with no link - // to avoid glitches of the edition life cycle - lbl.setText(text); - } else if (child instanceof Text) { - Text txt = (Text) child; - if (EclipseUiUtils.isEmpty(text)) { - txt.setText(""); - txt.setMessage(message); - } else - txt.setText(text); - } - } - - private void setLinkValue(Label lbl, String text) { - if (FormStyle.email.style().equals(type)) - lbl.setText(FormUtils.getMailLink(text)); - else if (FormStyle.phone.style().equals(type)) - lbl.setText(FormUtils.getPhoneLink(text)); - else if (FormStyle.website.style().equals(type)) - lbl.setText(FormUtils.getUrlLink(text)); - else if (FormStyle.facebook.style().equals(type) - || FormStyle.instagram.style().equals(type) - || FormStyle.linkedIn.style().equals(type) - || FormStyle.twitter.style().equals(type)) - lbl.setText(FormUtils.getUrlLink(text)); - } -} \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditableMultiStringProperty.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditableMultiStringProperty.java deleted file mode 100644 index fd3f48e3a..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditableMultiStringProperty.java +++ /dev/null @@ -1,261 +0,0 @@ -package org.argeo.cms.ui.forms; - -import java.util.List; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.viewers.EditablePart; -import org.argeo.cms.ui.widgets.StyledControl; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.events.TraverseEvent; -import org.eclipse.swt.events.TraverseListener; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.layout.RowLayout; -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.eclipse.swt.widgets.Text; - -/** Display, add or remove values from a list in a CMS context */ -public class EditableMultiStringProperty extends StyledControl implements EditablePart { - private static final long serialVersionUID = -7044614381252178595L; - - private String propertyName; - private String message; - // TODO implement the ability to provide a list of possible values -// private String[] possibleValues; - private boolean canEdit; - private SelectionListener removeValueSL; - private List values; - - // TODO manage within the CSS - private int rowSpacing = 5; - private int rowMarging = 0; - private int oneValueMargingRight = 5; - private int btnWidth = 16; - private int btnHeight = 16; - private int btnHorizontalIndent = 3; - - public EditableMultiStringProperty(Composite parent, int style, Node node, String propertyName, List values, - String[] possibleValues, String addValueMsg, SelectionListener removeValueSelectionListener) - throws RepositoryException { - super(parent, style, node, true); - - this.propertyName = propertyName; - this.values = values; -// this.possibleValues = new String[] { "Un", "Deux", "Trois" }; - this.message = addValueMsg; - this.canEdit = removeValueSelectionListener != null; - this.removeValueSL = removeValueSelectionListener; - } - - public List getValues() { - return values; - } - - public void setValues(List values) { - this.values = values; - } - - // Row layout items do not need explicit layout data - protected void setControlLayoutData(Control control) { - } - - /** To be overridden */ - protected void setContainerLayoutData(Composite composite) { - composite.setLayoutData(CmsSwtUtils.fillWidth()); - } - - @Override - public Control getControl() { - return super.getControl(); - } - - @Override - protected Control createControl(Composite box, String style) { - Composite row = new Composite(box, SWT.NO_FOCUS); - row.setLayoutData(EclipseUiUtils.fillAll()); - - RowLayout rl = new RowLayout(SWT.HORIZONTAL); - rl.wrap = true; - rl.spacing = rowSpacing; - rl.marginRight = rl.marginLeft = rl.marginBottom = rl.marginTop = rowMarging; - row.setLayout(rl); - - if (values != null) { - for (final String value : values) { - if (canEdit) - createRemovableValue(row, SWT.SINGLE, value); - else - createValueLabel(row, SWT.SINGLE, value); - } - } - - if (!canEdit) - return row; - else if (isEditing()) - return createText(row, style); - else - return createLabel(row, style); - } - - /** - * Override to provide specific layout for the existing values, typically adding - * a pound (#) char for tags or anchor info for browsable links. We assume the - * parent composite already has a layout and it is the caller responsibility to - * apply corresponding layout data - */ - protected Label createValueLabel(Composite parent, int style, String value) { - Label label = new Label(parent, style); - label.setText("#" + value); - CmsSwtUtils.markup(label); - CmsSwtUtils.style(label, FormStyle.propertyText.style()); - return label; - } - - private Composite createRemovableValue(Composite parent, int style, String value) { - Composite valCmp = new Composite(parent, SWT.NO_FOCUS); - GridLayout gl = EclipseUiUtils.noSpaceGridLayout(new GridLayout(2, false)); - gl.marginRight = oneValueMargingRight; - valCmp.setLayout(gl); - - createValueLabel(valCmp, SWT.WRAP, value); - - Button deleteBtn = new Button(valCmp, SWT.FLAT); - deleteBtn.setData(FormConstants.LINKED_VALUE, value); - deleteBtn.addSelectionListener(removeValueSL); - CmsSwtUtils.style(deleteBtn, FormStyle.delete.style() + FormStyle.BUTTON_SUFFIX); - GridData gd = new GridData(); - gd.heightHint = btnHeight; - gd.widthHint = btnWidth; - gd.horizontalIndent = btnHorizontalIndent; - deleteBtn.setLayoutData(gd); - - return valCmp; - } - - protected Text createText(Composite box, String style) { - final Text text = new Text(box, getStyle()); - // The "add new value" text is not meant to change, so we can set it on - // creation - text.setMessage(message); - CmsSwtUtils.style(text, style); - text.setFocus(); - - text.addTraverseListener(new TraverseListener() { - private static final long serialVersionUID = 1L; - - public void keyTraversed(TraverseEvent e) { - if (e.keyCode == SWT.CR) { - addValue(text); - e.doit = false; - } - } - }); - - // The OK button does not work with the focusOut listener - // because focus out is called before the OK button is pressed - - // // we must call layout() now so that the row data can compute the - // height - // // of the other controls. - // text.getParent().layout(); - // int height = text.getSize().y; - // - // Button okBtn = new Button(box, SWT.BORDER | SWT.PUSH | SWT.BOTTOM); - // okBtn.setText("OK"); - // RowData rd = new RowData(SWT.DEFAULT, height - 2); - // okBtn.setLayoutData(rd); - // - // okBtn.addSelectionListener(new SelectionAdapter() { - // private static final long serialVersionUID = 2780819012423622369L; - // - // @Override - // public void widgetSelected(SelectionEvent e) { - // addValue(text); - // } - // }); - - return text; - } - - /** Performs the real addition, overwrite to make further sanity checks */ - protected void addValue(Text text) { - String value = text.getText(); - String errMsg = null; - - if (EclipseUiUtils.isEmpty(value)) - return; - - if (values.contains(value)) - errMsg = "Dupplicated value: " + value + ", please correct and try again"; - if (errMsg != null) - MessageDialog.openError(this.getShell(), "Addition not allowed", errMsg); - else { - values.add(value); - Composite newCmp = createRemovableValue(text.getParent(), SWT.SINGLE, value); - newCmp.moveAbove(text); - text.setText(""); - newCmp.getParent().layout(); - } - } - - protected Label createLabel(Composite box, String style) { - if (canEdit) { - Label lbl = new Label(box, getStyle()); - lbl.setText(message); - CmsSwtUtils.style(lbl, style); - CmsSwtUtils.markup(lbl); - if (mouseListener != null) - lbl.addMouseListener(mouseListener); - return lbl; - } - return null; - } - - protected void clear(boolean deep) { - Control child = getControl(); - if (deep) - super.clear(deep); - else { - child.getParent().dispose(); - } - } - - public void setText(String text) { - Control child = getControl(); - if (child instanceof Label) { - Label lbl = (Label) child; - if (canEdit) - lbl.setText(text); - else - lbl.setText(""); - } else if (child instanceof Text) { - Text txt = (Text) child; - txt.setText(text); - } - } - - public synchronized void startEditing() { - CmsSwtUtils.style(getControl(), FormStyle.propertyText.style()); -// getControl().setData(STYLE, FormStyle.propertyText.style()); - super.startEditing(); - } - - public synchronized void stopEditing() { - CmsSwtUtils.style(getControl(), FormStyle.propertyMessage.style()); -// getControl().setData(STYLE, FormStyle.propertyMessage.style()); - super.stopEditing(); - } - - public String getPropertyName() { - return propertyName; - } -} \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditablePropertyDate.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditablePropertyDate.java deleted file mode 100644 index 8591a925f..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditablePropertyDate.java +++ /dev/null @@ -1,298 +0,0 @@ -package org.argeo.cms.ui.forms; - -import java.text.DateFormat; -import java.util.Calendar; -import java.util.GregorianCalendar; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.viewers.EditablePart; -import org.argeo.cms.ui.widgets.StyledControl; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.events.MouseListener; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.ShellAdapter; -import org.eclipse.swt.events.ShellEvent; -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.DateTime; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Shell; -import org.eclipse.swt.widgets.Text; - -/** CMS form part to display and edit a date */ -public class EditablePropertyDate extends StyledControl implements EditablePart { - private static final long serialVersionUID = 2500215515778162468L; - - // Context - private String propertyName; - private String message; - private DateFormat dateFormat; - - // UI Objects - private Text dateTxt; - private Button openCalBtn; - - // TODO manage within the CSS - private int fieldBtnSpacing = 5; - - /** - * - * @param parent - * @param style - * @param node - * @param propertyName - * @param message - * @param dateFormat provide a {@link DateFormat} as contract to be able to - * read/write dates as strings - * @throws RepositoryException - */ - public EditablePropertyDate(Composite parent, int style, Node node, String propertyName, String message, - DateFormat dateFormat) throws RepositoryException { - super(parent, style, node, false); - - this.propertyName = propertyName; - this.message = message; - this.dateFormat = dateFormat; - - if (node.hasProperty(propertyName)) { - this.setStyle(FormStyle.propertyText.style()); - this.setText(dateFormat.format(node.getProperty(propertyName).getDate().getTime())); - } else { - this.setStyle(FormStyle.propertyMessage.style()); - this.setText(message); - } - } - - public void setText(String text) { - Control child = getControl(); - if (child instanceof Label) { - Label lbl = (Label) child; - if (EclipseUiUtils.isEmpty(text)) - lbl.setText(message); - else - lbl.setText(text); - } else if (child instanceof Text) { - Text txt = (Text) child; - if (EclipseUiUtils.isEmpty(text)) { - txt.setText(""); - } else - txt.setText(text); - } - } - - public synchronized void startEditing() { - // if (dateTxt != null && !dateTxt.isDisposed()) - CmsSwtUtils.style(getControl(), FormStyle.propertyText); -// getControl().setData(STYLE, FormStyle.propertyText.style()); - super.startEditing(); - } - - public synchronized void stopEditing() { - if (EclipseUiUtils.isEmpty(dateTxt.getText())) - CmsSwtUtils.style(getControl(), FormStyle.propertyMessage); -// getControl().setData(STYLE, FormStyle.propertyMessage.style()); - else - CmsSwtUtils.style(getControl(), FormStyle.propertyText); -// getControl().setData(STYLE, FormStyle.propertyText.style()); - super.stopEditing(); - } - - public String getPropertyName() { - return propertyName; - } - - @Override - protected Control createControl(Composite box, String style) { - if (isEditing()) { - return createCustomEditableControl(box, style); - } else - return createLabel(box, style); - } - - protected Label createLabel(Composite box, String style) { - Label lbl = new Label(box, getStyle() | SWT.WRAP); - lbl.setLayoutData(CmsSwtUtils.fillWidth()); - CmsSwtUtils.style(lbl, style); - CmsSwtUtils.markup(lbl); - if (mouseListener != null) - lbl.addMouseListener(mouseListener); - return lbl; - } - - private Control createCustomEditableControl(Composite box, String style) { - box.setLayoutData(CmsSwtUtils.fillWidth()); - Composite dateComposite = new Composite(box, SWT.NONE); - GridLayout gl = EclipseUiUtils.noSpaceGridLayout(new GridLayout(2, false)); - gl.horizontalSpacing = fieldBtnSpacing; - dateComposite.setLayout(gl); - dateTxt = new Text(dateComposite, SWT.BORDER); - CmsSwtUtils.style(dateTxt, style); - dateTxt.setLayoutData(new GridData(120, SWT.DEFAULT)); - dateTxt.setToolTipText( - "Enter a date with form \"" + FormUtils.DEFAULT_SHORT_DATE_FORMAT + "\" or use the calendar"); - openCalBtn = new Button(dateComposite, SWT.FLAT); - CmsSwtUtils.style(openCalBtn, FormStyle.calendar.style() + FormStyle.BUTTON_SUFFIX); - GridData gd = new GridData(SWT.CENTER, SWT.CENTER, false, false); - gd.heightHint = 17; - openCalBtn.setLayoutData(gd); - // openCalBtn.setImage(PeopleRapImages.CALENDAR_BTN); - - openCalBtn.addSelectionListener(new SelectionAdapter() { - private static final long serialVersionUID = 1L; - - public void widgetSelected(SelectionEvent event) { - CalendarPopup popup = new CalendarPopup(dateTxt); - popup.open(); - } - }); - - // dateTxt.addFocusListener(new FocusListener() { - // private static final long serialVersionUID = 1L; - // - // @Override - // public void focusLost(FocusEvent event) { - // String newVal = dateTxt.getText(); - // // Enable reset of the field - // if (FormUtils.notNull(newVal)) - // calendar = null; - // else { - // try { - // Calendar newCal = parseDate(newVal); - // // DateText.this.setText(newCal); - // calendar = newCal; - // } catch (ParseException pe) { - // // Silent. Manage error popup? - // if (calendar != null) - // EditablePropertyDate.this.setText(calendar); - // } - // } - // } - // - // @Override - // public void focusGained(FocusEvent event) { - // } - // }); - return dateTxt; - } - - protected void clear(boolean deep) { - Control child = getControl(); - if (deep || child instanceof Label) - super.clear(deep); - else { - child.getParent().dispose(); - } - } - - /** Enable setting a custom tooltip on the underlying text */ - @Deprecated - public void setToolTipText(String toolTipText) { - dateTxt.setToolTipText(toolTipText); - } - - @Deprecated - /** Enable setting a custom message on the underlying text */ - public void setMessage(String message) { - dateTxt.setMessage(message); - } - - @Deprecated - public void setText(Calendar cal) { - String newValueStr = ""; - if (cal != null) - newValueStr = dateFormat.format(cal.getTime()); - if (!newValueStr.equals(dateTxt.getText())) - dateTxt.setText(newValueStr); - } - - // UTILITIES TO MANAGE THE CALENDAR POPUP - // TODO manage the popup shell in a cleaner way - private class CalendarPopup extends Shell { - private static final long serialVersionUID = 1L; - private DateTime dateTimeCtl; - - public CalendarPopup(Control source) { - super(source.getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP); - populate(); - // Add border and shadow style - CmsSwtUtils.markup(CalendarPopup.this); - CmsSwtUtils.style(CalendarPopup.this, FormStyle.popupCalendar.style()); - pack(); - layout(); - setLocation(source.toDisplay((source.getLocation().x - 2), (source.getSize().y) + 3)); - - addShellListener(new ShellAdapter() { - private static final long serialVersionUID = 5178980294808435833L; - - @Override - public void shellDeactivated(ShellEvent e) { - close(); - dispose(); - } - }); - open(); - } - - private void setProperty() { - // Direct set does not seems to work. investigate - // cal.set(dateTimeCtl.getYear(), dateTimeCtl.getMonth(), - // dateTimeCtl.getDay(), 12, 0); - Calendar cal = new GregorianCalendar(); - cal.set(Calendar.YEAR, dateTimeCtl.getYear()); - cal.set(Calendar.MONTH, dateTimeCtl.getMonth()); - cal.set(Calendar.DAY_OF_MONTH, dateTimeCtl.getDay()); - String dateStr = dateFormat.format(cal.getTime()); - dateTxt.setText(dateStr); - } - - protected void populate() { - setLayout(EclipseUiUtils.noSpaceGridLayout()); - - dateTimeCtl = new DateTime(this, SWT.CALENDAR); - dateTimeCtl.setLayoutData(EclipseUiUtils.fillAll()); - - Calendar calendar = FormUtils.parseDate(dateFormat, dateTxt.getText()); - - if (calendar != null) - dateTimeCtl.setDate(calendar.get(Calendar.YEAR), calendar.get(Calendar.MONTH), - calendar.get(Calendar.DAY_OF_MONTH)); - - dateTimeCtl.addSelectionListener(new SelectionAdapter() { - private static final long serialVersionUID = -8414377364434281112L; - - @Override - public void widgetSelected(SelectionEvent e) { - setProperty(); - } - }); - - dateTimeCtl.addMouseListener(new MouseListener() { - private static final long serialVersionUID = 1L; - - @Override - public void mouseUp(MouseEvent e) { - } - - @Override - public void mouseDown(MouseEvent e) { - } - - @Override - public void mouseDoubleClick(MouseEvent e) { - setProperty(); - close(); - dispose(); - } - }); - } - } -} \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditablePropertyString.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditablePropertyString.java deleted file mode 100644 index 092009355..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/EditablePropertyString.java +++ /dev/null @@ -1,80 +0,0 @@ -package org.argeo.cms.ui.forms; - -import static org.argeo.cms.ui.forms.FormStyle.propertyMessage; -import static org.argeo.cms.ui.forms.FormStyle.propertyText; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.viewers.EditablePart; -import org.argeo.cms.ui.widgets.EditableText; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; - -/** Editable String in a CMS context */ -public class EditablePropertyString extends EditableText implements EditablePart { - private static final long serialVersionUID = 5055000749992803591L; - - private String propertyName; - private String message; - - // encode the '&' character in rap - private final static String AMPERSAND = "&"; - private final static String AMPERSAND_REGEX = "&(?![#a-zA-Z0-9]+;)"; - - public EditablePropertyString(Composite parent, int style, Node node, String propertyName, String message) - throws RepositoryException { - super(parent, style, node, true); - //setUseTextAsLabel(true); - this.propertyName = propertyName; - this.message = message; - - if (node.hasProperty(propertyName)) { - this.setStyle(propertyText.style()); - this.setText(node.getProperty(propertyName).getString()); - } else { - this.setStyle(propertyMessage.style()); - this.setText(message + " "); - } - } - - public void setText(String text) { - Control child = getControl(); - if (child instanceof Label) { - Label lbl = (Label) child; - if (EclipseUiUtils.isEmpty(text)) - lbl.setText(message + " "); - else - // TODO enhance this - lbl.setText(text.replaceAll(AMPERSAND_REGEX, AMPERSAND)); - } else if (child instanceof Text) { - Text txt = (Text) child; - if (EclipseUiUtils.isEmpty(text)) { - txt.setText(""); - txt.setMessage(message + " "); - } else - txt.setText(text.replaceAll("
", "\n")); - } - } - - public synchronized void startEditing() { - CmsSwtUtils.style(getControl(), FormStyle.propertyText); - super.startEditing(); - } - - public synchronized void stopEditing() { - if (EclipseUiUtils.isEmpty(((Text) getControl()).getText())) - CmsSwtUtils.style(getControl(), FormStyle.propertyMessage); - else - CmsSwtUtils.style(getControl(), FormStyle.propertyText); - super.stopEditing(); - } - - public String getPropertyName() { - return propertyName; - } -} \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormConstants.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormConstants.java deleted file mode 100644 index fe9f7e7d7..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormConstants.java +++ /dev/null @@ -1,7 +0,0 @@ -package org.argeo.cms.ui.forms; - -/** Constants used in the various CMS Forms */ -public interface FormConstants { - // DATAKEYS - public final static String LINKED_VALUE = "LinkedValue"; -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormEditorHeader.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormEditorHeader.java deleted file mode 100644 index f3a56f7b9..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormEditorHeader.java +++ /dev/null @@ -1,114 +0,0 @@ -package org.argeo.cms.ui.forms; - -import java.util.Observable; -import java.util.Observer; - -import javax.jcr.Node; - -import org.argeo.api.cms.CmsEditable; -import org.argeo.cms.swt.CmsSwtUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; - -/** Add life cycle management abilities to an editable form page */ -public class FormEditorHeader implements SelectionListener, Observer { - private static final long serialVersionUID = 7392898696542484282L; - - // private final Node context; - private final CmsEditable cmsEditable; - private Button publishBtn; - - // Should we provide here the ability to switch from read only to edition - // mode? - // private Button editBtn; - // private boolean readOnly; - - // TODO add information about the current node status, typically if it is - // dirty or not - - private Composite parent; - private Composite display; - private Object layoutData; - - public FormEditorHeader(Composite parent, int style, Node context, - CmsEditable cmsEditable) { - this.cmsEditable = cmsEditable; - this.parent = parent; - // readOnly = SWT.READ_ONLY == (style & SWT.READ_ONLY); - // this.context = context; - if (this.cmsEditable instanceof Observable) - ((Observable) this.cmsEditable).addObserver(this); - refresh(); - } - - public void setLayoutData(Object layoutData) { - this.layoutData = layoutData; - if (display != null && !display.isDisposed()) - display.setLayoutData(layoutData); - } - - protected void refresh() { - if (display != null && !display.isDisposed()) - display.dispose(); - - display = new Composite(parent, SWT.NONE); - display.setLayoutData(layoutData); - - CmsSwtUtils.style(display, FormStyle.header.style()); - display.setBackgroundMode(SWT.INHERIT_FORCE); - - display.setLayout(CmsSwtUtils.noSpaceGridLayout()); - - publishBtn = createSimpleBtn(display, getPublishButtonLabel()); - display.moveAbove(null); - parent.layout(); - } - - private Button createSimpleBtn(Composite parent, String label) { - Button button = new Button(parent, SWT.FLAT | SWT.PUSH); - button.setText(label); - CmsSwtUtils.style(button, FormStyle.header.style()); - button.addSelectionListener(this); - return button; - } - - private String getPublishButtonLabel() { - // Rather check if the current node differs from what has been - // previously committed - // For the time being, we always reach here, the underlying CmsEditable - // is always editing. - if (cmsEditable.isEditing()) - return " Publish "; - else - return " Edit "; - } - - @Override - public void widgetSelected(SelectionEvent e) { - if (e.getSource() == publishBtn) { - // For the time being, the underlying CmsEditable - // is always editing when we reach this point - if (cmsEditable.isEditing()) { - // we always leave the node in a check outed state - cmsEditable.stopEditing(); - cmsEditable.startEditing(); - } else { - cmsEditable.startEditing(); - } - } - } - - @Override - public void widgetDefaultSelected(SelectionEvent e) { - } - - @Override - public void update(Observable o, Object arg) { - if (o == cmsEditable) { - refresh(); - } - } -} \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormPageViewer.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormPageViewer.java deleted file mode 100644 index cc732d49d..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormPageViewer.java +++ /dev/null @@ -1,608 +0,0 @@ -package org.argeo.cms.ui.forms; - -import java.io.IOException; -import java.io.InputStream; -import java.text.DateFormat; -import java.text.SimpleDateFormat; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.List; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.Value; -import javax.jcr.ValueFormatException; - -import org.argeo.api.cms.Cms2DSize; -import org.argeo.api.cms.CmsEditable; -import org.argeo.api.cms.CmsImageManager; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.viewers.AbstractPageViewer; -import org.argeo.cms.ui.viewers.EditablePart; -import org.argeo.cms.ui.viewers.Section; -import org.argeo.cms.ui.viewers.SectionPart; -import org.argeo.cms.ui.widgets.EditableImage; -import org.argeo.cms.ui.widgets.Img; -import org.argeo.cms.ui.widgets.StyledControl; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.argeo.jcr.JcrException; -import org.argeo.jcr.JcrUtils; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.rap.fileupload.FileDetails; -import org.eclipse.rap.fileupload.FileUploadEvent; -import org.eclipse.rap.fileupload.FileUploadHandler; -import org.eclipse.rap.fileupload.FileUploadListener; -import org.eclipse.rap.fileupload.FileUploadReceiver; -import org.eclipse.rap.rwt.service.ServerPushSession; -import org.eclipse.rap.rwt.widgets.FileUpload; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.FocusEvent; -import org.eclipse.swt.events.FocusListener; -import org.eclipse.swt.events.MouseAdapter; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.events.MouseListener; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.SelectionListener; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.layout.FormAttachment; -import org.eclipse.swt.layout.FormData; -import org.eclipse.swt.layout.FormLayout; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.layout.RowLayout; -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.eclipse.swt.widgets.Text; - -/** Manage life cycle of a form page that is linked to a given node */ -public class FormPageViewer extends AbstractPageViewer { - private final static CmsLog log = CmsLog.getLog(FormPageViewer.class); - private static final long serialVersionUID = 5277789504209413500L; - - private final Section mainSection; - - // TODO manage within the CSS - private Integer labelColWidth = null; - private int rowLayoutHSpacing = 8; - - // Context cached in the viewer - // The reference to translate from text to calendar and reverse - private DateFormat dateFormat = new SimpleDateFormat(FormUtils.DEFAULT_SHORT_DATE_FORMAT); - private CmsImageManager imageManager; - private FileUploadListener fileUploadListener; - - public FormPageViewer(Section mainSection, int style, CmsEditable cmsEditable) throws RepositoryException { - super(mainSection, style, cmsEditable); - this.mainSection = mainSection; - - if (getCmsEditable().canEdit()) { - fileUploadListener = new FUL(); - } - } - - @Override - protected void prepare(EditablePart part, Object caretPosition) { - if (part instanceof Img) { - ((Img) part).setFileUploadListener(fileUploadListener); - } - } - - /** To be overridden.Save the edited part. */ - protected void save(EditablePart part) throws RepositoryException { - Node node = null; - if (part instanceof EditableMultiStringProperty) { - EditableMultiStringProperty ept = (EditableMultiStringProperty) part; - // SWT : View - List values = ept.getValues(); - // JCR : Model - node = ept.getNode(); - String propName = ept.getPropertyName(); - if (values.isEmpty()) { - if (node.hasProperty(propName)) - node.getProperty(propName).remove(); - } else { - node.setProperty(propName, values.toArray(new String[0])); - } - // => Viewer : Controller - } else if (part instanceof EditablePropertyString) { - EditablePropertyString ept = (EditablePropertyString) part; - // SWT : View - String txt = ((Text) ept.getControl()).getText(); - // JCR : Model - node = ept.getNode(); - String propName = ept.getPropertyName(); - if (EclipseUiUtils.isEmpty(txt)) { - if (node.hasProperty(propName)) - node.getProperty(propName).remove(); - } else { - setPropertySilently(node, propName, txt); - // node.setProperty(propName, txt); - } - // node.getSession().save(); - // => Viewer : Controller - } else if (part instanceof EditablePropertyDate) { - EditablePropertyDate ept = (EditablePropertyDate) part; - Calendar cal = FormUtils.parseDate(dateFormat, ((Text) ept.getControl()).getText()); - node = ept.getNode(); - String propName = ept.getPropertyName(); - if (cal == null) { - if (node.hasProperty(propName)) - node.getProperty(propName).remove(); - } else { - node.setProperty(propName, cal); - } - // node.getSession().save(); - // => Viewer : Controller - } - // TODO: make this configurable, sometimes we do not want to save the - // current session at this stage - if (node != null && node.getSession().hasPendingChanges()) { - JcrUtils.updateLastModified(node, true); - node.getSession().save(); - } - } - - @Override - protected void updateContent(EditablePart part) throws RepositoryException { - if (part instanceof EditableMultiStringProperty) { - EditableMultiStringProperty ept = (EditableMultiStringProperty) part; - // SWT : View - Node node = ept.getNode(); - String propName = ept.getPropertyName(); - List valStrings = new ArrayList(); - if (node.hasProperty(propName)) { - Value[] values = node.getProperty(propName).getValues(); - for (Value val : values) - valStrings.add(val.getString()); - } - ept.setValues(valStrings); - } else if (part instanceof EditablePropertyString) { - // || part instanceof EditableLink - EditablePropertyString ept = (EditablePropertyString) part; - // JCR : Model - Node node = ept.getNode(); - String propName = ept.getPropertyName(); - if (node.hasProperty(propName)) { - String value = node.getProperty(propName).getString(); - ept.setText(value); - } else - ept.setText(""); - // => Viewer : Controller - } else if (part instanceof EditablePropertyDate) { - EditablePropertyDate ept = (EditablePropertyDate) part; - // JCR : Model - Node node = ept.getNode(); - String propName = ept.getPropertyName(); - if (node.hasProperty(propName)) - ept.setText(dateFormat.format(node.getProperty(propName).getDate().getTime())); - else - ept.setText(""); - } else if (part instanceof SectionPart) { - SectionPart sectionPart = (SectionPart) part; - Node partNode = sectionPart.getNode(); - // use control AFTER setting style, since it may have been reset - if (part instanceof EditableImage) { - EditableImage editableImage = (EditableImage) part; - imageManager().load(partNode, part.getControl(), editableImage.getPreferredImageSize()); - } - } - } - - // FILE UPLOAD LISTENER - protected class FUL implements FileUploadListener { - - public FUL() { - } - - public void uploadProgress(FileUploadEvent event) { - // TODO Monitor upload progress - } - - public void uploadFailed(FileUploadEvent event) { - throw new IllegalStateException("Upload failed " + event, event.getException()); - } - - public void uploadFinished(FileUploadEvent event) { - for (FileDetails file : event.getFileDetails()) { - if (log.isDebugEnabled()) - log.debug("Received: " + file.getFileName()); - } - mainSection.getDisplay().syncExec(new Runnable() { - @Override - public void run() { - saveEdit(); - } - }); - FileUploadHandler uploadHandler = (FileUploadHandler) event.getSource(); - uploadHandler.dispose(); - } - } - - // FOCUS OUT LISTENER - protected FocusListener createFocusListener() { - return new FocusOutListener(); - } - - private class FocusOutListener implements FocusListener { - private static final long serialVersionUID = -6069205786732354186L; - - @Override - public void focusLost(FocusEvent event) { - saveEdit(); - } - - @Override - public void focusGained(FocusEvent event) { - // does nothing; - } - } - - // MOUSE LISTENER - @Override - protected MouseListener createMouseListener() { - return new ML(); - } - - private class ML extends MouseAdapter { - private static final long serialVersionUID = 8526890859876770905L; - - @Override - public void mouseDoubleClick(MouseEvent e) { - if (e.button == 1) { - Control source = (Control) e.getSource(); - if (getCmsEditable().canEdit()) { - if (getCmsEditable().isEditing() && !(getEdited() instanceof Img)) { - if (source == mainSection) - return; - EditablePart part = findDataParent(source); - upload(part); - } else { - getCmsEditable().startEditing(); - } - } - } - } - - @Override - public void mouseDown(MouseEvent e) { - if (getCmsEditable().isEditing()) { - if (e.button == 1) { - Control source = (Control) e.getSource(); - EditablePart composite = findDataParent(source); - Point point = new Point(e.x, e.y); - if (!(composite instanceof Img)) - edit(composite, source.toDisplay(point)); - } else if (e.button == 3) { - // EditablePart composite = findDataParent((Control) e - // .getSource()); - // if (styledTools != null) - // styledTools.show(composite, new Point(e.x, e.y)); - } - } - } - - protected synchronized void upload(EditablePart part) { - if (part instanceof SectionPart) { - if (part instanceof Img) { - if (getEdited() == part) - return; - edit(part, null); - layout(part.getControl()); - } - } - } - } - - @Override - public Control getControl() { - return mainSection; - } - - protected CmsImageManager imageManager() { - if (imageManager == null) - imageManager = (CmsImageManager) CmsSwtUtils.getCmsView(mainSection).getImageManager(); - return imageManager; - } - - // LOCAL UI HELPERS - protected Section createSectionIfNeeded(Composite body, Node node) throws RepositoryException { - Section section = null; - if (node != null) { - section = new Section(body, SWT.NO_FOCUS, node); - section.setLayoutData(CmsSwtUtils.fillWidth()); - section.setLayout(CmsSwtUtils.noSpaceGridLayout()); - } - return section; - } - - protected void createSimpleLT(Composite bodyRow, Node node, String propName, String label, String msg) - throws RepositoryException { - if (getCmsEditable().canEdit() || node.hasProperty(propName)) { - createPropertyLbl(bodyRow, label); - EditablePropertyString eps = new EditablePropertyString(bodyRow, SWT.WRAP | SWT.LEFT, node, propName, msg); - eps.setMouseListener(getMouseListener()); - eps.setFocusListener(getFocusListener()); - eps.setLayoutData(CmsSwtUtils.fillWidth()); - } - } - - protected void createMultiStringLT(Composite bodyRow, Node node, String propName, String label, String msg) - throws RepositoryException { - boolean canEdit = getCmsEditable().canEdit(); - if (canEdit || node.hasProperty(propName)) { - createPropertyLbl(bodyRow, label); - - List valueStrings = new ArrayList(); - - if (node.hasProperty(propName)) { - Value[] values = node.getProperty(propName).getValues(); - for (Value value : values) - valueStrings.add(value.getString()); - } - - // TODO use a drop down to display possible values to the end user - EditableMultiStringProperty emsp = new EditableMultiStringProperty(bodyRow, SWT.SINGLE | SWT.LEAD, node, - propName, valueStrings, new String[] { "Implement this" }, msg, - canEdit ? getRemoveValueSelListener() : null); - addListeners(emsp); - // emsp.setMouseListener(getMouseListener()); - emsp.setStyle(FormStyle.propertyMessage.style()); - emsp.setLayoutData(CmsSwtUtils.fillWidth()); - } - } - - protected Label createPropertyLbl(Composite parent, String value) { - return createPropertyLbl(parent, value, SWT.NONE); - } - - protected Label createPropertyLbl(Composite parent, String value, int vAlign) { - // boolean isSmall = CmsView.getCmsView(parent).getUxContext().isSmall(); - Label label = new Label(parent, SWT.LEAD | SWT.WRAP); - label.setText(value + " "); - CmsSwtUtils.style(label, FormStyle.propertyLabel.style()); - GridData gd = new GridData(SWT.LEAD, vAlign, false, false); - if (labelColWidth != null) - gd.widthHint = labelColWidth; - label.setLayoutData(gd); - return label; - } - - protected Label newStyledLabel(Composite parent, String style, String value) { - Label label = new Label(parent, SWT.NONE); - label.setText(value); - CmsSwtUtils.style(label, style); - return label; - } - - protected Composite createRowLayoutComposite(Composite parent) throws RepositoryException { - Composite bodyRow = new Composite(parent, SWT.NO_FOCUS); - bodyRow.setLayoutData(CmsSwtUtils.fillWidth()); - RowLayout rl = new RowLayout(SWT.WRAP); - rl.type = SWT.HORIZONTAL; - rl.spacing = rowLayoutHSpacing; - rl.marginHeight = rl.marginWidth = 0; - rl.marginTop = rl.marginBottom = rl.marginLeft = rl.marginRight = 0; - bodyRow.setLayout(rl); - return bodyRow; - } - - protected Composite createAddImgComposite(final Section section, Composite parent, final Node parentNode) - throws RepositoryException { - - Composite body = new Composite(parent, SWT.NO_FOCUS); - body.setLayout(new GridLayout()); - - FormFileUploadReceiver receiver = new FormFileUploadReceiver(section, parentNode, null); - final FileUploadHandler currentUploadHandler = new FileUploadHandler(receiver); - if (fileUploadListener != null) - currentUploadHandler.addUploadListener(fileUploadListener); - - // Button creation - final FileUpload fileUpload = new FileUpload(body, SWT.BORDER); - fileUpload.setText("Import an image"); - fileUpload.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, true, true)); - fileUpload.addSelectionListener(new SelectionAdapter() { - private static final long serialVersionUID = 4869523412991968759L; - - @Override - public void widgetSelected(SelectionEvent e) { - ServerPushSession pushSession = new ServerPushSession(); - pushSession.start(); - String uploadURL = currentUploadHandler.getUploadUrl(); - fileUpload.submit(uploadURL); - } - }); - - return body; - } - - protected class FormFileUploadReceiver extends FileUploadReceiver { - - private Node context; - private Section section; - private String name; - - public FormFileUploadReceiver(Section section, Node context, String name) { - this.context = context; - this.section = section; - this.name = name; - } - - @Override - public void receive(InputStream stream, FileDetails details) throws IOException { - - if (name == null) - name = details.getFileName(); - - // TODO clean image name more carefully - String cleanedName = name.replaceAll("[^a-zA-Z0-9-.]", ""); - // We add a unique prefix to workaround the cache issue: when - // deleting and re-adding a new image with same name, the end user - // browser will use the cache and the image will remain unchanged - // for a while - cleanedName = System.currentTimeMillis() % 100000 + "_" + cleanedName; - - imageManager().uploadImage(context, context, cleanedName, stream, details.getContentType()); - // TODO clean refresh strategy - section.getDisplay().asyncExec(new Runnable() { - @Override - public void run() { - try { - FormPageViewer.this.refresh(section); - section.layout(); - section.getParent().layout(); - } catch (RepositoryException re) { - throw new JcrException("Unable to refresh " + "image section for " + context, re); - } - } - }); - } - } - - protected void addListeners(StyledControl control) { - control.setMouseListener(getMouseListener()); - control.setFocusListener(getFocusListener()); - } - - protected Img createImgComposite(Composite parent, Node node, Point preferredSize) throws RepositoryException { - Img img = new Img(parent, SWT.NONE, node, new Cms2DSize(preferredSize.x, preferredSize.y)) { - private static final long serialVersionUID = 1297900641952417540L; - - @Override - protected void setContainerLayoutData(Composite composite) { - composite.setLayoutData(CmsSwtUtils.grabWidth(SWT.CENTER, SWT.DEFAULT)); - } - - @Override - protected void setControlLayoutData(Control control) { - control.setLayoutData(CmsSwtUtils.grabWidth(SWT.CENTER, SWT.DEFAULT)); - } - }; - img.setLayoutData(CmsSwtUtils.grabWidth(SWT.CENTER, SWT.DEFAULT)); - updateContent(img); - addListeners(img); - return img; - } - - protected Composite addDeleteAbility(final Section section, final Node sessionNode, int topWeight, - int rightWeight) { - Composite comp = new Composite(section, SWT.NONE); - comp.setLayoutData(CmsSwtUtils.fillAll()); - comp.setLayout(new FormLayout()); - - // The body to be populated - Composite body = new Composite(comp, SWT.NO_FOCUS); - body.setLayoutData(EclipseUiUtils.fillFormData()); - - if (getCmsEditable().canEdit()) { - // the delete button - Button deleteBtn = new Button(comp, SWT.FLAT); - CmsSwtUtils.style(deleteBtn, FormStyle.deleteOverlay.style()); - FormData formData = new FormData(); - formData.right = new FormAttachment(rightWeight, 0); - formData.top = new FormAttachment(topWeight, 0); - deleteBtn.setLayoutData(formData); - deleteBtn.moveAbove(body); - - deleteBtn.addSelectionListener(new SelectionAdapter() { - private static final long serialVersionUID = 4304223543657238462L; - - @Override - public void widgetSelected(SelectionEvent e) { - super.widgetSelected(e); - if (MessageDialog.openConfirm(section.getShell(), "Confirm deletion", - "Are you really you want to remove this?")) { - Session session; - try { - session = sessionNode.getSession(); - Section parSection = section.getParentSection(); - sessionNode.remove(); - session.save(); - refresh(parSection); - layout(parSection); - } catch (RepositoryException re) { - throw new JcrException("Unable to delete " + sessionNode, re); - } - - } - - } - }); - } - return body; - } - -// // LOCAL HELPERS FOR NODE MANAGEMENT -// private Node getOrCreateNode(Node parent, String nodeName, String nodeType) throws RepositoryException { -// Node node = null; -// if (getCmsEditable().canEdit() && !parent.hasNode(nodeName)) { -// node = JcrUtils.mkdirs(parent, nodeName, nodeType); -// parent.getSession().save(); -// } -// -// if (getCmsEditable().canEdit() || parent.hasNode(nodeName)) -// node = parent.getNode(nodeName); -// -// return node; -// } - - private SelectionListener getRemoveValueSelListener() { - return new SelectionAdapter() { - private static final long serialVersionUID = 9022259089907445195L; - - @Override - public void widgetSelected(SelectionEvent e) { - Object source = e.getSource(); - if (source instanceof Button) { - Button btn = (Button) source; - Object obj = btn.getData(FormConstants.LINKED_VALUE); - EditablePart ep = findDataParent(btn); - if (ep != null && ep instanceof EditableMultiStringProperty) { - EditableMultiStringProperty emsp = (EditableMultiStringProperty) ep; - List values = emsp.getValues(); - if (values.contains(obj)) { - values.remove(values.indexOf(obj)); - emsp.setValues(values); - try { - save(emsp); - // TODO workaround to force refresh - edit(emsp, 0); - cancelEdit(); - } catch (RepositoryException e1) { - throw new JcrException("Unable to remove value " + obj, e1); - } - layout(emsp); - } - } - } - } - }; - } - - protected void setPropertySilently(Node node, String propName, String value) throws RepositoryException { - try { - // TODO Clean this: - // Format strings to replace \n - value = value.replaceAll("\n", "
"); - // Do not make the update if validation fails - try { - MarkupValidatorCopy.getInstance().validate(value); - } catch (Exception e) { - log.warn("Cannot set [" + value + "] on prop " + propName + "of " + node - + ", String cannot be validated - " + e.getMessage()); - return; - } - // TODO check if the newly created property is of the correct type, - // otherwise the property will be silently created with a STRING - // property type. - node.setProperty(propName, value); - } catch (ValueFormatException vfe) { - log.warn("Cannot set [" + value + "] on prop " + propName + "of " + node + " - " + vfe.getMessage()); - } - } -} \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormStyle.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormStyle.java deleted file mode 100644 index 24067eaaa..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormStyle.java +++ /dev/null @@ -1,29 +0,0 @@ -package org.argeo.cms.ui.forms; - -import org.argeo.api.cms.CmsStyle; - -/** Syles used */ -public enum FormStyle implements CmsStyle { - // Main - form, title, - // main part - header, headerBtn, headerCombo, section, sectionHeader, - // Property fields - propertyLabel, propertyText, propertyMessage, errorMessage, - // Date - popupCalendar, - // Buttons - starred, unstarred, starOverlay, editOverlay, deleteOverlay, updateOverlay, deleteOverlaySmall, calendar, delete, - // Contacts - email, address, phone, website, - // Social Media - facebook, twitter, linkedIn, instagram; - - @Override - public String getClassPrefix() { - return "argeo-form"; - } - - // TODO clean button style management - public final static String BUTTON_SUFFIX = "_btn"; -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormUtils.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormUtils.java deleted file mode 100644 index 1a445bd76..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/FormUtils.java +++ /dev/null @@ -1,196 +0,0 @@ -package org.argeo.cms.ui.forms; - -import java.text.DateFormat; -import java.text.ParseException; -import java.util.Calendar; -import java.util.Date; -import java.util.GregorianCalendar; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.api.cms.CmsView; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.CmsException; -import org.argeo.cms.ui.util.CmsUiUtils; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.eclipse.jface.fieldassist.ControlDecoration; -import org.eclipse.jface.fieldassist.FieldDecorationRegistry; -import org.eclipse.jface.viewers.DoubleClickEvent; -import org.eclipse.jface.viewers.IDoubleClickListener; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.TableViewer; -import org.eclipse.swt.SWT; -import org.eclipse.swt.graphics.Color; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; - -/** Utilitary methods to ease implementation of CMS forms */ -public class FormUtils { - private final static CmsLog log = CmsLog.getLog(FormUtils.class); - - public final static String DEFAULT_SHORT_DATE_FORMAT = "dd/MM/yyyy"; - - /** Best effort to convert a String to a calendar. Fails silently */ - public static Calendar parseDate(DateFormat dateFormat, String calStr) { - Calendar cal = null; - if (EclipseUiUtils.notEmpty(calStr)) { - try { - Date date = dateFormat.parse(calStr); - cal = new GregorianCalendar(); - cal.setTime(date); - } catch (ParseException pe) { - // Silent - log.warn("Unable to parse date: " + calStr + " - msg: " - + pe.getMessage()); - } - } - return cal; - } - - /** Add a double click listener on tables that display a JCR node list */ - public static void addCanonicalDoubleClickListener(final TableViewer v) { - v.addDoubleClickListener(new IDoubleClickListener() { - - @Override - public void doubleClick(DoubleClickEvent event) { - CmsView cmsView = CmsUiUtils.getCmsView(); - Node node = (Node) ((IStructuredSelection) event.getSelection()) - .getFirstElement(); - try { - cmsView.navigateTo(node.getPath()); - } catch (RepositoryException e) { - throw new CmsException("Unable to get path for node " - + node + " before calling navigateTo(path)", e); - } - } - }); - } - - // MANAGE ERROR DECORATION - - public static ControlDecoration addDecoration(final Text text) { - final ControlDecoration dynDecoration = new ControlDecoration(text, - SWT.LEFT); - Image icon = getDecorationImage(FieldDecorationRegistry.DEC_ERROR); - dynDecoration.setImage(icon); - dynDecoration.setMarginWidth(3); - dynDecoration.hide(); - return dynDecoration; - } - - public static void refreshDecoration(Text text, ControlDecoration deco, - boolean isValid, boolean clean) { - if (isValid || clean) { - text.setBackground(null); - deco.hide(); - } else { - text.setBackground(new Color(text.getDisplay(), 250, 200, 150)); - deco.show(); - } - } - - public static Image getDecorationImage(String image) { - FieldDecorationRegistry registry = FieldDecorationRegistry.getDefault(); - return registry.getFieldDecoration(image).getImage(); - } - - public static void addCompulsoryDecoration(Label label) { - final ControlDecoration dynDecoration = new ControlDecoration(label, - SWT.RIGHT | SWT.TOP); - Image icon = getDecorationImage(FieldDecorationRegistry.DEC_REQUIRED); - dynDecoration.setImage(icon); - dynDecoration.setMarginWidth(3); - } - - // TODO the read only generation of read only links for various contact type - // should be factorised in the cms Utils. - /** - * Creates the read-only HTML snippet to display in a label with styling - * enabled in order to provide a click-able phone number - */ - public static String getPhoneLink(String value) { - return getPhoneLink(value, value); - } - - /** - * Creates the read-only HTML snippet to display in a label with styling - * enabled in order to provide a click-able phone number - * - * @param value - * @param label - * a potentially distinct label - * @return - */ - public static String getPhoneLink(String value, String label) { - StringBuilder builder = new StringBuilder(); - builder.append("").append(label) - .append(""); - return builder.toString(); - } - - /** - * Creates the read-only HTML snippet to display in a label with styling - * enabled in order to provide a click-able mail - */ - public static String getMailLink(String value) { - return getMailLink(value, value); - } - - /** - * Creates the read-only HTML snippet to display in a label with styling - * enabled in order to provide a click-able mail - * - * @param value - * @param label - * a potentially distinct label - * @return - */ - public static String getMailLink(String value, String label) { - StringBuilder builder = new StringBuilder(); - value = replaceAmpersand(value); - builder.append("").append(label).append(""); - return builder.toString(); - } - - /** - * Creates the read-only HTML snippet to display in a label with styling - * enabled in order to provide a click-able link - */ - public static String getUrlLink(String value) { - return getUrlLink(value, value); - } - - /** - * Creates the read-only HTML snippet to display in a label with styling - * enabled in order to provide a click-able link - */ - public static String getUrlLink(String value, String label) { - StringBuilder builder = new StringBuilder(); - value = replaceAmpersand(value); - label = replaceAmpersand(label); - if (!(value.startsWith("http://") || value.startsWith("https://"))) - value = "http://" + value; - builder.append("" + label + ""); - return builder.toString(); - } - - private static String AMPERSAND = "&"; - - /** - * Cleans a String by replacing any '&' by its HTML encoding '&#38;' to - * avoid SAXParseException while rendering HTML with RWT - */ - public static String replaceAmpersand(String value) { - value = value.replaceAll("&(?![#a-zA-Z0-9]+;)", AMPERSAND); - return value; - } - - // Prevents instantiation - private FormUtils() { - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/MarkupValidatorCopy.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/MarkupValidatorCopy.java deleted file mode 100644 index 3f588d1ea..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/MarkupValidatorCopy.java +++ /dev/null @@ -1,169 +0,0 @@ -package org.argeo.cms.ui.forms; - -import java.io.StringReader; -import java.text.MessageFormat; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.xml.parsers.SAXParser; -import javax.xml.parsers.SAXParserFactory; - -import org.eclipse.rap.rwt.SingletonUtil; -import org.eclipse.swt.widgets.Widget; -import org.xml.sax.Attributes; -import org.xml.sax.InputSource; -import org.xml.sax.helpers.DefaultHandler; - -/** - * Copy of RAP v2.3 since it is in an internal package. - */ -class MarkupValidatorCopy { - - // Used by Eclipse Scout project - public static final String MARKUP_VALIDATION_DISABLED = "org.eclipse.rap.rwt.markupValidationDisabled"; - - private static final String DTD = createDTD(); - private static final Map SUPPORTED_ELEMENTS = createSupportedElementsMap(); - private final SAXParser saxParser; - - public static MarkupValidatorCopy getInstance() { - return SingletonUtil.getSessionInstance(MarkupValidatorCopy.class); - } - - public MarkupValidatorCopy() { - saxParser = createSAXParser(); - } - - public void validate(String text) { - StringBuilder markup = new StringBuilder(); - markup.append(DTD); - markup.append(""); - markup.append(text); - markup.append(""); - InputSource inputSource = new InputSource(new StringReader(markup.toString())); - try { - saxParser.parse(inputSource, new MarkupHandler()); - } catch (RuntimeException exception) { - throw exception; - } catch (Exception exception) { - throw new IllegalArgumentException("Failed to parse markup text", exception); - } - } - - public static boolean isValidationDisabledFor(Widget widget) { - return Boolean.TRUE.equals(widget.getData(MARKUP_VALIDATION_DISABLED)); - } - - private static SAXParser createSAXParser() { - SAXParser result = null; - SAXParserFactory parserFactory = SAXParserFactory.newInstance(); - try { - result = parserFactory.newSAXParser(); - } catch (Exception exception) { - throw new RuntimeException("Failed to create SAX parser", exception); - } - return result; - } - - private static String createDTD() { - StringBuilder result = new StringBuilder(); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append(""); - result.append("]>"); - return result.toString(); - } - - private static Map createSupportedElementsMap() { - Map result = new HashMap(); - result.put("html", new String[0]); - result.put("br", new String[0]); - result.put("b", new String[] { "style" }); - result.put("strong", new String[] { "style" }); - result.put("i", new String[] { "style" }); - result.put("em", new String[] { "style" }); - result.put("sub", new String[] { "style" }); - result.put("sup", new String[] { "style" }); - result.put("big", new String[] { "style" }); - result.put("small", new String[] { "style" }); - result.put("del", new String[] { "style" }); - result.put("ins", new String[] { "style" }); - result.put("code", new String[] { "style" }); - result.put("samp", new String[] { "style" }); - result.put("kbd", new String[] { "style" }); - result.put("var", new String[] { "style" }); - result.put("cite", new String[] { "style" }); - result.put("dfn", new String[] { "style" }); - result.put("q", new String[] { "style" }); - result.put("abbr", new String[] { "style", "title" }); - result.put("span", new String[] { "style" }); - result.put("img", new String[] { "style", "src", "width", "height", "title", "alt" }); - result.put("a", new String[] { "style", "href", "target", "title" }); - return result; - } - - private static class MarkupHandler extends DefaultHandler { - - @Override - public void startElement(String uri, String localName, String name, Attributes attributes) { - checkSupportedElements(name, attributes); - checkSupportedAttributes(name, attributes); - checkMandatoryAttributes(name, attributes); - } - - private static void checkSupportedElements(String elementName, Attributes attributes) { - if (!SUPPORTED_ELEMENTS.containsKey(elementName)) { - throw new IllegalArgumentException("Unsupported element in markup text: " + elementName); - } - } - - private static void checkSupportedAttributes(String elementName, Attributes attributes) { - if (attributes.getLength() > 0) { - List supportedAttributes = Arrays.asList(SUPPORTED_ELEMENTS.get(elementName)); - int index = 0; - String attributeName = attributes.getQName(index); - while (attributeName != null) { - if (!supportedAttributes.contains(attributeName)) { - String message = "Unsupported attribute \"{0}\" for element \"{1}\" in markup text"; - message = MessageFormat.format(message, new Object[] { attributeName, elementName }); - throw new IllegalArgumentException(message); - } - index++; - attributeName = attributes.getQName(index); - } - } - } - - private static void checkMandatoryAttributes(String elementName, Attributes attributes) { - checkIntAttribute(elementName, attributes, "img", "width"); - checkIntAttribute(elementName, attributes, "img", "height"); - } - - private static void checkIntAttribute(String elementName, Attributes attributes, String checkedElementName, - String checkedAttributeName) { - if (checkedElementName.equals(elementName)) { - String attribute = attributes.getValue(checkedAttributeName); - try { - Integer.parseInt(attribute); - } catch (NumberFormatException exception) { - String message = "Mandatory attribute \"{0}\" for element \"{1}\" is missing or not a valid integer"; - Object[] arguments = new Object[] { checkedAttributeName, checkedElementName }; - message = MessageFormat.format(message, arguments); - throw new IllegalArgumentException(message); - } - } - } - - } - -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/package-info.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/package-info.java deleted file mode 100644 index 5f954c1c4..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/forms/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Argeo CMS forms, based on SWT/JFace. */ -package org.argeo.cms.ui.forms; \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/CmsFsBrowser.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/CmsFsBrowser.java deleted file mode 100644 index 5a5ecdb8b..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/CmsFsBrowser.java +++ /dev/null @@ -1,524 +0,0 @@ -package org.argeo.cms.ui.fs; - -import java.io.IOException; -import java.net.URI; -import java.net.URISyntaxException; -import java.nio.file.DirectoryStream; -import java.nio.file.FileSystem; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.nio.file.spi.FileSystemProvider; -import java.security.PrivilegedActionException; -import java.security.PrivilegedExceptionAction; -import java.util.ArrayList; -import java.util.List; - -import javax.jcr.Node; -import javax.jcr.Repository; -import javax.jcr.Session; - -import org.argeo.cms.CmsException; -import org.argeo.cms.auth.CurrentUser; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.eclipse.ui.ColumnDefinition; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.argeo.eclipse.ui.fs.FileIconNameLabelProvider; -import org.argeo.eclipse.ui.fs.FsTableViewer; -import org.argeo.eclipse.ui.fs.FsUiConstants; -import org.argeo.eclipse.ui.fs.FsUiUtils; -import org.argeo.eclipse.ui.fs.NioFileLabelProvider; -import org.argeo.jcr.JcrUtils; -import org.eclipse.jface.viewers.DoubleClickEvent; -import org.eclipse.jface.viewers.IDoubleClickListener; -import org.eclipse.jface.viewers.ISelectionChangedListener; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.SelectionChangedEvent; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.SashForm; -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.events.MouseAdapter; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.layout.RowData; -import org.eclipse.swt.layout.RowLayout; -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.eclipse.swt.widgets.Table; -import org.eclipse.swt.widgets.Text; - -/** - * Default CMS browser composite: a sashForm layout with bookmarks at the left - * hand side, a simple table in the middle and an overview at right hand side. - */ -public class CmsFsBrowser extends Composite { - // private final static Log log = LogFactory.getLog(CmsFsBrowser.class); - private static final long serialVersionUID = -40347919096946585L; - - private final FileSystemProvider nodeFileSystemProvider; - private final Node currentBaseContext; - - // UI Parts for the browser - private Composite leftPannelCmp; - private Composite filterCmp; - private Text filterTxt; - private FsTableViewer directoryDisplayViewer; - private Composite rightPannelCmp; - - private FsContextMenu contextMenu; - - // Local context (this composite is state full) - private Path initialPath; - private Path currDisplayedFolder; - private Path currSelected; - - // local variables (to be cleaned) - private int bookmarkColWith = 500; - - /* - * WARNING: unfinalised implementation of the mechanism to retrieve base - * paths - */ - - private final static String NODE_PREFIX = "node://"; - - private String getCurrentHomePath() { - Session session = null; - try { - Repository repo = currentBaseContext.getSession().getRepository(); - session = CurrentUser.tryAs(() -> repo.login()); - String homepath = CmsJcrUtils.getUserHome(session).getPath(); - return homepath; - } catch (Exception e) { - throw new CmsException("Cannot retrieve Current User Home Path", e); - } finally { - JcrUtils.logoutQuietly(session); - } - } - - protected Path[] getMyFilesPath() { - // return Paths.get(System.getProperty("user.dir")); - String currHomeUriStr = NODE_PREFIX + getCurrentHomePath(); - try { - URI uri = new URI(currHomeUriStr); - FileSystem fileSystem = nodeFileSystemProvider.getFileSystem(uri); - if (fileSystem == null) { - PrivilegedExceptionAction pea = new PrivilegedExceptionAction() { - @Override - public FileSystem run() throws Exception { - return nodeFileSystemProvider.newFileSystem(uri, null); - } - - }; - fileSystem = CurrentUser.tryAs(pea); - } - Path[] paths = { fileSystem.getPath(getCurrentHomePath()), fileSystem.getPath("/") }; - return paths; - } catch (URISyntaxException | PrivilegedActionException e) { - throw new RuntimeException("unable to initialise home file system for " + currHomeUriStr, e); - } - } - - private Path[] getMyGroupsFilesPath() { - // TODO - Path[] paths = { Paths.get(System.getProperty("user.dir")), Paths.get("/tmp") }; - return paths; - } - - private Path[] getMyBookmarks() { - // TODO - Path[] paths = { Paths.get(System.getProperty("user.dir")), Paths.get("/tmp"), Paths.get("/opt") }; - return paths; - } - - /* End of warning */ - - public CmsFsBrowser(Composite parent, int style, Node context, FileSystemProvider fileSystemProvider) { - super(parent, style); - this.nodeFileSystemProvider = fileSystemProvider; - this.currentBaseContext = context; - - this.setLayout(EclipseUiUtils.noSpaceGridLayout()); - - SashForm form = new SashForm(this, SWT.HORIZONTAL); - - leftPannelCmp = new Composite(form, SWT.NO_FOCUS); - // Bookmarks are still static - populateBookmarks(leftPannelCmp); - - Composite centerCmp = new Composite(form, SWT.BORDER | SWT.NO_FOCUS); - createDisplay(centerCmp); - - rightPannelCmp = new Composite(form, SWT.NO_FOCUS); - - form.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - form.setWeights(new int[] { 15, 40, 20 }); - } - - void refresh() { - modifyFilter(false); - // also refresh bookmarks and groups - } - - private void createDisplay(final Composite parent) { - parent.setLayout(EclipseUiUtils.noSpaceGridLayout()); - - // top filter - filterCmp = new Composite(parent, SWT.NO_FOCUS); - filterCmp.setLayoutData(EclipseUiUtils.fillWidth()); - addFilterPanel(filterCmp); - - // Main display - directoryDisplayViewer = new FsTableViewer(parent, SWT.MULTI); - List colDefs = new ArrayList<>(); - colDefs.add(new ColumnDefinition(new FileIconNameLabelProvider(), "Name", 250)); - colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_SIZE), "Size", 100)); - colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_TYPE), "Type", 150)); - colDefs.add(new ColumnDefinition(new NioFileLabelProvider(FsUiConstants.PROPERTY_LAST_MODIFIED), - "Last modified", 400)); - final Table table = directoryDisplayViewer.configureDefaultTable(colDefs); - table.setLayoutData(EclipseUiUtils.fillAll()); - - // table.addKeyListener(new KeyListener() { - // private static final long serialVersionUID = -8083424284436715709L; - // - // @Override - // public void keyReleased(KeyEvent e) { - // } - // - // @Override - // public void keyPressed(KeyEvent e) { - // if (log.isDebugEnabled()) - // log.debug("Key event received: " + e.keyCode); - // IStructuredSelection selection = (IStructuredSelection) - // directoryDisplayViewer.getSelection(); - // Path selected = null; - // if (!selection.isEmpty()) - // selected = ((Path) selection.getFirstElement()); - // if (e.keyCode == SWT.CR) { - // if (!Files.isDirectory(selected)) - // return; - // if (selected != null) { - // currDisplayedFolder = selected; - // directoryDisplayViewer.setInput(currDisplayedFolder, "*"); - // } - // } else if (e.keyCode == SWT.BS) { - // currDisplayedFolder = currDisplayedFolder.getParent(); - // directoryDisplayViewer.setInput(currDisplayedFolder, "*"); - // directoryDisplayViewer.getTable().setFocus(); - // } - // } - // }); - - directoryDisplayViewer.addSelectionChangedListener(new ISelectionChangedListener() { - - @Override - public void selectionChanged(SelectionChangedEvent event) { - IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection(); - Path selected = null; - if (selection.isEmpty()) - setSelected(null); - else - selected = ((Path) selection.getFirstElement()); - if (selected != null) { - // TODO manage multiple selection - setSelected(selected); - } - } - }); - - directoryDisplayViewer.addDoubleClickListener(new IDoubleClickListener() { - @Override - public void doubleClick(DoubleClickEvent event) { - IStructuredSelection selection = (IStructuredSelection) directoryDisplayViewer.getSelection(); - Path selected = null; - if (!selection.isEmpty()) - selected = ((Path) selection.getFirstElement()); - if (selected != null) { - if (!Files.isDirectory(selected)) - return; - setInput(selected); - } - } - }); - - // The context menu - contextMenu = new FsContextMenu(this); - - table.addMouseListener(new MouseAdapter() { - private static final long serialVersionUID = 6737579410648595940L; - - @Override - public void mouseDown(MouseEvent e) { - if (e.button == 3) { - // contextMenu.setCurrFolderPath(currDisplayedFolder); - contextMenu.show(table, new Point(e.x, e.y), currDisplayedFolder); - } - } - }); - } - - private void addPathElementBtn(Path path) { - Button elemBtn = new Button(filterCmp, SWT.PUSH); - String nameStr; - if (path.toString().equals("/")) - nameStr = "[jcr:root]"; - else - nameStr = path.getFileName().toString(); - elemBtn.setText(nameStr + " >> "); - CmsSwtUtils.style(elemBtn, FsStyles.BREAD_CRUMB_BTN); - elemBtn.addSelectionListener(new SelectionAdapter() { - private static final long serialVersionUID = -4103695476023480651L; - - @Override - public void widgetSelected(SelectionEvent e) { - setInput(path); - } - }); - } - - public void setInput(Path path) { - if (path.equals(currDisplayedFolder)) - return; - currDisplayedFolder = path; - - Path diff = initialPath.relativize(currDisplayedFolder); - - for (Control child : filterCmp.getChildren()) - if (!child.equals(filterTxt)) - child.dispose(); - - addPathElementBtn(initialPath); - Path currTarget = initialPath; - if (!diff.toString().equals("")) - for (Path pathElem : diff) { - currTarget = currTarget.resolve(pathElem); - addPathElementBtn(currTarget); - } - - filterTxt.setText(""); - filterTxt.moveBelow(null); - setSelected(null); - filterCmp.getParent().layout(true, true); - } - - private void setSelected(Path path) { - currSelected = path; - setOverviewInput(path); - } - - public Viewer getViewer() { - return directoryDisplayViewer; - } - - private void populateBookmarks(Composite parent) { - CmsSwtUtils.clear(parent); - parent.setLayout(new GridLayout()); - ISelectionChangedListener selList = new BookmarksSelChangeListener(); - - FsTableViewer homeViewer = new FsTableViewer(parent, SWT.SINGLE | SWT.NO_SCROLL); - Table table = homeViewer.configureDefaultSingleColumnTable(bookmarkColWith); - GridData gd = EclipseUiUtils.fillWidth(); - gd.horizontalIndent = 10; - table.setLayoutData(gd); - homeViewer.addSelectionChangedListener(selList); - homeViewer.setPathsInput(getMyFilesPath()); - - appendTitle(parent, "Shared files"); - FsTableViewer groupsViewer = new FsTableViewer(parent, SWT.SINGLE | SWT.NO_SCROLL); - table = groupsViewer.configureDefaultSingleColumnTable(bookmarkColWith); - gd = EclipseUiUtils.fillWidth(); - gd.horizontalIndent = 10; - table.setLayoutData(gd); - groupsViewer.addSelectionChangedListener(selList); - groupsViewer.setPathsInput(getMyGroupsFilesPath()); - - appendTitle(parent, "My bookmarks"); - FsTableViewer bookmarksViewer = new FsTableViewer(parent, SWT.SINGLE | SWT.NO_SCROLL); - table = bookmarksViewer.configureDefaultSingleColumnTable(bookmarkColWith); - gd = EclipseUiUtils.fillWidth(); - gd.horizontalIndent = 10; - table.setLayoutData(gd); - bookmarksViewer.addSelectionChangedListener(selList); - bookmarksViewer.setPathsInput(getMyBookmarks()); - } - - /** - * Recreates the content of the box that displays information about the - * current selected Path. - */ - private void setOverviewInput(Path path) { - try { - EclipseUiUtils.clear(rightPannelCmp); - rightPannelCmp.setLayout(new GridLayout()); - if (path != null) { - // if (isImg(context)) { - // EditableImage image = new Img(parent, RIGHT, context, - // imageWidth); - // image.setLayoutData(new GridData(SWT.CENTER, SWT.CENTER, - // true, false, - // 2, 1)); - // } - - Label contextL = new Label(rightPannelCmp, SWT.NONE); - contextL.setText(path.getFileName().toString()); - contextL.setFont(EclipseUiUtils.getBoldFont(rightPannelCmp)); - addProperty(rightPannelCmp, "Last modified", Files.getLastModifiedTime(path).toString()); - // addProperty(rightPannelCmp, "Owner", - // Files.getOwner(path).getName()); - if (Files.isDirectory(path)) { - addProperty(rightPannelCmp, "Type", "Folder"); - } else { - String mimeType = Files.probeContentType(path); - if (EclipseUiUtils.isEmpty(mimeType)) - mimeType = "Unknown"; - addProperty(rightPannelCmp, "Type", mimeType); - addProperty(rightPannelCmp, "Size", FsUiUtils.humanReadableByteCount(Files.size(path), false)); - } - } - rightPannelCmp.layout(true, true); - } catch (IOException e) { - throw new CmsException("Cannot display details for " + path.toString(), e); - } - } - - private void addFilterPanel(Composite parent) { - RowLayout rl = new RowLayout(SWT.HORIZONTAL); - rl.wrap = true; - parent.setLayout(rl); - // parent.setLayout(EclipseUiUtils.noSpaceGridLayout(new GridLayout(2, - // false))); - - filterTxt = new Text(parent, SWT.SEARCH | SWT.ICON_CANCEL); - filterTxt.setMessage("Search current folder"); - filterTxt.setLayoutData(new RowData(250, SWT.DEFAULT)); - filterTxt.addModifyListener(new ModifyListener() { - private static final long serialVersionUID = 1L; - - public void modifyText(ModifyEvent event) { - modifyFilter(false); - } - }); - filterTxt.addKeyListener(new KeyListener() { - private static final long serialVersionUID = 2533535233583035527L; - - @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; - // FilterEntitiesVirtualTable currTable = null; - // if (currEdited != null) { - // FilterEntitiesVirtualTable table = - // browserCols.get(currEdited); - // if (table != null && !table.isDisposed()) - // currTable = table; - // } - // - // if (e.keyCode == SWT.ARROW_DOWN) - // currTable.setFocus(); - // else if (e.keyCode == SWT.BS) { - // if (filterTxt.getText().equals("") - // && !(currEdited.getNameCount() == 1 || - // currEdited.equals(initialPath))) { - // Path oldEdited = currEdited; - // Path parentPath = currEdited.getParent(); - // setEdited(parentPath); - // if (browserCols.containsKey(parentPath)) - // browserCols.get(parentPath).setSelected(oldEdited); - // filterTxt.setFocus(); - // e.doit = false; - // } - // } else if (e.keyCode == SWT.TAB && !shiftPressed) { - // Path uniqueChild = getOnlyChild(currEdited, - // filterTxt.getText()); - // if (uniqueChild != null) { - // // Highlight the unique chosen child - // currTable.setSelected(uniqueChild); - // setEdited(uniqueChild); - // } - // filterTxt.setFocus(); - // e.doit = false; - // } - } - }); - } - - private Path getOnlyChild(Path parent, String filter) { - try (DirectoryStream stream = Files.newDirectoryStream(currDisplayedFolder, filter + "*")) { - Path uniqueChild = null; - boolean moreThanOne = false; - loop: for (Path entry : stream) { - if (uniqueChild == null) { - uniqueChild = entry; - } else { - moreThanOne = true; - break loop; - } - } - if (!moreThanOne) - return uniqueChild; - return null; - } catch (IOException ioe) { - throw new CmsException( - "Unable to determine unique child existence and get it under " + parent + " with filter " + filter, - ioe); - } - } - - private void modifyFilter(boolean fromOutside) { - if (!fromOutside) - if (currDisplayedFolder != null) { - String filter = filterTxt.getText() + "*"; - directoryDisplayViewer.setInput(currDisplayedFolder, filter); - } - } - - private class BookmarksSelChangeListener implements ISelectionChangedListener { - - @Override - public void selectionChanged(SelectionChangedEvent event) { - IStructuredSelection selection = (IStructuredSelection) event.getSelection(); - if (selection.isEmpty()) - return; - else { - Path newSelected = (Path) selection.getFirstElement(); - if (newSelected.equals(currDisplayedFolder) && newSelected.equals(initialPath)) - return; - initialPath = newSelected; - setInput(newSelected); - } - } - } - - // Simplify UI implementation - private void addProperty(Composite parent, String propName, String value) { - Label contextL = new Label(parent, SWT.NONE); - contextL.setText(propName + ": " + value); - } - - private Label appendTitle(Composite parent, String value) { - Label titleLbl = new Label(parent, SWT.NONE); - titleLbl.setText(value); - titleLbl.setFont(EclipseUiUtils.getBoldFont(parent)); - GridData gd = EclipseUiUtils.fillWidth(); - gd.horizontalIndent = 5; - gd.verticalIndent = 5; - titleLbl.setLayoutData(gd); - return titleLbl; - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FileDrop.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FileDrop.java deleted file mode 100644 index e875b5a3d..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FileDrop.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.argeo.cms.ui.fs; - -import java.io.IOException; -import java.io.InputStream; - -import org.argeo.api.cms.CmsLog; -import org.argeo.eclipse.ui.specific.FileDropAdapter; -import org.eclipse.swt.dnd.DND; -import org.eclipse.swt.dnd.DropTarget; -import org.eclipse.swt.dnd.DropTargetEvent; -import org.eclipse.swt.widgets.Control; - -/** Allows a control to receive file drops. */ -public class FileDrop { - private final static CmsLog log = CmsLog.getLog(FileDrop.class); - - public void createDropTarget(Control control) { - FileDropAdapter fileDropAdapter = new FileDropAdapter() { - @Override - protected void processUpload(InputStream in, String fileName, String contentType) throws IOException { - if (log.isDebugEnabled()) - log.debug("Process upload of " + fileName + " (" + contentType + ")"); - processFileUpload(in, fileName, contentType); - } - }; - DropTarget dropTarget = new DropTarget(control, DND.DROP_MOVE | DND.DROP_COPY); - fileDropAdapter.prepareDropTarget(control, dropTarget); - } - - public void handleFileDrop(Control control, DropTargetEvent event) { - } - - /** Executed in UI thread */ - protected void processFileUpload(InputStream in, String fileName, String contentType) throws IOException { - - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FsContextMenu.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FsContextMenu.java deleted file mode 100644 index c548e2aa0..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FsContextMenu.java +++ /dev/null @@ -1,383 +0,0 @@ -package org.argeo.cms.ui.fs; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.lang.reflect.Method; -import java.net.URI; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import org.apache.commons.io.IOUtils; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.CmsException; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.eclipse.ui.EclipseUiUtils; -import org.argeo.eclipse.ui.dialogs.SingleValue; -import org.eclipse.jface.dialogs.MessageDialog; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.events.ShellEvent; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Button; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.FileDialog; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Shell; - -/** Generic popup context menu to manage NIO Path in a Viewer. */ -public class FsContextMenu extends Shell { - private static final long serialVersionUID = -9120261153509855795L; - - private final static CmsLog log = CmsLog.getLog(FsContextMenu.class); - - // Default known actions - public final static String ACTION_ID_CREATE_FOLDER = "createFolder"; - public final static String ACTION_ID_BOOKMARK_FOLDER = "bookmarkFolder"; - public final static String ACTION_ID_SHARE_FOLDER = "shareFolder"; - public final static String ACTION_ID_DOWNLOAD_FOLDER = "downloadFolder"; - public final static String ACTION_ID_DELETE = "delete"; - public final static String ACTION_ID_UPLOAD_FILE = "uploadFiles"; - public final static String ACTION_ID_OPEN = "open"; - - // Local context - private final CmsFsBrowser browser; - // private final Viewer viewer; - private final static String KEY_ACTION_ID = "actionId"; - private final static String[] DEFAULT_ACTIONS = { ACTION_ID_CREATE_FOLDER, ACTION_ID_BOOKMARK_FOLDER, - ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, ACTION_ID_DELETE, ACTION_ID_UPLOAD_FILE, - ACTION_ID_OPEN }; - private Map actionButtons = new HashMap(); - - private Path currFolderPath; - - public FsContextMenu(CmsFsBrowser browser) { // Viewer viewer, Display - // display) { - super(browser.getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP); - this.browser = browser; - setLayout(EclipseUiUtils.noSpaceGridLayout()); - - Composite boxCmp = new Composite(this, SWT.NO_FOCUS | SWT.BORDER); - boxCmp.setLayout(EclipseUiUtils.noSpaceGridLayout()); - CmsSwtUtils.style(boxCmp, FsStyles.CONTEXT_MENU_BOX); - createContextMenu(boxCmp); - - addShellListener(new ActionsShellListener()); - } - - protected void createContextMenu(Composite boxCmp) { - ActionsSelListener asl = new ActionsSelListener(); - for (String actionId : DEFAULT_ACTIONS) { - Button btn = new Button(boxCmp, SWT.FLAT | SWT.PUSH | SWT.LEAD); - btn.setText(getLabel(actionId)); - btn.setLayoutData(EclipseUiUtils.fillWidth()); - CmsSwtUtils.markup(btn); - CmsSwtUtils.style(btn, actionId + FsStyles.BUTTON_SUFFIX); - btn.setData(KEY_ACTION_ID, actionId); - btn.addSelectionListener(asl); - actionButtons.put(actionId, btn); - } - } - - protected String getLabel(String actionId) { - switch (actionId) { - case ACTION_ID_CREATE_FOLDER: - return "Create Folder"; - case ACTION_ID_BOOKMARK_FOLDER: - return "Bookmark Folder"; - case ACTION_ID_SHARE_FOLDER: - return "Share Folder"; - case ACTION_ID_DOWNLOAD_FOLDER: - return "Download as zip archive"; - case ACTION_ID_DELETE: - return "Delete"; - case ACTION_ID_UPLOAD_FILE: - return "Upload Files"; - case ACTION_ID_OPEN: - return "Open"; - default: - throw new IllegalArgumentException("Unknown action ID " + actionId); - } - } - - protected void aboutToShow(Control source, Point location) { - IStructuredSelection selection = ((IStructuredSelection) browser.getViewer().getSelection()); - boolean emptySel = true; - boolean multiSel = false; - boolean isFolder = true; - if (selection != null && !selection.isEmpty()) { - emptySel = false; - multiSel = selection.size() > 1; - if (!multiSel && selection.getFirstElement() instanceof Path) { - isFolder = Files.isDirectory((Path) selection.getFirstElement()); - } - } - if (emptySel) { - setVisible(true, ACTION_ID_CREATE_FOLDER, ACTION_ID_UPLOAD_FILE); - setVisible(false, ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, ACTION_ID_DELETE, ACTION_ID_OPEN, - // to be implemented - ACTION_ID_BOOKMARK_FOLDER); - } else if (multiSel) { - setVisible(true, ACTION_ID_CREATE_FOLDER, ACTION_ID_UPLOAD_FILE, ACTION_ID_DELETE); - setVisible(false, ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, ACTION_ID_OPEN, - // to be implemented - ACTION_ID_BOOKMARK_FOLDER); - } else if (isFolder) { - setVisible(true, ACTION_ID_CREATE_FOLDER, ACTION_ID_UPLOAD_FILE, ACTION_ID_DELETE); - setVisible(false, ACTION_ID_OPEN, - // to be implemented - ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, ACTION_ID_BOOKMARK_FOLDER); - } else { - setVisible(true, ACTION_ID_CREATE_FOLDER, ACTION_ID_UPLOAD_FILE, ACTION_ID_OPEN, ACTION_ID_DELETE); - setVisible(false, ACTION_ID_SHARE_FOLDER, ACTION_ID_DOWNLOAD_FOLDER, - // to be implemented - ACTION_ID_BOOKMARK_FOLDER); - } - } - - private void setVisible(boolean visible, String... buttonIds) { - for (String id : buttonIds) { - Button button = actionButtons.get(id); - button.setVisible(visible); - GridData gd = (GridData) button.getLayoutData(); - gd.heightHint = visible ? SWT.DEFAULT : 0; - } - } - - public void show(Control source, Point location, Path currFolderPath) { - if (isVisible()) - setVisible(false); - // TODO find a better way to retrieve the parent path (cannot be deduced - // from table content because it will fail on an empty folder) - this.currFolderPath = currFolderPath; - aboutToShow(source, location); - pack(); - layout(); - if (source instanceof Control) - setLocation(((Control) source).toDisplay(location.x, location.y)); - open(); - } - - class StyleButton extends Label { - private static final long serialVersionUID = 7731102609123946115L; - - public StyleButton(Composite parent, int swtStyle) { - super(parent, swtStyle); - } - - } - - // class ActionsMouseListener extends MouseAdapter { - // private static final long serialVersionUID = -1041871937815812149L; - // - // @Override - // public void mouseDown(MouseEvent e) { - // Object eventSource = e.getSource(); - // if (e.button == 1) { - // if (eventSource instanceof Button) { - // Button pressedBtn = (Button) eventSource; - // String actionId = (String) pressedBtn.getData(KEY_ACTION_ID); - // switch (actionId) { - // case ACTION_ID_CREATE_FOLDER: - // createFolder(); - // break; - // case ACTION_ID_DELETE: - // deleteItems(); - // break; - // default: - // throw new IllegalArgumentException("Unimplemented action " + actionId); - // // case ACTION_ID_SHARE_FOLDER: - // // return "Share Folder"; - // // case ACTION_ID_DOWNLOAD_FOLDER: - // // return "Download as zip archive"; - // // case ACTION_ID_UPLOAD_FILE: - // // return "Upload Files"; - // // case ACTION_ID_OPEN: - // // return "Open"; - // } - // } - // } - // viewer.getControl().setFocus(); - // // setVisible(false); - // } - // } - - class ActionsSelListener extends SelectionAdapter { - private static final long serialVersionUID = -1041871937815812149L; - - @Override - public void widgetSelected(SelectionEvent e) { - Object eventSource = e.getSource(); - if (eventSource instanceof Button) { - Button pressedBtn = (Button) eventSource; - String actionId = (String) pressedBtn.getData(KEY_ACTION_ID); - switch (actionId) { - case ACTION_ID_CREATE_FOLDER: - createFolder(); - break; - case ACTION_ID_DELETE: - deleteItems(); - break; - case ACTION_ID_OPEN: - openFile(); - break; - case ACTION_ID_UPLOAD_FILE: - uploadFiles(); - break; - default: - throw new IllegalArgumentException("Unimplemented action " + actionId); - // case ACTION_ID_SHARE_FOLDER: - // return "Share Folder"; - // case ACTION_ID_DOWNLOAD_FOLDER: - // return "Download as zip archive"; - // case ACTION_ID_OPEN: - // return "Open"; - } - } - browser.setFocus(); - // viewer.getControl().setFocus(); - // setVisible(false); - - } - } - - class ActionsShellListener extends org.eclipse.swt.events.ShellAdapter { - private static final long serialVersionUID = -5092341449523150827L; - - @Override - public void shellDeactivated(ShellEvent e) { - setVisible(false); - } - } - - private void openFile() { - log.warn("Implement single sourced, workbench independant \"Open File\" action"); - } - - private void deleteItems() { - IStructuredSelection selection = ((IStructuredSelection) browser.getViewer().getSelection()); - if (selection.isEmpty()) - return; - - StringBuilder builder = new StringBuilder(); - @SuppressWarnings("unchecked") - Iterator iterator = selection.iterator(); - List paths = new ArrayList<>(); - - while (iterator.hasNext()) { - Path path = (Path) iterator.next(); - builder.append(path.getFileName() + ", "); - paths.add(path); - } - String msg = "You are about to delete following elements: " + builder.substring(0, builder.length() - 2) - + ". Are you sure?"; - if (MessageDialog.openConfirm(this, "Confirm deletion", msg)) { - for (Path path : paths) { - try { - // Might have already been deleted if we are in a tree - Files.deleteIfExists(path); - } catch (IOException e) { - throw new CmsException("Cannot delete path " + path, e); - } - } - browser.refresh(); - } - } - - private void createFolder() { - String msg = "Please provide a name."; - String name = SingleValue.ask("Create folder", msg); - // TODO enhance check of name validity - if (EclipseUiUtils.notEmpty(name)) { - try { - Path child = currFolderPath.resolve(name); - if (Files.exists(child)) - throw new CmsException("An item with name " + name + " already exists at " - + currFolderPath.toString() + ", cannot create"); - else - Files.createDirectories(child); - browser.refresh(); - } catch (IOException e) { - throw new CmsException("Cannot create folder " + name + " at " + currFolderPath.toString(), e); - } - } - } - - private void uploadFiles() { - try { - FileDialog dialog = new FileDialog(browser.getShell(), SWT.MULTI); - dialog.setText("Choose one or more files to upload"); - - if (EclipseUiUtils.notEmpty(dialog.open())) { - String[] names = dialog.getFileNames(); - // Workaround small differences between RAP and RCP - // 1. returned names are absolute path on RAP and - // relative in RCP - // 2. in RCP we must use getFilterPath that does not - // exists on RAP - Method filterMethod = null; - Path parPath = null; - try { - filterMethod = dialog.getClass().getDeclaredMethod("getFilterPath"); - String filterPath = (String) filterMethod.invoke(dialog); - parPath = Paths.get(filterPath); - } catch (NoSuchMethodException nsme) { // RAP - } - if (names.length == 0) - return; - else { - loop: for (String name : names) { - Path tmpPath = Paths.get(name); - if (parPath != null) - tmpPath = parPath.resolve(tmpPath); - if (Files.exists(tmpPath)) { - URI uri = tmpPath.toUri(); - String uriStr = uri.toString(); - - if (Files.isDirectory(tmpPath)) { - MessageDialog.openError(browser.getShell(), "Unimplemented directory import", - "Upload of directories in the system is not yet implemented"); - continue loop; - } - Path targetPath = currFolderPath.resolve(tmpPath.getFileName().toString()); - InputStream in = null; - try { - in = new ByteArrayInputStream(Files.readAllBytes(tmpPath)); - Files.copy(in, targetPath); - Files.delete(tmpPath); - } finally { - IOUtils.closeQuietly(in); - } - if (log.isDebugEnabled()) - log.debug("copied uploaded file " + uriStr + " to " + targetPath.toString()); - } else { - String msg = "Cannot copy tmp file from " + tmpPath.toString(); - if (parPath != null) - msg += "\nPlease remember that file upload fails when choosing files from the \"Recently Used\" bookmarks on some OS"; - MessageDialog.openError(browser.getShell(), "Missing file", msg); - continue loop; - } - } - browser.refresh(); - } - } - } catch (Exception e) { - e.printStackTrace(); - MessageDialog.openError(getShell(), "Upload has failed", "Cannot import files to " + currFolderPath); - } - } - - public void setCurrFolderPath(Path currFolderPath) { - this.currFolderPath = currFolderPath; - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FsStyles.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FsStyles.java deleted file mode 100644 index 9ae319282..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/FsStyles.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.argeo.cms.ui.fs; - -/** FS Ui specific CSS styles */ -public interface FsStyles { - String BREAD_CRUMB_BTN = "breadCrumb_btn"; - String CONTEXT_MENU_BOX = "contextMenu_box"; - String BUTTON_SUFFIX = "_btn"; -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/package-info.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/package-info.java deleted file mode 100644 index 6a6c27286..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/fs/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** SWT/JFace file system components. */ -package org.argeo.cms.ui.fs; \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/Activator.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/Activator.java deleted file mode 100644 index e10da3aed..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/Activator.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.argeo.cms.ui.internal; - -import org.argeo.api.cms.CmsState; -import org.osgi.framework.BundleActivator; -import org.osgi.framework.BundleContext; -import org.osgi.util.tracker.ServiceTracker; - -public class Activator implements BundleActivator { - - // avoid dependency to RWT OSGi - private final static String CONTEXT_NAME_PROP = "contextName"; - - private static ServiceTracker nodeState; - - // @Override - public void start(BundleContext bc) throws Exception { - // UI -// bc.registerService(ApplicationConfiguration.class, new MaintenanceUi(), -// LangUtils.dico(CONTEXT_NAME_PROP, "system")); -// bc.registerService(ApplicationConfiguration.class, new UserUi(), LangUtils.dico(CONTEXT_NAME_PROP, "user")); - - nodeState = new ServiceTracker<>(bc, CmsState.class, null); - nodeState.open(); - } - - @Override - public void stop(BundleContext context) throws Exception { - if (nodeState != null) { - nodeState.close(); - nodeState = null; - } - } - - public static CmsState getNodeState() { - return nodeState.getService(); - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrContentProvider.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrContentProvider.java deleted file mode 100644 index 44885b1ca..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrContentProvider.java +++ /dev/null @@ -1,81 +0,0 @@ -package org.argeo.cms.ui.internal; - -import java.util.ArrayList; - -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.RepositoryException; - -import org.argeo.cms.CmsException; -import org.eclipse.jface.viewers.ITreeContentProvider; -import org.eclipse.jface.viewers.Viewer; - -@Deprecated -class JcrContentProvider implements ITreeContentProvider { - private static final long serialVersionUID = -1333678161322488674L; - - @Override - public void dispose() { - } - - @Override - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - if (newInput == null) - return; - if (!(newInput instanceof Node)) - throw new CmsException("Input " + newInput + " must be a node"); - } - - @Override - public Object[] getElements(Object inputElement) { - try { - Node node = (Node) inputElement; - ArrayList arr = new ArrayList(); - NodeIterator nit = node.getNodes(); - while (nit.hasNext()) { - arr.add(nit.nextNode()); - } - return arr.toArray(); - } catch (RepositoryException e) { - throw new CmsException("Cannot get elements", e); - } - } - - @Override - public Object[] getChildren(Object parentElement) { - try { - Node node = (Node) parentElement; - ArrayList arr = new ArrayList(); - NodeIterator nit = node.getNodes(); - while (nit.hasNext()) { - arr.add(nit.nextNode()); - } - return arr.toArray(); - } catch (RepositoryException e) { - throw new CmsException("Cannot get elements", e); - } - } - - @Override - public Object getParent(Object element) { - try { - Node node = (Node) element; - if (node.getName().equals("")) - return null; - else - return node.getParent(); - } catch (RepositoryException e) { - throw new CmsException("Cannot get elements", e); - } - } - - @Override - public boolean hasChildren(Object element) { - try { - Node node = (Node) element; - return node.hasNodes(); - } catch (RepositoryException e) { - throw new CmsException("Cannot get elements", e); - } - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrFileUploadReceiver.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrFileUploadReceiver.java deleted file mode 100644 index c8582f0c1..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/JcrFileUploadReceiver.java +++ /dev/null @@ -1,74 +0,0 @@ -package org.argeo.cms.ui.internal; - -import static javax.jcr.nodetype.NodeType.NT_FILE; - -import java.io.IOException; -import java.io.InputStream; - -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.RepositoryException; -import javax.jcr.nodetype.NodeType; - -import org.apache.commons.io.FilenameUtils; -import org.argeo.api.cms.CmsImageManager; -import org.argeo.cms.ui.widgets.Img; -import org.argeo.jcr.JcrException; -import org.argeo.jcr.JcrUtils; -import org.eclipse.rap.fileupload.FileDetails; -import org.eclipse.rap.fileupload.FileUploadReceiver; - -public class JcrFileUploadReceiver extends FileUploadReceiver { - private Img img; - private final Node parentNode; - private final String nodeName; - private final CmsImageManager imageManager; - - /** If nodeName is null, use the uploaded file name */ - public JcrFileUploadReceiver(Img img, Node parentNode, String nodeName, CmsImageManager imageManager) { - super(); - this.img = img; - this.parentNode = parentNode; - this.nodeName = nodeName; - this.imageManager = imageManager; - } - - @Override - public void receive(InputStream stream, FileDetails details) throws IOException { - try { - String fileName = nodeName != null ? nodeName : details.getFileName(); - String contentType = details.getContentType(); - if (isImage(details.getFileName(), contentType)) { - imageManager.uploadImage(img.getNode(),parentNode, fileName, stream, contentType); - return; - } - - Node fileNode; - if (parentNode.hasNode(fileName)) { - fileNode = parentNode.getNode(fileName); - if (!fileNode.isNodeType(NT_FILE)) - fileNode.remove(); - } - fileNode = JcrUtils.copyStreamAsFile(parentNode, fileName, stream); - - if (contentType != null) { - fileNode.addMixin(NodeType.MIX_MIMETYPE); - fileNode.setProperty(Property.JCR_MIMETYPE, contentType); - } - processNewFile(fileNode); - fileNode.getSession().save(); - } catch (RepositoryException e) { - throw new JcrException("Cannot receive " + details, e); - } - } - - protected Boolean isImage(String fileName, String contentType) { - String ext = FilenameUtils.getExtension(fileName); - return ext != null && (ext.equals("png") || ext.equalsIgnoreCase("jpg")); - } - - protected void processNewFile(Node node) { - - } - -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/SimpleEditableImage.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/SimpleEditableImage.java deleted file mode 100644 index c5c1a01a2..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/internal/SimpleEditableImage.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.argeo.cms.ui.internal; - -import javax.jcr.RepositoryException; - -import org.argeo.api.cms.Cms2DSize; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.util.CmsUiUtils; -import org.argeo.cms.ui.widgets.EditableImage; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Text; - -/** NOT working yet. */ -public class SimpleEditableImage extends EditableImage { - private static final long serialVersionUID = -5689145523114022890L; - - private String src; - private Cms2DSize imageSize; - - public SimpleEditableImage(Composite parent, int swtStyle) { - super(parent, swtStyle); - // load(getControl()); - getParent().layout(); - } - - public SimpleEditableImage(Composite parent, int swtStyle, String src, Cms2DSize imageSize) { - super(parent, swtStyle); - this.src = src; - this.imageSize = imageSize; - } - - @Override - protected Control createControl(Composite box, String style) { - if (isEditing()) { - return createText(box, style); - } else { - return createLabel(box, style); - } - } - - protected String createImgTag() throws RepositoryException { - String imgTag; - if (src != null) - imgTag = CmsUiUtils.img(src, imageSize); - else - imgTag = CmsUiUtils.noImg(imageSize != null ? imageSize : NO_IMAGE_SIZE); - return imgTag; - } - - protected Text createText(Composite box, String style) { - Text text = new Text(box, getStyle()); - CmsSwtUtils.style(text, style); - return text; - } - - public String getSrc() { - return src; - } - - public void setSrc(String src) { - this.src = src; - } - - public Cms2DSize getImageSize() { - return imageSize; - } - - public void setImageSize(Cms2DSize imageSize) { - this.imageSize = imageSize; - } - -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/DefaultRepositoryRegister.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/DefaultRepositoryRegister.java deleted file mode 100644 index 380634118..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/DefaultRepositoryRegister.java +++ /dev/null @@ -1,75 +0,0 @@ -package org.argeo.cms.ui.jcr; - -import java.util.Collections; -import java.util.Map; -import java.util.Observable; -import java.util.TreeMap; - -import javax.jcr.Repository; -import javax.jcr.RepositoryException; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.api.cms.CmsLog; - -public class DefaultRepositoryRegister extends Observable implements RepositoryRegister { - /** Key for a JCR repository alias */ - private final static String CN = CmsConstants.CN; - /** Key for a JCR repository URI */ - // public final static String JCR_REPOSITORY_URI = "argeo.jcr.repository.uri"; - private final static CmsLog log = CmsLog.getLog(DefaultRepositoryRegister.class); - - /** Read only map which will be directly exposed. */ - private Map repositories = Collections.unmodifiableMap(new TreeMap()); - - @SuppressWarnings("rawtypes") - public synchronized Repository getRepository(Map parameters) throws RepositoryException { - if (!parameters.containsKey(CN)) - throw new RepositoryException("Parameter " + CN + " has to be defined."); - String alias = parameters.get(CN).toString(); - if (!repositories.containsKey(alias)) - throw new RepositoryException("No repository registered with alias " + alias); - - return repositories.get(alias); - } - - /** Access to the read-only map */ - public synchronized Map getRepositories() { - return repositories; - } - - /** Registers a service, typically called when OSGi services are bound. */ - @SuppressWarnings("rawtypes") - public synchronized void register(Repository repository, Map properties) { - String alias; - if (properties == null || !properties.containsKey(CN)) { - log.warn("Cannot register a repository if no " + CN + " property is specified."); - return; - } - alias = properties.get(CN).toString(); - Map map = new TreeMap(repositories); - map.put(alias, repository); - repositories = Collections.unmodifiableMap(map); - setChanged(); - notifyObservers(alias); - } - - /** Unregisters a service, typically called when OSGi services are unbound. */ - @SuppressWarnings("rawtypes") - public synchronized void unregister(Repository repository, Map properties) { - // TODO: also check bean name? - if (properties == null || !properties.containsKey(CN)) { - log.warn("Cannot unregister a repository without property " + CN); - return; - } - - String alias = properties.get(CN).toString(); - Map map = new TreeMap(repositories); - if (map.remove(alias) == null) { - log.warn("No repository was registered with alias " + alias); - return; - } - repositories = Collections.unmodifiableMap(map); - setChanged(); - notifyObservers(alias); - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/FullVersioningTreeContentProvider.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/FullVersioningTreeContentProvider.java deleted file mode 100644 index 0f7ee7735..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/FullVersioningTreeContentProvider.java +++ /dev/null @@ -1,98 +0,0 @@ -package org.argeo.cms.ui.jcr; - -import java.util.ArrayList; -import java.util.List; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; -import javax.jcr.nodetype.NodeType; -import javax.jcr.version.Version; -import javax.jcr.version.VersionHistory; -import javax.jcr.version.VersionIterator; -import javax.jcr.version.VersionManager; - -import org.argeo.eclipse.ui.EclipseUiException; -import org.eclipse.jface.viewers.ITreeContentProvider; -import org.eclipse.jface.viewers.Viewer; - -/** - * Display some version information of a JCR full versionable node in a tree - * like structure - */ -public class FullVersioningTreeContentProvider implements ITreeContentProvider { - private static final long serialVersionUID = 8691772509491211112L; - - /** - * Sends back the first level of the Tree. input element must be a single - * node object - */ - public Object[] getElements(Object inputElement) { - try { - Node rootNode = (Node) inputElement; - String curPath = rootNode.getPath(); - VersionManager vm = rootNode.getSession().getWorkspace() - .getVersionManager(); - - VersionHistory vh = vm.getVersionHistory(curPath); - List result = new ArrayList(); - VersionIterator vi = vh.getAllLinearVersions(); - - while (vi.hasNext()) { - result.add(vi.nextVersion()); - } - return result.toArray(); - } catch (RepositoryException re) { - throw new EclipseUiException( - "Unexpected error while getting version elements", re); - } - } - - public Object[] getChildren(Object parentElement) { - try { - if (parentElement instanceof Version) { - List tmp = new ArrayList(); - tmp.add(((Version) parentElement).getFrozenNode()); - return tmp.toArray(); - } - } catch (RepositoryException re) { - throw new EclipseUiException("Unexpected error while getting child " - + "node for version element", re); - } - return null; - } - - public Object getParent(Object element) { - try { - // this will not work in a simpleVersionning environment, parent is - // not a node. - if (element instanceof Node - && ((Node) element).isNodeType(NodeType.NT_FROZEN_NODE)) { - Node node = (Node) element; - return node.getParent(); - } else - return null; - } catch (RepositoryException e) { - return null; - } - } - - public boolean hasChildren(Object element) { - try { - if (element instanceof Version) - return true; - else if (element instanceof Node) - return ((Node) element).hasNodes(); - else - return false; - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot check children of " + element, e); - } - } - - public void dispose() { - } - - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - } - -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrBrowserUtils.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrBrowserUtils.java deleted file mode 100644 index e4c5873a0..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrBrowserUtils.java +++ /dev/null @@ -1,68 +0,0 @@ -package org.argeo.cms.ui.jcr; - -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.PropertyType; -import javax.jcr.RepositoryException; - -import org.argeo.cms.ui.jcr.model.RepositoriesElem; -import org.argeo.cms.ui.jcr.model.RepositoryElem; -import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem; -import org.argeo.cms.ui.jcr.model.WorkspaceElem; -import org.argeo.eclipse.ui.EclipseUiException; -import org.argeo.eclipse.ui.TreeParent; - -/** Useful methods to manage the JCR Browser */ -public class JcrBrowserUtils { - - public static String getPropertyTypeAsString(Property prop) { - try { - return PropertyType.nameFromValue(prop.getType()); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot check type for " + prop, e); - } - } - - /** Insure that the UI component is not stale, refresh if needed */ - public static void forceRefreshIfNeeded(TreeParent element) { - Node curNode = null; - - boolean doRefresh = false; - - try { - if (element instanceof SingleJcrNodeElem) { - curNode = ((SingleJcrNodeElem) element).getNode(); - } else if (element instanceof WorkspaceElem) { - curNode = ((WorkspaceElem) element).getRootNode(); - } - - if (curNode != null && element.getChildren().length != curNode.getNodes().getSize()) - doRefresh = true; - else if (element instanceof RepositoryElem) { - RepositoryElem rn = (RepositoryElem) element; - if (rn.isConnected()) { - String[] wkpNames = rn.getAccessibleWorkspaceNames(); - if (element.getChildren().length != wkpNames.length) - doRefresh = true; - } - } else if (element instanceof RepositoriesElem) { - doRefresh = true; - // Always force refresh for RepositoriesElem : the condition - // below does not take remote repository into account and it is - // not trivial to do so. - - // RepositoriesElem rn = (RepositoriesElem) element; - // if (element.getChildren().length != - // rn.getRepositoryRegister() - // .getRepositories().size()) - // doRefresh = true; - } - if (doRefresh) { - element.clearChildren(); - element.getChildren(); - } - } catch (RepositoryException re) { - throw new EclipseUiException("Unexpected error while synchronising the UI with the JCR repository", re); - } - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrDClickListener.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrDClickListener.java deleted file mode 100644 index 1707681b4..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrDClickListener.java +++ /dev/null @@ -1,60 +0,0 @@ -package org.argeo.cms.ui.jcr; - -import javax.jcr.Node; - -import org.argeo.cms.ui.jcr.model.RepositoryElem; -import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem; -import org.argeo.cms.ui.jcr.model.WorkspaceElem; -import org.eclipse.jface.viewers.DoubleClickEvent; -import org.eclipse.jface.viewers.IDoubleClickListener; -import org.eclipse.jface.viewers.IStructuredSelection; -import org.eclipse.jface.viewers.TreeViewer; - -/** Centralizes the management of double click on a NodeTreeViewer */ -public class JcrDClickListener implements IDoubleClickListener { - // private final static Log log = LogFactory - // .getLog(GenericNodeDoubleClickListener.class); - - private TreeViewer nodeViewer; - - // private JcrFileProvider jfp; - // private FileHandler fileHandler; - - public JcrDClickListener(TreeViewer nodeViewer) { - this.nodeViewer = nodeViewer; - // jfp = new JcrFileProvider(); - // Commented out. see https://www.argeo.org/bugzilla/show_bug.cgi?id=188 - // fileHandler = null; - // fileHandler = new FileHandler(jfp); - } - - public void doubleClick(DoubleClickEvent event) { - if (event.getSelection() == null || event.getSelection().isEmpty()) - return; - Object obj = ((IStructuredSelection) event.getSelection()).getFirstElement(); - if (obj instanceof RepositoryElem) { - RepositoryElem rpNode = (RepositoryElem) obj; - if (rpNode.isConnected()) { - rpNode.logout(); - } else { - rpNode.login(); - } - nodeViewer.refresh(obj); - } else if (obj instanceof WorkspaceElem) { - WorkspaceElem wn = (WorkspaceElem) obj; - if (wn.isConnected()) - wn.logout(); - else - wn.login(); - nodeViewer.refresh(obj); - } else if (obj instanceof SingleJcrNodeElem) { - SingleJcrNodeElem sjn = (SingleJcrNodeElem) obj; - Node node = sjn.getNode(); - openNode(node); - } - } - - protected void openNode(Node node) { - // TODO implement generic behaviour - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrImages.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrImages.java deleted file mode 100644 index d1d1e31ef..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrImages.java +++ /dev/null @@ -1,24 +0,0 @@ -package org.argeo.cms.ui.jcr; - -import org.argeo.cms.ui.theme.CmsImages; -import org.eclipse.swt.graphics.Image; - -/** Shared icons. */ -public class JcrImages { - public final static Image NODE = CmsImages.createIcon("node.gif"); - public final static Image FOLDER = CmsImages.createIcon("folder.gif"); - public final static Image FILE = CmsImages.createIcon("file.gif"); - public final static Image BINARY = CmsImages.createIcon("binary.png"); - public final static Image HOME = CmsImages.createIcon("person-logged-in.png"); - public final static Image SORT = CmsImages.createIcon("sort.gif"); - public final static Image REMOVE = CmsImages.createIcon("remove.gif"); - - public final static Image REPOSITORIES = CmsImages.createIcon("repositories.gif"); - public final static Image REPOSITORY_DISCONNECTED = CmsImages.createIcon("repository_disconnected.gif"); - public final static Image REPOSITORY_CONNECTED = CmsImages.createIcon("repository_connected.gif"); - public final static Image REMOTE_DISCONNECTED = CmsImages.createIcon("remote_disconnected.gif"); - public final static Image REMOTE_CONNECTED = CmsImages.createIcon("remote_connected.gif"); - public final static Image WORKSPACE_DISCONNECTED = CmsImages.createIcon("workspace_disconnected.png"); - public final static Image WORKSPACE_CONNECTED = CmsImages.createIcon("workspace_connected.png"); - -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrTreeContentProvider.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrTreeContentProvider.java deleted file mode 100644 index cc8479f78..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/JcrTreeContentProvider.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.argeo.cms.ui.jcr; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.RepositoryException; - -import org.argeo.eclipse.ui.EclipseUiException; -import org.argeo.eclipse.ui.jcr.util.JcrItemsComparator; -import org.eclipse.jface.viewers.ITreeContentProvider; -import org.eclipse.jface.viewers.Viewer; - -/** - * Implementation of the {@code ITreeContentProvider} in order to display a - * single JCR node and its children in a tree like structure - */ -public class JcrTreeContentProvider implements ITreeContentProvider { - private static final long serialVersionUID = -2128326504754297297L; - // private Node rootNode; - private JcrItemsComparator itemComparator = new JcrItemsComparator(); - - /** - * Sends back the first level of the Tree. input element must be a single node - * object - */ - public Object[] getElements(Object inputElement) { - Node rootNode = (Node) inputElement; - return childrenNodes(rootNode); - } - - public Object[] getChildren(Object parentElement) { - return childrenNodes((Node) parentElement); - } - - public Object getParent(Object element) { - try { - Node node = (Node) element; - if (!node.getPath().equals("/")) - return node.getParent(); - else - return null; - } catch (RepositoryException e) { - return null; - } - } - - public boolean hasChildren(Object element) { - try { - return ((Node) element).hasNodes(); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot check children existence on " + element, e); - } - } - - protected Object[] childrenNodes(Node parentNode) { - try { - List children = new ArrayList(); - NodeIterator nit = parentNode.getNodes(); - while (nit.hasNext()) { - Node node = nit.nextNode(); -// if (node.getName().startsWith("rep:") || node.getName().startsWith("jcr:") -// || node.getName().startsWith("nt:")) -// continue nodes; - children.add(node); - } - Node[] arr = children.toArray(new Node[0]); - Arrays.sort(arr, itemComparator); - return arr; - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot list children of " + parentNode, e); - } - } - - public void dispose() { - } - - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/NodeContentProvider.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/NodeContentProvider.java deleted file mode 100644 index 00449df26..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/NodeContentProvider.java +++ /dev/null @@ -1,175 +0,0 @@ -package org.argeo.cms.ui.jcr; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Comparator; -import java.util.List; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; -import javax.jcr.RepositoryFactory; -import javax.jcr.Session; -import javax.jcr.nodetype.NodeType; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.cms.security.Keyring; -import org.argeo.cms.ui.jcr.model.RepositoriesElem; -import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem; -import org.argeo.eclipse.ui.TreeParent; -import org.eclipse.jface.viewers.ITreeContentProvider; -import org.eclipse.jface.viewers.Viewer; - -/** - * Implementation of the {@code ITreeContentProvider} to display multiple - * repository environment in a tree like structure - */ -public class NodeContentProvider implements ITreeContentProvider { - private static final long serialVersionUID = -4083809398848374403L; - final private RepositoryRegister repositoryRegister; - final private RepositoryFactory repositoryFactory; - - // Current user session on the default workspace of the argeo Node - final private Session userSession; - final private Keyring keyring; - private boolean sortChildren; - - // Reference for cleaning - private SingleJcrNodeElem homeNode = null; - private RepositoriesElem repositoriesNode = null; - - // Utils - private TreeBrowserComparator itemComparator = new TreeBrowserComparator(); - - public NodeContentProvider(Session userSession, Keyring keyring, - RepositoryRegister repositoryRegister, - RepositoryFactory repositoryFactory, Boolean sortChildren) { - this.userSession = userSession; - this.keyring = keyring; - this.repositoryRegister = repositoryRegister; - this.repositoryFactory = repositoryFactory; - this.sortChildren = sortChildren; - } - - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - if (newInput == null)// dispose - return; - - if (userSession != null) { - Node userHome = CmsJcrUtils.getUserHome(userSession); - if (userHome != null) { - // TODO : find a way to dynamically get alias for the node - if (homeNode != null) - homeNode.dispose(); - homeNode = new SingleJcrNodeElem(null, userHome, - userSession.getUserID(), CmsConstants.EGO_REPOSITORY); - } - } - if (repositoryRegister != null) { - if (repositoriesNode != null) - repositoriesNode.dispose(); - repositoriesNode = new RepositoriesElem("Repositories", - repositoryRegister, repositoryFactory, null, userSession, - keyring); - } - } - - /** - * Sends back the first level of the Tree. Independent from inputElement - * that can be null - */ - public Object[] getElements(Object inputElement) { - List objs = new ArrayList(); - if (homeNode != null) - objs.add(homeNode); - if (repositoriesNode != null) - objs.add(repositoriesNode); - return objs.toArray(); - } - - public Object[] getChildren(Object parentElement) { - if (parentElement instanceof TreeParent) { - if (sortChildren) { - Object[] tmpArr = ((TreeParent) parentElement).getChildren(); - if (tmpArr == null) - return new Object[0]; - TreeParent[] arr = new TreeParent[tmpArr.length]; - for (int i = 0; i < tmpArr.length; i++) - arr[i] = (TreeParent) tmpArr[i]; - Arrays.sort(arr, itemComparator); - return arr; - } else - return ((TreeParent) parentElement).getChildren(); - } else - return new Object[0]; - } - - /** - * Sets whether the content provider should order the children nodes or not. - * It is user duty to call a full refresh of the tree after changing this - * parameter. - */ - public void setSortChildren(boolean sortChildren) { - this.sortChildren = sortChildren; - } - - public Object getParent(Object element) { - if (element instanceof TreeParent) { - return ((TreeParent) element).getParent(); - } else - return null; - } - - public boolean hasChildren(Object element) { - if (element instanceof RepositoriesElem) { - RepositoryRegister rr = ((RepositoriesElem) element) - .getRepositoryRegister(); - return rr.getRepositories().size() > 0; - } else if (element instanceof TreeParent) { - TreeParent tp = (TreeParent) element; - return tp.hasChildren(); - } - return false; - } - - public void dispose() { - if (homeNode != null) - homeNode.dispose(); - if (repositoriesNode != null) { - // logs out open sessions - // see https://bugzilla.argeo.org/show_bug.cgi?id=23 - repositoriesNode.dispose(); - } - } - - /** - * Specific comparator for this view. See specification here: - * https://www.argeo.org/bugzilla/show_bug.cgi?id=139 - */ - private class TreeBrowserComparator implements Comparator { - - public int category(TreeParent element) { - if (element instanceof SingleJcrNodeElem) { - Node node = ((SingleJcrNodeElem) element).getNode(); - try { - if (node.isNodeType(NodeType.NT_FOLDER)) - return 5; - } catch (RepositoryException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - return 10; - } - - public int compare(TreeParent o1, TreeParent o2) { - int cat1 = category(o1); - int cat2 = category(o2); - - if (cat1 != cat2) { - return cat1 - cat2; - } - return o1.getName().compareTo(o2.getName()); - } - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/NodeLabelProvider.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/NodeLabelProvider.java deleted file mode 100644 index a5751c083..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/NodeLabelProvider.java +++ /dev/null @@ -1,113 +0,0 @@ -package org.argeo.cms.ui.jcr; - -import javax.jcr.NamespaceException; -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.RepositoryException; -import javax.jcr.nodetype.NodeType; - -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.ui.jcr.model.RemoteRepositoryElem; -import org.argeo.cms.ui.jcr.model.RepositoriesElem; -import org.argeo.cms.ui.jcr.model.RepositoryElem; -import org.argeo.cms.ui.jcr.model.SingleJcrNodeElem; -import org.argeo.cms.ui.jcr.model.WorkspaceElem; -import org.argeo.eclipse.ui.EclipseUiException; -import org.eclipse.jface.viewers.ColumnLabelProvider; -import org.eclipse.swt.graphics.Image; - -/** Provides reasonable defaults for know JCR types. */ -public class NodeLabelProvider extends ColumnLabelProvider { - private static final long serialVersionUID = -3662051696443321843L; - - private final static CmsLog log = CmsLog.getLog(NodeLabelProvider.class); - - public String getText(Object element) { - try { - if (element instanceof SingleJcrNodeElem) { - SingleJcrNodeElem sjn = (SingleJcrNodeElem) element; - return getText(sjn.getNode()); - } else if (element instanceof Node) { - return getText((Node) element); - } else - return super.getText(element); - } catch (RepositoryException e) { - throw new EclipseUiException("Unexpected JCR error while getting node name."); - } - } - - protected String getText(Node node) throws RepositoryException { - String label = node.getName(); - StringBuffer mixins = new StringBuffer(""); - for (NodeType type : node.getMixinNodeTypes()) - mixins.append(' ').append(type.getName()); - - return label + " [" + node.getPrimaryNodeType().getName() + mixins + "]"; - } - - @Override - public Image getImage(Object element) { - if (element instanceof RemoteRepositoryElem) { - if (((RemoteRepositoryElem) element).isConnected()) - return JcrImages.REMOTE_CONNECTED; - else - return JcrImages.REMOTE_DISCONNECTED; - } else if (element instanceof RepositoryElem) { - if (((RepositoryElem) element).isConnected()) - return JcrImages.REPOSITORY_CONNECTED; - else - return JcrImages.REPOSITORY_DISCONNECTED; - } else if (element instanceof WorkspaceElem) { - if (((WorkspaceElem) element).isConnected()) - return JcrImages.WORKSPACE_CONNECTED; - else - return JcrImages.WORKSPACE_DISCONNECTED; - } else if (element instanceof RepositoriesElem) { - return JcrImages.REPOSITORIES; - } else if (element instanceof SingleJcrNodeElem) { - Node nodeElem = ((SingleJcrNodeElem) element).getNode(); - return getImage(nodeElem); - - // if (element instanceof Node) { - // return getImage((Node) element); - // } else if (element instanceof WrappedNode) { - // return getImage(((WrappedNode) element).getNode()); - // } else if (element instanceof NodesWrapper) { - // return getImage(((NodesWrapper) element).getNode()); - // } - } - // try { - // return super.getImage(); - // } catch (RepositoryException e) { - // return null; - // } - return super.getImage(element); - } - - protected Image getImage(Node node) { - try { - if (node.getPrimaryNodeType().isNodeType(NodeType.NT_FILE)) - return JcrImages.FILE; - else if (node.getPrimaryNodeType().isNodeType(NodeType.NT_FOLDER)) - return JcrImages.FOLDER; - else if (node.getPrimaryNodeType().isNodeType(NodeType.NT_RESOURCE)) - return JcrImages.BINARY; - try { - // TODO check workspace type? - if (node.getDepth() == 1 && node.hasProperty(Property.JCR_ID)) - return JcrImages.HOME; - - // optimizes -// if (node.hasProperty(LdapAttrs.uid.property()) && node.isNodeType(NodeTypes.NODE_USER_HOME)) -// return JcrImages.HOME; - } catch (NamespaceException e) { - // node namespace is not registered in this repo - } - return JcrImages.NODE; - } catch (RepositoryException e) { - log.warn("Error while retrieving type for " + node + " in order to display corresponding image"); - e.printStackTrace(); - return null; - } - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/OsgiRepositoryRegister.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/OsgiRepositoryRegister.java deleted file mode 100644 index 444350aeb..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/OsgiRepositoryRegister.java +++ /dev/null @@ -1,52 +0,0 @@ -package org.argeo.cms.ui.jcr; - -import java.util.HashMap; -import java.util.Map; - -import javax.jcr.Repository; - -import org.osgi.framework.BundleContext; -import org.osgi.framework.FrameworkUtil; -import org.osgi.framework.ServiceReference; -import org.osgi.util.tracker.ServiceTracker; - -public class OsgiRepositoryRegister extends DefaultRepositoryRegister { - private final static BundleContext bc = FrameworkUtil.getBundle(OsgiRepositoryRegister.class).getBundleContext(); - private final ServiceTracker repositoryTracker; - - public OsgiRepositoryRegister() { - repositoryTracker = new ServiceTracker(bc, Repository.class, null) { - - @Override - public Repository addingService(ServiceReference reference) { - - Repository repository = super.addingService(reference); - Map props = new HashMap<>(); - for (String key : reference.getPropertyKeys()) { - props.put(key, reference.getProperty(key)); - } - register(repository, props); - return repository; - } - - @Override - public void removedService(ServiceReference reference, Repository service) { - Map props = new HashMap<>(); - for (String key : reference.getPropertyKeys()) { - props.put(key, reference.getProperty(key)); - } - unregister(service, props); - super.removedService(reference, service); - } - - }; - } - - public void init() { - repositoryTracker.open(); - } - - public void destroy() { - repositoryTracker.close(); - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/PropertiesContentProvider.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/PropertiesContentProvider.java deleted file mode 100644 index fd544bbd8..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/PropertiesContentProvider.java +++ /dev/null @@ -1,42 +0,0 @@ -package org.argeo.cms.ui.jcr; - -import java.util.Set; -import java.util.TreeSet; - -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.PropertyIterator; -import javax.jcr.RepositoryException; - -import org.argeo.eclipse.ui.EclipseUiException; -import org.argeo.eclipse.ui.jcr.util.JcrItemsComparator; -import org.eclipse.jface.viewers.IStructuredContentProvider; -import org.eclipse.jface.viewers.Viewer; - -/** Simple content provider that displays all properties of a given Node */ -public class PropertiesContentProvider implements IStructuredContentProvider { - private static final long serialVersionUID = 5227554668841613078L; - private JcrItemsComparator itemComparator = new JcrItemsComparator(); - - public void dispose() { - } - - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - } - - public Object[] getElements(Object inputElement) { - try { - if (inputElement instanceof Node) { - Set props = new TreeSet(itemComparator); - PropertyIterator pit = ((Node) inputElement).getProperties(); - while (pit.hasNext()) - props.add(pit.nextProperty()); - return props.toArray(); - } - return new Object[] {}; - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot get element for " - + inputElement, e); - } - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/PropertyLabelProvider.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/PropertyLabelProvider.java deleted file mode 100644 index 37b90f7ee..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/PropertyLabelProvider.java +++ /dev/null @@ -1,101 +0,0 @@ -package org.argeo.cms.ui.jcr; - -import java.text.DateFormat; -import java.text.SimpleDateFormat; - -import javax.jcr.Property; -import javax.jcr.PropertyType; -import javax.jcr.RepositoryException; -import javax.jcr.Value; - -import org.argeo.cms.ui.CmsUiConstants; -import org.argeo.eclipse.ui.EclipseUiException; -import org.argeo.jcr.JcrUtils; -import org.eclipse.jface.viewers.ColumnLabelProvider; -import org.eclipse.jface.viewers.ViewerCell; - -/** Default basic label provider for a given JCR Node's properties */ -public class PropertyLabelProvider extends ColumnLabelProvider { - private static final long serialVersionUID = -5405794508731390147L; - - // To be able to change column order easily - public static final int COLUMN_PROPERTY = 0; - public static final int COLUMN_VALUE = 1; - public static final int COLUMN_TYPE = 2; - public static final int COLUMN_ATTRIBUTES = 3; - - // Utils - protected DateFormat timeFormatter = new SimpleDateFormat(CmsUiConstants.DATE_TIME_FORMAT); - - public void update(ViewerCell cell) { - Object element = cell.getElement(); - cell.setText(getColumnText(element, cell.getColumnIndex())); - } - - public String getColumnText(Object element, int columnIndex) { - try { - if (element instanceof Property) { - Property prop = (Property) element; - if (prop.isMultiple()) { - switch (columnIndex) { - case COLUMN_PROPERTY: - return prop.getName(); - case COLUMN_VALUE: - // Corresponding values are listed on children - return ""; - case COLUMN_TYPE: - return JcrBrowserUtils.getPropertyTypeAsString(prop); - case COLUMN_ATTRIBUTES: - return JcrUtils.getPropertyDefinitionAsString(prop); - } - } else { - switch (columnIndex) { - case COLUMN_PROPERTY: - return prop.getName(); - case COLUMN_VALUE: - return formatValueAsString(prop.getValue()); - case COLUMN_TYPE: - return JcrBrowserUtils.getPropertyTypeAsString(prop); - case COLUMN_ATTRIBUTES: - return JcrUtils.getPropertyDefinitionAsString(prop); - } - } - } else if (element instanceof Value) { - Value val = (Value) element; - switch (columnIndex) { - case COLUMN_PROPERTY: - // Nothing to show - return ""; - case COLUMN_VALUE: - return formatValueAsString(val); - case COLUMN_TYPE: - // listed on the parent - return ""; - case COLUMN_ATTRIBUTES: - // Corresponding attributes are listed on the parent - return ""; - } - } - } catch (RepositoryException re) { - throw new EclipseUiException("Cannot retrieve prop value on " + element, re); - } - return null; - } - - private String formatValueAsString(Value value) { - // TODO enhance this method - try { - String strValue; - - if (value.getType() == PropertyType.BINARY) - strValue = ""; - else if (value.getType() == PropertyType.DATE) - strValue = timeFormatter.format(value.getDate().getTime()); - else - strValue = value.getString(); - return strValue; - } catch (RepositoryException e) { - throw new EclipseUiException("unexpected error while formatting value", e); - } - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/RepositoryRegister.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/RepositoryRegister.java deleted file mode 100644 index 802c75619..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/RepositoryRegister.java +++ /dev/null @@ -1,16 +0,0 @@ -package org.argeo.cms.ui.jcr; - -import java.util.Map; - -import javax.jcr.Repository; -import javax.jcr.RepositoryFactory; - -/** Allows to register repositories by name. */ -public interface RepositoryRegister extends RepositoryFactory { - /** - * The registered {@link Repository} as a read-only map. Note that this - * method should be called for each access in order to be sure to be up to - * date in case repositories have registered/unregistered - */ - public Map getRepositories(); -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/VersionLabelProvider.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/VersionLabelProvider.java deleted file mode 100644 index 37dfe2b8f..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/VersionLabelProvider.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.argeo.cms.ui.jcr; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; -import javax.jcr.version.Version; - -import org.argeo.eclipse.ui.EclipseUiException; -import org.eclipse.jface.viewers.ColumnLabelProvider; - -/** - * Simple wrapping of the ColumnLabelProvider class to provide text display in - * order to build a tree for version. The getText() method does not assume that - * {@link Version} extends {@link Node} class to respect JCR 2.0 specification - * - */ -public class VersionLabelProvider extends ColumnLabelProvider { - private static final long serialVersionUID = 5270739851193688238L; - - public String getText(Object element) { - try { - if (element instanceof Version) { - Version version = (Version) element; - return version.getName(); - } else if (element instanceof Node) { - return ((Node) element).getName(); - } - } catch (RepositoryException re) { - throw new EclipseUiException( - "Unexpected error while getting element name", re); - } - return super.getText(element); - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/MaintainedRepositoryElem.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/MaintainedRepositoryElem.java deleted file mode 100644 index 61654b61a..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/MaintainedRepositoryElem.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.argeo.cms.ui.jcr.model; - -import javax.jcr.Repository; - -import org.argeo.eclipse.ui.TreeParent; - -/** Wrap a MaintainedRepository */ -public class MaintainedRepositoryElem extends RepositoryElem { - - public MaintainedRepositoryElem(String alias, Repository repository, TreeParent parent) { - super(alias, repository, parent); - // if (!(repository instanceof MaintainedRepository)) { - // throw new ArgeoException("Repository " + alias - // + " is not a maintained repository"); - // } - } - - // protected MaintainedRepository getMaintainedRepository() { - // return (MaintainedRepository) getRepository(); - // } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RemoteRepositoryElem.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RemoteRepositoryElem.java deleted file mode 100644 index 428e7f1cd..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RemoteRepositoryElem.java +++ /dev/null @@ -1,76 +0,0 @@ -package org.argeo.cms.ui.jcr.model; - -import java.util.Arrays; - -import javax.jcr.Node; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.RepositoryFactory; -import javax.jcr.Session; -import javax.jcr.SimpleCredentials; - -import org.argeo.cms.ArgeoNames; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.cms.security.Keyring; -import org.argeo.eclipse.ui.EclipseUiException; -import org.argeo.eclipse.ui.TreeParent; - -/** Root of a remote repository */ -public class RemoteRepositoryElem extends RepositoryElem { - private final Keyring keyring; - /** - * A session of the logged in user on the default workspace of the node - * repository. - */ - private final Session userSession; - private final String remoteNodePath; - - private final RepositoryFactory repositoryFactory; - private final String uri; - - public RemoteRepositoryElem(String alias, RepositoryFactory repositoryFactory, String uri, TreeParent parent, - Session userSession, Keyring keyring, String remoteNodePath) { - super(alias, null, parent); - this.repositoryFactory = repositoryFactory; - this.uri = uri; - this.keyring = keyring; - this.userSession = userSession; - this.remoteNodePath = remoteNodePath; - } - - @Override - protected Session repositoryLogin(String workspaceName) throws RepositoryException { - Node remoteRepository = userSession.getNode(remoteNodePath); - String userID = remoteRepository.getProperty(ArgeoNames.ARGEO_USER_ID).getString(); - if (userID.trim().equals("")) { - return getRepository().login(workspaceName); - } else { - String pwdPath = remoteRepository.getPath() + '/' + ArgeoNames.ARGEO_PASSWORD; - char[] password = keyring.getAsChars(pwdPath); - try { - SimpleCredentials credentials = new SimpleCredentials(userID, password); - return getRepository().login(credentials, workspaceName); - } finally { - Arrays.fill(password, 0, password.length, ' '); - } - } - } - - @Override - public Repository getRepository() { - if (repository == null) - repository = CmsJcrUtils.getRepositoryByUri(repositoryFactory, uri); - return super.getRepository(); - } - - public void remove() { - try { - Node remoteNode = userSession.getNode(remoteNodePath); - remoteNode.remove(); - remoteNode.getSession().save(); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot remove " + remoteNodePath, e); - } - } - -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RepositoriesElem.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RepositoriesElem.java deleted file mode 100644 index 858633202..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RepositoriesElem.java +++ /dev/null @@ -1,112 +0,0 @@ -package org.argeo.cms.ui.jcr.model; - -import java.util.Map; - -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.RepositoryFactory; -import javax.jcr.Session; - -import org.argeo.cms.ArgeoNames; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.cms.security.Keyring; -import org.argeo.cms.ui.jcr.RepositoryRegister; -import org.argeo.eclipse.ui.EclipseUiException; -import org.argeo.eclipse.ui.TreeParent; -import org.argeo.eclipse.ui.dialogs.ErrorFeedback; - -/** - * UI Tree component that implements the Argeo abstraction of a - * {@link RepositoryFactory} that enable a user to "mount" various repositories - * in a single Tree like View. It is usually meant to be at the root of the UI - * Tree and thus {@link getParent()} method will return null. - * - * The {@link RepositoryFactory} is injected at instantiation time and must be - * use get or register new {@link Repository} objects upon which a reference is - * kept here. - */ - -public class RepositoriesElem extends TreeParent implements ArgeoNames { - private final RepositoryRegister repositoryRegister; - private final RepositoryFactory repositoryFactory; - - /** - * A session of the logged in user on the default workspace of the node - * repository. - */ - private final Session userSession; - private final Keyring keyring; - - public RepositoriesElem(String name, RepositoryRegister repositoryRegister, RepositoryFactory repositoryFactory, - TreeParent parent, Session userSession, Keyring keyring) { - super(name); - this.repositoryRegister = repositoryRegister; - this.repositoryFactory = repositoryFactory; - this.userSession = userSession; - this.keyring = keyring; - } - - /** - * Override normal behavior to initialize the various repositories only at - * request time - */ - @Override - public synchronized Object[] getChildren() { - if (isLoaded()) { - return super.getChildren(); - } else { - // initialize current object - Map refRepos = repositoryRegister.getRepositories(); - for (String name : refRepos.keySet()) { - Repository repository = refRepos.get(name); - // if (repository instanceof MaintainedRepository) - // super.addChild(new MaintainedRepositoryElem(name, - // repository, this)); - // else - super.addChild(new RepositoryElem(name, repository, this)); - } - - // remote - if (keyring != null) { - try { - addRemoteRepositories(keyring); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot browse remote repositories", e); - } - } - return super.getChildren(); - } - } - - protected void addRemoteRepositories(Keyring jcrKeyring) throws RepositoryException { - Node userHome = CmsJcrUtils.getUserHome(userSession); - if (userHome != null && userHome.hasNode(ARGEO_REMOTE)) { - NodeIterator it = userHome.getNode(ARGEO_REMOTE).getNodes(); - while (it.hasNext()) { - Node remoteNode = it.nextNode(); - String uri = remoteNode.getProperty(ARGEO_URI).getString(); - try { - RemoteRepositoryElem remoteRepositoryNode = new RemoteRepositoryElem(remoteNode.getName(), - repositoryFactory, uri, this, userSession, jcrKeyring, remoteNode.getPath()); - super.addChild(remoteRepositoryNode); - } catch (Exception e) { - ErrorFeedback.show("Cannot add remote repository " + remoteNode, e); - } - } - } - } - - public void registerNewRepository(String alias, Repository repository) { - // TODO: implement this - // Create a new RepositoryNode Object - // add it - // super.addChild(new RepositoriesNode(...)); - } - - /** Returns the {@link RepositoryRegister} wrapped by this object. */ - public RepositoryRegister getRepositoryRegister() { - return repositoryRegister; - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RepositoryElem.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RepositoryElem.java deleted file mode 100644 index afff3ef9e..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/RepositoryElem.java +++ /dev/null @@ -1,98 +0,0 @@ -package org.argeo.cms.ui.jcr.model; - -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; - -import org.argeo.api.cms.CmsConstants; -import org.argeo.eclipse.ui.EclipseUiException; -import org.argeo.eclipse.ui.TreeParent; -import org.argeo.jcr.JcrUtils; - -/** - * UI Tree component that wraps a JCR {@link Repository}. It also keeps a - * reference to its parent Tree Ui component; typically the unique - * {@link RepositoriesElem} object of the current view to enable bi-directionnal - * browsing in the tree. - */ - -public class RepositoryElem extends TreeParent { - private String alias; - protected Repository repository; - private Session defaultSession = null; - - /** Create a new repository with distinct name and alias */ - public RepositoryElem(String alias, Repository repository, TreeParent parent) { - super(alias); - this.repository = repository; - setParent(parent); - this.alias = alias; - } - - public void login() { - try { - defaultSession = repositoryLogin(CmsConstants.SYS_WORKSPACE); - String[] wkpNames = defaultSession.getWorkspace().getAccessibleWorkspaceNames(); - for (String wkpName : wkpNames) { - if (wkpName.equals(defaultSession.getWorkspace().getName())) - addChild(new WorkspaceElem(this, wkpName, defaultSession)); - else - addChild(new WorkspaceElem(this, wkpName)); - } - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot connect to repository " + alias, e); - } - } - - public synchronized void logout() { - for (Object child : getChildren()) { - if (child instanceof WorkspaceElem) - ((WorkspaceElem) child).logout(); - } - clearChildren(); - JcrUtils.logoutQuietly(defaultSession); - defaultSession = null; - } - - /** - * Actual call to the {@link Repository#login(javax.jcr.Credentials, String)} - * method. To be overridden. - */ - protected Session repositoryLogin(String workspaceName) throws RepositoryException { - return repository.login(workspaceName); - } - - public String[] getAccessibleWorkspaceNames() { - try { - return defaultSession.getWorkspace().getAccessibleWorkspaceNames(); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot retrieve workspace names", e); - } - } - - public void createWorkspace(String workspaceName) { - if (!isConnected()) - login(); - try { - defaultSession.getWorkspace().createWorkspace(workspaceName); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot create workspace", e); - } - } - - /** returns the {@link Repository} referenced by the current UI Node */ - public Repository getRepository() { - return repository; - } - - public String getAlias() { - return alias; - } - - public Boolean isConnected() { - if (defaultSession != null && defaultSession.isLive()) - return true; - else - return false; - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/SingleJcrNodeElem.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/SingleJcrNodeElem.java deleted file mode 100644 index 859deee97..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/SingleJcrNodeElem.java +++ /dev/null @@ -1,84 +0,0 @@ -package org.argeo.cms.ui.jcr.model; - -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.RepositoryException; -import javax.jcr.Workspace; - -import org.argeo.eclipse.ui.EclipseUiException; -import org.argeo.eclipse.ui.TreeParent; - -/** - * UI Tree component. Wraps a node of a JCR {@link Workspace}. It also keeps a - * reference to its parent node that can either be a {@link WorkspaceElem}, a - * {@link SingleJcrNodeElem} or null if the node is "mounted" as the root of the - * UI tree. - */ -public class SingleJcrNodeElem extends TreeParent { - - private final Node node; - private String alias = null; - - /** Creates a new UiNode in the UI Tree */ - public SingleJcrNodeElem(TreeParent parent, Node node, String name) { - super(name); - setParent(parent); - this.node = node; - } - - /** - * Creates a new UiNode in the UI Tree, keeping a reference to the alias of - * the corresponding repository in the current UI environment. It is useful - * to be able to mount nodes as roots of the UI tree. - */ - public SingleJcrNodeElem(TreeParent parent, Node node, String name, String alias) { - super(name); - setParent(parent); - this.node = node; - this.alias = alias; - } - - /** Returns the node wrapped by the current UI object */ - public Node getNode() { - return node; - } - - protected String getRepositoryAlias() { - return alias; - } - - /** - * Overrides normal behaviour to initialise children only when first - * requested - */ - @Override - public synchronized Object[] getChildren() { - if (isLoaded()) { - return super.getChildren(); - } else { - // initialize current object - try { - NodeIterator ni = node.getNodes(); - while (ni.hasNext()) { - Node curNode = ni.nextNode(); - addChild(new SingleJcrNodeElem(this, curNode, curNode.getName())); - } - return super.getChildren(); - } catch (RepositoryException re) { - throw new EclipseUiException("Cannot initialize SingleJcrNode children", re); - } - } - } - - @Override - public boolean hasChildren() { - try { - if (node.getSession().isLive()) - return node.hasNodes(); - else - return false; - } catch (RepositoryException re) { - throw new EclipseUiException("Cannot check children node existence", re); - } - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/WorkspaceElem.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/WorkspaceElem.java deleted file mode 100644 index 24fc5758d..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/WorkspaceElem.java +++ /dev/null @@ -1,117 +0,0 @@ -package org.argeo.cms.ui.jcr.model; - -import javax.jcr.AccessDeniedException; -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -// import javax.jcr.Workspace; -import javax.jcr.Workspace; - -import org.argeo.eclipse.ui.EclipseUiException; -import org.argeo.eclipse.ui.TreeParent; -import org.argeo.jcr.JcrUtils; - -/** - * UI Tree component. Wraps the root node of a JCR {@link Workspace}. It also - * keeps a reference to its parent {@link RepositoryElem}, to be able to - * retrieve alias of the current used repository - */ -public class WorkspaceElem extends TreeParent { - private Session session = null; - - public WorkspaceElem(RepositoryElem parent, String name) { - this(parent, name, null); - } - - public WorkspaceElem(RepositoryElem parent, String name, Session session) { - super(name); - this.session = session; - setParent(parent); - } - - public synchronized Session getSession() { - return session; - } - - public synchronized Node getRootNode() { - try { - if (session != null) - return session.getRootNode(); - else - return null; - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot get root node of workspace " + getName(), e); - } - } - - public synchronized void login() { - try { - session = ((RepositoryElem) getParent()).repositoryLogin(getName()); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot connect to repository " + getName(), e); - } - } - - public Boolean isConnected() { - if (session != null && session.isLive()) - return true; - else - return false; - } - - @Override - public synchronized void dispose() { - logout(); - super.dispose(); - } - - /** Logouts the session, does not nothing if there is no live session. */ - public synchronized void logout() { - clearChildren(); - JcrUtils.logoutQuietly(session); - session = null; - } - - @Override - public synchronized boolean hasChildren() { - try { - if (isConnected()) - try { - return session.getRootNode().hasNodes(); - } catch (AccessDeniedException e) { - // current user may not have access to the root node - return false; - } - else - return false; - } catch (RepositoryException re) { - throw new EclipseUiException("Unexpected error while checking children node existence", re); - } - } - - /** Override normal behaviour to initialize display of the workspace */ - @Override - public synchronized Object[] getChildren() { - if (isLoaded()) { - return super.getChildren(); - } else { - // initialize current object - try { - Node rootNode; - if (session == null) - return null; - else - rootNode = session.getRootNode(); - NodeIterator ni = rootNode.getNodes(); - while (ni.hasNext()) { - Node node = ni.nextNode(); - addChild(new SingleJcrNodeElem(this, node, node.getName())); - } - return super.getChildren(); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot initialize WorkspaceNode UI object." + getName(), e); - } - } - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/package-info.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/package-info.java deleted file mode 100644 index 8f5474449..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/model/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Model for SWT/JFace JCR components. */ -package org.argeo.cms.ui.jcr.model; \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/package-info.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/package-info.java deleted file mode 100644 index 26ae330b5..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/jcr/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** SWT/JFace JCR components. */ -package org.argeo.cms.ui.jcr; \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/package-info.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/package-info.java deleted file mode 100644 index 82fdee796..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** SWT/JFace components for Argeo CMS. */ -package org.argeo.cms.ui; \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsLink.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsLink.java deleted file mode 100644 index 3821e6045..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsLink.java +++ /dev/null @@ -1,282 +0,0 @@ -package org.argeo.cms.ui.util; - -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.api.cms.CmsStyle; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.auth.CurrentUser; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.CmsUiProvider; -import org.argeo.jcr.JcrException; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.rap.rwt.service.ResourceManager; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.MouseListener; -import org.eclipse.swt.graphics.ImageData; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.osgi.framework.BundleContext; - -/** A link to an internal or external location. */ -public class CmsLink implements CmsUiProvider { - private final static CmsLog log = CmsLog.getLog(CmsLink.class); - private BundleContext bundleContext; - - private String label; - private String style; - private String target; - private String image; - private boolean openNew = false; - private MouseListener mouseListener; - - private int horizontalAlignment = SWT.CENTER; - private int verticalAlignment = SWT.CENTER; - - private String loggedInLabel = null; - private String loggedInTarget = null; - - // internal - // private Boolean isUrl = false; - private Integer imageWidth, imageHeight; - - public CmsLink() { - super(); - } - - public CmsLink(String label, String target) { - this(label, target, (String) null); - } - - public CmsLink(String label, String target, CmsStyle style) { - this(label, target, style != null ? style.style() : null); - } - - public CmsLink(String label, String target, String style) { - super(); - this.label = label; - this.target = target; - this.style = style; - init(); - } - - public void init() { - if (image != null) { - ImageData image = loadImage(); - if (imageHeight == null && imageWidth == null) { - imageWidth = image.width; - imageHeight = image.height; - } else if (imageHeight == null) { - imageHeight = (imageWidth * image.height) / image.width; - } else if (imageWidth == null) { - imageWidth = (imageHeight * image.width) / image.height; - } - } - } - - /** @return {@link Composite} with a single {@link Label} child. */ - @Override - public Control createUi(final Composite parent, Node context) { -// if (image != null && (imageWidth == null || imageHeight == null)) { -// throw new CmsException("Image is not properly configured." -// + " Make sure bundleContext property is set and init() method has been called."); -// } - - Composite comp = new Composite(parent, SWT.NONE); - comp.setLayout(CmsSwtUtils.noSpaceGridLayout()); - - Label link = new Label(comp, SWT.NONE); - CmsSwtUtils.markup(link); - GridData layoutData = new GridData(horizontalAlignment, verticalAlignment, false, false); - if (image != null) { - if (imageHeight != null) - layoutData.heightHint = imageHeight; - if (label == null) - if (imageWidth != null) - layoutData.widthHint = imageWidth; - } - - link.setLayoutData(layoutData); - CmsSwtUtils.style(comp, style != null ? style : getDefaultStyle()); - CmsSwtUtils.style(link, style != null ? style : getDefaultStyle()); - - // label - StringBuilder labelText = new StringBuilder(); - if (loggedInTarget != null && isLoggedIn()) { - labelText.append(""); - } else if (target != null) { - labelText.append(""); - } - if (image != null) { - registerImageIfNeeded(); - String imageLocation = RWT.getResourceManager().getLocation(image); - labelText.append(""); - - } - - if (loggedInLabel != null && isLoggedIn()) { - labelText.append(' ').append(loggedInLabel); - } else if (label != null) { - labelText.append(' ').append(label); - } - - if ((loggedInTarget != null && isLoggedIn()) || target != null) - labelText.append(""); - - link.setText(labelText.toString()); - - if (mouseListener != null) - link.addMouseListener(mouseListener); - - return comp; - } - - private void registerImageIfNeeded() { - ResourceManager resourceManager = RWT.getResourceManager(); - if (!resourceManager.isRegistered(image)) { - URL res = getImageUrl(); - try (InputStream inputStream = res.openStream()) { - resourceManager.register(image, inputStream); - if (log.isTraceEnabled()) - log.trace("Registered image " + image); - } catch (IOException e) { - throw new RuntimeException("Cannot load image " + image, e); - } - } - } - - private ImageData loadImage() { - URL url = getImageUrl(); - ImageData result = null; - try (InputStream inputStream = url.openStream()) { - result = new ImageData(inputStream); - if (log.isTraceEnabled()) - log.trace("Loaded image " + image); - } catch (IOException e) { - throw new RuntimeException("Cannot load image " + image, e); - } - return result; - } - - private URL getImageUrl() { - URL url; - try { - // pure URL - url = new URL(image); - } catch (MalformedURLException e1) { - url = bundleContext.getBundle().getResource(image); - } - - if (url == null) - throw new IllegalStateException("No image " + image + " available."); - - return url; - } - - public void setBundleContext(BundleContext bundleContext) { - this.bundleContext = bundleContext; - } - - public void setLabel(String label) { - this.label = label; - } - - public void setStyle(String style) { - this.style = style; - } - - /** @deprecated Use {@link #setStyle(String)} instead. */ - @Deprecated - public void setCustom(String custom) { - this.style = custom; - } - - public void setTarget(String target) { - this.target = target; - // try { - // new URL(target); - // isUrl = true; - // } catch (MalformedURLException e1) { - // isUrl = false; - // } - } - - public void setImage(String image) { - this.image = image; - } - - public void setLoggedInLabel(String loggedInLabel) { - this.loggedInLabel = loggedInLabel; - } - - public void setLoggedInTarget(String loggedInTarget) { - this.loggedInTarget = loggedInTarget; - } - - public void setMouseListener(MouseListener mouseListener) { - this.mouseListener = mouseListener; - } - - public void setvAlign(String vAlign) { - if ("bottom".equals(vAlign)) { - verticalAlignment = SWT.BOTTOM; - } else if ("top".equals(vAlign)) { - verticalAlignment = SWT.TOP; - } else if ("center".equals(vAlign)) { - verticalAlignment = SWT.CENTER; - } else { - throw new IllegalArgumentException( - "Unsupported vertical alignment " + vAlign + " (must be: top, bottom or center)"); - } - } - - protected boolean isLoggedIn() { - return !CurrentUser.isAnonymous(); - } - - public void setImageWidth(Integer imageWidth) { - this.imageWidth = imageWidth; - } - - public void setImageHeight(Integer imageHeight) { - this.imageHeight = imageHeight; - } - - public void setOpenNew(boolean openNew) { - this.openNew = openNew; - } - - protected String getDefaultStyle() { - return SimpleStyle.link.name(); - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsPane.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsPane.java deleted file mode 100644 index fc0c82146..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsPane.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.argeo.cms.ui.util; - -import org.argeo.cms.swt.CmsSwtUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.layout.GridLayout; -import org.eclipse.swt.layout.RowLayout; -import org.eclipse.swt.widgets.Composite; - -/** The main pane of a CMS display, with QA and support areas. */ -public class CmsPane { - - private Composite mainArea; - private Composite qaArea; - private Composite supportArea; - - public CmsPane(Composite parent, int style) { - parent.setLayout(CmsSwtUtils.noSpaceGridLayout()); - -// qaArea = new Composite(parent, SWT.NONE); -// qaArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); -// RowLayout qaLayout = new RowLayout(); -// qaLayout.spacing = 0; -// qaArea.setLayout(qaLayout); - - mainArea = new Composite(parent, SWT.NONE); - mainArea.setLayout(new GridLayout()); - mainArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - -// supportArea = new Composite(parent, SWT.NONE); -// supportArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); -// RowLayout supportLayout = new RowLayout(); -// supportLayout.spacing = 0; -// supportArea.setLayout(supportLayout); - } - - public Composite getMainArea() { - return mainArea; - } - - public Composite getQaArea() { - return qaArea; - } - - public Composite getSupportArea() { - return supportArea; - } - -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsUiUtils.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsUiUtils.java deleted file mode 100644 index 8b384799f..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/CmsUiUtils.java +++ /dev/null @@ -1,220 +0,0 @@ -package org.argeo.cms.ui.util; - -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.StringTokenizer; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; -import javax.servlet.http.HttpServletRequest; - -import org.argeo.api.cms.Cms2DSize; -import org.argeo.api.cms.CmsView; -import org.argeo.api.cms.CmsConstants; -import org.argeo.cms.jcr.CmsJcrUtils; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.CmsUiConstants; -import org.argeo.jcr.JcrUtils; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.rap.rwt.service.ResourceManager; -import org.eclipse.swt.graphics.Image; -import org.eclipse.swt.graphics.ImageData; -import org.eclipse.swt.layout.RowData; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Table; - -/** Static utilities for the CMS framework. */ -public class CmsUiUtils { - // private final static Log log = LogFactory.getLog(CmsUiUtils.class); - - /* - * CMS VIEW - */ - - /** - * The CMS view related to this display, or null if none is available from this - * call. - * - * @deprecated Use {@link CmsSwtUtils#getCmsView(Composite)} instead. - */ - @Deprecated - public static CmsView getCmsView() { -// return UiContext.getData(CmsView.class.getName()); - return CmsSwtUtils.getCmsView(Display.getCurrent().getActiveShell()); - } - - public static StringBuilder getServerBaseUrl(HttpServletRequest request) { - try { - URL url = new URL(request.getRequestURL().toString()); - StringBuilder buf = new StringBuilder(); - buf.append(url.getProtocol()).append("://").append(url.getHost()); - if (url.getPort() != -1) - buf.append(':').append(url.getPort()); - return buf; - } catch (MalformedURLException e) { - throw new IllegalArgumentException("Cannot extract server base URL from " + request.getRequestURL(), e); - } - } - - // - public static String getDataUrl(Node node, HttpServletRequest request) { - try { - StringBuilder buf = getServerBaseUrl(request); - buf.append(getDataPath(node)); - return new URL(buf.toString()).toString(); - } catch (MalformedURLException e) { - throw new IllegalArgumentException("Cannot build data URL for " + node, e); - } - } - - /** A path in the node repository */ - public static String getDataPath(Node node) { - return getDataPath(CmsConstants.EGO_REPOSITORY, node); - } - - public static String getDataPath(String cn, Node node) { - return CmsJcrUtils.getDataPath(cn, node); - } - - /** Clean reserved URL characters for use in HTTP links. */ - public static String getDataPathForUrl(Node node) { - return cleanPathForUrl(getDataPath(node)); - } - - /** Clean reserved URL characters for use in HTTP links. */ - public static String cleanPathForUrl(String path) { - StringTokenizer st = new StringTokenizer(path, "/"); - StringBuilder sb = new StringBuilder(); - while (st.hasMoreElements()) { - sb.append('/'); - String encoded = URLEncoder.encode(st.nextToken(), StandardCharsets.UTF_8); - encoded = encoded.replace("+", "%20"); - sb.append(encoded); - - } - return sb.toString(); - } - - /** @deprecated Use rowData16px() instead. GridData should not be reused. */ - @Deprecated - public static RowData ROW_DATA_16px = new RowData(16, 16); - - - - /* - * FORM LAYOUT - */ - - - - @Deprecated - public static void setItemHeight(Table table, int height) { - table.setData(CmsUiConstants.ITEM_HEIGHT, height); - } - - // - // JCR - // - public static Node getOrAddEmptyFile(Node parent, Enum child) throws RepositoryException { - if (has(parent, child)) - return child(parent, child); - return JcrUtils.copyBytesAsFile(parent, child.name(), new byte[0]); - } - - public static Node child(Node parent, Enum en) throws RepositoryException { - return parent.getNode(en.name()); - } - - public static Boolean has(Node parent, Enum en) throws RepositoryException { - return parent.hasNode(en.name()); - } - - public static Node getOrAdd(Node parent, Enum en) throws RepositoryException { - return getOrAdd(parent, en, null); - } - - public static Node getOrAdd(Node parent, Enum en, String primaryType) throws RepositoryException { - if (has(parent, en)) - return child(parent, en); - else if (primaryType == null) - return parent.addNode(en.name()); - else - return parent.addNode(en.name(), primaryType); - } - - // IMAGES - - public static String img(Node fileNode, String width, String height) { - return img(null, fileNode, width, height); - } - - public static String img(String serverBase, Node fileNode, String width, String height) { -// String src = (serverBase != null ? serverBase : "") + NodeUtils.getDataPath(fileNode); - String src; - src = (serverBase != null ? serverBase : "") + getDataPathForUrl(fileNode); - return imgBuilder(src, width, height).append("/>").toString(); - } - - public static String img(String src, String width, String height) { - return imgBuilder(src, width, height).append("/>").toString(); - } - - public static String img(String src, Cms2DSize size) { - return img(src, Integer.toString(size.getWidth()), Integer.toString(size.getHeight())); - } - - public static StringBuilder imgBuilder(String src, String width, String height) { - return new StringBuilder(64).append(" { - private final static CmsLog log = CmsLog.getLog(DefaultImageManager.class); -// private MimetypesFileTypeMap fileTypeMap = new MimetypesFileTypeMap(); - - public Boolean load(Node node, Control control, Cms2DSize preferredSize) { - Cms2DSize imageSize = getImageSize(node); - Cms2DSize size; - String imgTag = null; - if (preferredSize == null || imageSize.getWidth() == 0 || imageSize.getHeight() == 0 - || (preferredSize.getWidth() == 0 && preferredSize.getHeight() == 0)) { - if (imageSize.getWidth() != 0 && imageSize.getHeight() != 0) { - // actual image size if completely known - size = imageSize; - } else { - // no image if not completely known - size = resizeTo(NO_IMAGE_SIZE, preferredSize != null ? preferredSize : imageSize); - imgTag = CmsUiUtils.noImg(size); - } - - } else if (preferredSize.getWidth() != 0 && preferredSize.getHeight() != 0) { - // given size if completely provided - size = preferredSize; - } else { - // at this stage : - // image is completely known - assert imageSize.getWidth() != 0 && imageSize.getHeight() != 0; - // one and only one of the dimension as been specified - assert preferredSize.getWidth() == 0 || preferredSize.getHeight() == 0; - size = resizeTo(imageSize, preferredSize); - } - - boolean loaded = false; - if (control == null) - return loaded; - - if (control instanceof Label) { - if (imgTag == null) { - // IMAGE RETRIEVED HERE - imgTag = getImageTag(node, size); - // - if (imgTag == null) - imgTag = CmsUiUtils.noImg(size); - else - loaded = true; - } - - Label lbl = (Label) control; - lbl.setText(imgTag); - // lbl.setSize(size); - } else if (control instanceof FileUpload) { - FileUpload lbl = (FileUpload) control; - lbl.setImage(CmsUiUtils.noImage(size)); - lbl.setSize(new Point(size.getWidth(), size.getHeight())); - return loaded; - } else - loaded = false; - - return loaded; - } - - private Cms2DSize resizeTo(Cms2DSize orig, Cms2DSize constraints) { - if (constraints.getWidth() != 0 && constraints.getHeight() != 0) { - return constraints; - } else if (constraints.getWidth() == 0 && constraints.getHeight() == 0) { - return orig; - } else if (constraints.getHeight() == 0) {// force width - return new Cms2DSize(constraints.getWidth(), - scale(orig.getHeight(), orig.getWidth(), constraints.getWidth())); - } else if (constraints.getWidth() == 0) {// force height - return new Cms2DSize(scale(orig.getWidth(), orig.getHeight(), constraints.getHeight()), - constraints.getHeight()); - } - throw new IllegalArgumentException("Cannot resize " + orig + " to " + constraints); - } - - private int scale(int origDimension, int otherDimension, int otherConstraint) { - return Math.round(origDimension * divide(otherConstraint, otherDimension)); - } - - private float divide(int a, int b) { - return ((float) a) / ((float) b); - } - - public Cms2DSize getImageSize(Node node) { - // TODO optimise - Image image = getSwtImage(node); - return new Cms2DSize(image.getBounds().width, image.getBounds().height); - } - - /** @return null if not available */ - @Override - public String getImageTag(Node node) { - return getImageTag(node, getImageSize(node)); - } - - private String getImageTag(Node node, Cms2DSize size) { - StringBuilder buf = getImageTagBuilder(node, size); - if (buf == null) - return null; - return buf.append("/>").toString(); - } - - /** @return null if not available */ - @Override - public StringBuilder getImageTagBuilder(Node node, Cms2DSize size) { - return getImageTagBuilder(node, Integer.toString(size.getWidth()), Integer.toString(size.getHeight())); - } - - /** @return null if not available */ - private StringBuilder getImageTagBuilder(Node node, String width, String height) { - String url = getImageUrl(node); - if (url == null) - return null; - return CmsUiUtils.imgBuilder(url, width, height); - } - - /** @return null if not available */ - @Override - public String getImageUrl(Node node) { - return CmsUiUtils.getDataPathForUrl(node); - } - - protected String getResourceName(Node node) { - try { - String workspace = node.getSession().getWorkspace().getName(); - if (node.hasNode(JCR_CONTENT)) - return workspace + '_' + node.getNode(JCR_CONTENT).getIdentifier(); - else - return workspace + '_' + node.getIdentifier(); - } catch (RepositoryException e) { - throw new JcrException(e); - } - } - - public Binary getImageBinary(Node node) { - try { - if (node.isNodeType(NT_FILE)) { - return node.getNode(JCR_CONTENT).getProperty(JCR_DATA).getBinary(); - } else { - return null; - } - } catch (RepositoryException e) { - throw new JcrException(e); - } - } - - public Image getSwtImage(Node node) { - InputStream inputStream = null; - Binary binary = getImageBinary(node); - if (binary == null) - return null; - try { - inputStream = binary.getStream(); - return new Image(Display.getCurrent(), inputStream); - } catch (RepositoryException e) { - throw new JcrException(e); - } finally { - IOUtils.closeQuietly(inputStream); - JcrUtils.closeQuietly(binary); - } - } - - @Override - public String uploadImage(Node context, Node parentNode, String fileName, InputStream in, String contentType) { - InputStream inputStream = null; - try { - String previousResourceName = null; - if (parentNode.hasNode(fileName)) { - Node node = parentNode.getNode(fileName); - previousResourceName = getResourceName(node); - if (node.hasNode(JCR_CONTENT)) { - node.getNode(JCR_CONTENT).remove(); - node.addNode(JCR_CONTENT, NT_RESOURCE); - } - } - - byte[] arr = IOUtils.toByteArray(in); - Node fileNode = JcrUtils.copyBytesAsFile(parentNode, fileName, arr); - inputStream = new ByteArrayInputStream(arr); - ImageData id = new ImageData(inputStream); - processNewImageFile(context, fileNode, id); - - String mime = contentType != null ? contentType : Files.probeContentType(Paths.get(fileName)); - if (mime != null) { - fileNode.getNode(JCR_CONTENT).setProperty(Property.JCR_MIMETYPE, mime); - } - fileNode.getSession().save(); - - // reset resource manager - ResourceManager resourceManager = RWT.getResourceManager(); - if (previousResourceName != null && resourceManager.isRegistered(previousResourceName)) { - resourceManager.unregister(previousResourceName); - if (log.isDebugEnabled()) - log.debug("Unregistered image " + previousResourceName); - } - return CmsUiUtils.getDataPath(fileNode); - } catch (IOException e) { - throw new RuntimeException("Cannot upload image " + fileName + " in " + parentNode, e); - } catch (RepositoryException e) { - throw new JcrException(e); - } finally { - IOUtils.closeQuietly(inputStream); - } - } - - /** Does nothing by default. */ - protected void processNewImageFile(Node context, Node fileNode, ImageData id) - throws RepositoryException, IOException { - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/MenuLink.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/MenuLink.java deleted file mode 100644 index 284d2bd0c..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/MenuLink.java +++ /dev/null @@ -1,22 +0,0 @@ -package org.argeo.cms.ui.util; - -import org.argeo.cms.swt.CmsStyles; - -/** - * Convenience class setting the custom style {@link CmsStyles#CMS_MENU_LINK} on - * a {@link CmsLink} when simple menus are used. - */ -public class MenuLink extends CmsLink { - public MenuLink() { - setCustom(CmsStyles.CMS_MENU_LINK); - } - - public MenuLink(String label, String target, String custom) { - super(label, target, custom); - } - - public MenuLink(String label, String target) { - super(label, target, CmsStyles.CMS_MENU_LINK); - } - -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleCmsHeader.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleCmsHeader.java deleted file mode 100644 index e8bf66297..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleCmsHeader.java +++ /dev/null @@ -1,97 +0,0 @@ -package org.argeo.cms.ui.util; - -import java.util.ArrayList; -import java.util.List; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.cms.CmsException; -import org.argeo.cms.swt.CmsStyles; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.CmsUiProvider; -import org.eclipse.rap.rwt.RWT; -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; - -/** A header in three parts */ -public class SimpleCmsHeader implements CmsUiProvider { - private List lead = new ArrayList(); - private List center = new ArrayList(); - private List end = new ArrayList(); - - private Boolean subPartsSameWidth = false; - - @Override - public Control createUi(Composite parent, Node context) throws RepositoryException { - Composite header = new Composite(parent, SWT.NONE); - header.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_HEADER); - header.setBackgroundMode(SWT.INHERIT_DEFAULT); - header.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(3, false))); - - configurePart(context, header, lead); - configurePart(context, header, center); - configurePart(context, header, end); - return header; - } - - protected void configurePart(Node context, Composite parent, List partProviders) - throws RepositoryException { - final int style; - final String custom; - if (lead == partProviders) { - style = SWT.LEAD; - custom = CmsStyles.CMS_HEADER_LEAD; - } else if (center == partProviders) { - style = SWT.CENTER; - custom = CmsStyles.CMS_HEADER_CENTER; - } else if (end == partProviders) { - style = SWT.END; - custom = CmsStyles.CMS_HEADER_END; - } else { - throw new CmsException("Unsupported part providers " + partProviders); - } - - Composite part = new Composite(parent, SWT.NONE); - part.setData(RWT.CUSTOM_VARIANT, custom); - GridData gridData = new GridData(style, SWT.FILL, true, true); - part.setLayoutData(gridData); - part.setLayout(CmsSwtUtils.noSpaceGridLayout(new GridLayout(partProviders.size(), subPartsSameWidth))); - for (CmsUiProvider uiProvider : partProviders) { - Control subPart = uiProvider.createUi(part, context); - subPart.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); - } - } - - public void setLead(List lead) { - this.lead = lead; - } - - public void setCenter(List center) { - this.center = center; - } - - public void setEnd(List end) { - this.end = end; - } - - public void setSubPartsSameWidth(Boolean subPartsSameWidth) { - this.subPartsSameWidth = subPartsSameWidth; - } - - public List getLead() { - return lead; - } - - public List getCenter() { - return center; - } - - public List getEnd() { - return end; - } - -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleDynamicPages.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleDynamicPages.java deleted file mode 100644 index 8e0e7c179..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleDynamicPages.java +++ /dev/null @@ -1,118 +0,0 @@ -package org.argeo.cms.ui.util; - -import java.text.DateFormat; -import java.text.SimpleDateFormat; - -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.Property; -import javax.jcr.PropertyIterator; -import javax.jcr.PropertyType; -import javax.jcr.RepositoryException; -import javax.jcr.Value; - -import org.argeo.cms.CmsException; -import org.argeo.cms.ui.CmsUiProvider; -import org.argeo.jcr.JcrUtils; -import org.eclipse.rap.rwt.RWT; -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; - -public class SimpleDynamicPages implements CmsUiProvider { - - @Override - public Control createUi(Composite parent, Node context) - throws RepositoryException { - if (context == null) - throw new CmsException("Context cannot be null"); - parent.setLayout(new GridLayout(2, false)); - - // parent - if (!context.getPath().equals("/")) { - new CmsLink("..", context.getParent().getPath()).createUi(parent, - context); - new Label(parent, SWT.NONE).setText(context.getParent() - .getPrimaryNodeType().getName()); - } - - // context - Label contextL = new Label(parent, SWT.NONE); - contextL.setData(RWT.MARKUP_ENABLED, true); - contextL.setText("" + context.getName() + ""); - new Label(parent, SWT.NONE).setText(context.getPrimaryNodeType() - .getName()); - - // children - // Label childrenL = new Label(parent, SWT.NONE); - // childrenL.setData(RWT.MARKUP_ENABLED, true); - // childrenL.setText("Children:"); - // childrenL.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, - // false, 2, 1)); - - for (NodeIterator nIt = context.getNodes(); nIt.hasNext();) { - Node child = nIt.nextNode(); - new CmsLink(child.getName(), child.getPath()).createUi(parent, - context); - - new Label(parent, SWT.NONE).setText(child.getPrimaryNodeType() - .getName()); - } - - // properties - // Label propsL = new Label(parent, SWT.NONE); - // propsL.setData(RWT.MARKUP_ENABLED, true); - // propsL.setText("Properties:"); - // propsL.setLayoutData(new GridData(SWT.LEAD, SWT.CENTER, false, false, - // 2, 1)); - for (PropertyIterator pIt = context.getProperties(); pIt.hasNext();) { - Property property = pIt.nextProperty(); - - Label label = new Label(parent, SWT.NONE); - label.setText(property.getName()); - label.setToolTipText(JcrUtils - .getPropertyDefinitionAsString(property)); - - new Label(parent, SWT.NONE).setText(getPropAsString(property)); - } - - return null; - } - - private String getPropAsString(Property property) - throws RepositoryException { - String result = ""; - DateFormat timeFormatter = new SimpleDateFormat(""); - if (property.isMultiple()) { - result = getMultiAsString(property, ", "); - } else { - Value value = property.getValue(); - if (value.getType() == PropertyType.BINARY) - result = ""; - else if (value.getType() == PropertyType.DATE) - result = timeFormatter.format(value.getDate().getTime()); - else - result = value.getString(); - } - return result; - } - - private String getMultiAsString(Property property, String separator) - throws RepositoryException { - if (separator == null) - separator = "; "; - Value[] values = property.getValues(); - StringBuilder builder = new StringBuilder(); - for (Value val : values) { - String currStr = val.getString(); - if (!"".equals(currStr.trim())) - builder.append(currStr).append(separator); - } - if (builder.lastIndexOf(separator) >= 0) - return builder.substring(0, builder.length() - separator.length()); - else - return builder.toString(); - } -} \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleImageManager.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleImageManager.java deleted file mode 100644 index ac09b2a02..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleImageManager.java +++ /dev/null @@ -1,5 +0,0 @@ -package org.argeo.cms.ui.util; - -public class SimpleImageManager extends DefaultImageManager { - -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStaticPage.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStaticPage.java deleted file mode 100644 index 63e504b7a..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStaticPage.java +++ /dev/null @@ -1,32 +0,0 @@ -package org.argeo.cms.ui.util; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.cms.swt.CmsStyles; -import org.argeo.cms.ui.CmsUiProvider; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; - -public class SimpleStaticPage implements CmsUiProvider { - private String text; - - @Override - public Control createUi(Composite parent, Node context) - throws RepositoryException { - Label textC = new Label(parent, SWT.WRAP); - textC.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_STATIC_TEXT); - textC.setData(RWT.MARKUP_ENABLED, Boolean.TRUE); - textC.setText(text); - - return textC; - } - - public void setText(String text) { - this.text = text; - } - -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStyle.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStyle.java deleted file mode 100644 index 8ed06a292..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SimpleStyle.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.argeo.cms.ui.util; - -import org.argeo.api.cms.CmsStyle; - -/** Simple styles used by the CMS UI utilities. */ -public enum SimpleStyle implements CmsStyle { - link; -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/StyleSheetResourceLoader.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/StyleSheetResourceLoader.java deleted file mode 100644 index ad1523c1a..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/StyleSheetResourceLoader.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.argeo.cms.ui.util; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.util.LinkedHashMap; -import java.util.Map; - -import org.apache.commons.io.IOUtils; -import org.argeo.cms.CmsException; -import org.eclipse.rap.rwt.service.ResourceLoader; -import org.osgi.framework.Bundle; - -/** {@link ResourceLoader} caching stylesheets. */ -public class StyleSheetResourceLoader implements ResourceLoader { - private Bundle themeBundle; - private Map stylesheets = new LinkedHashMap(); - - public StyleSheetResourceLoader(Bundle themeBundle) { - this.themeBundle = themeBundle; - } - - @Override - public InputStream getResourceAsStream(String resourceName) throws IOException { - if (!stylesheets.containsKey(resourceName)) { - // TODO deal with other bundles - // Bundle bundle = bundleContext.getBundle(); - // String location = - // bundle.getLocation().substring("initial@reference:".length()); - // if (location.startsWith("file:")) { - // Path path = null; - // try { - // path = Paths.get(new URI(location)); - // } catch (URISyntaxException e) { - // e.printStackTrace(); - // } - // if (path != null) { - // Path resourcePath = path.resolve(resourceName); - // if (Files.exists(resourcePath)) - // return Files.newInputStream(resourcePath); - // } - // } - - URL res = themeBundle.getEntry(resourceName); - if (res == null) - throw new CmsException( - "Entry " + resourceName + " not found in bundle " + themeBundle.getSymbolicName()); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - IOUtils.copy(res.openStream(), out); - stylesheets.put(resourceName, new StyleSheet(out.toByteArray())); - } - return new ByteArrayInputStream(stylesheets.get(resourceName).getData()); - // return res.openStream(); - } - - private class StyleSheet { - private byte[] data; - - public StyleSheet(byte[] data) { - super(); - this.data = data; - } - - public byte[] getData() { - return data; - } - - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SystemNotifications.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SystemNotifications.java deleted file mode 100644 index 156a6082f..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/SystemNotifications.java +++ /dev/null @@ -1,129 +0,0 @@ -package org.argeo.cms.ui.util; - -import java.io.PrintWriter; -import java.io.StringWriter; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; -import java.text.SimpleDateFormat; -import java.util.Date; - -import org.apache.commons.io.IOUtils; -import org.argeo.cms.CmsException; -import org.argeo.cms.swt.CmsStyles; -import org.argeo.cms.swt.CmsSwtUtils; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.events.MouseListener; -import org.eclipse.swt.events.ShellAdapter; -import org.eclipse.swt.events.ShellEvent; -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.Label; -import org.eclipse.swt.widgets.Shell; - -/** Shell displaying system notifications such as exceptions */ -public class SystemNotifications extends Shell implements CmsStyles, - MouseListener { - private static final long serialVersionUID = -8129377525216022683L; - - private Control source; - - public SystemNotifications(Control source) { - super(source.getDisplay(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP); - setData(RWT.CUSTOM_VARIANT, CMS_USER_MENU); - - this.source = source; - - // TODO UI - // setLocation(source.toDisplay(source.getSize().x - getSize().x, - // source.getSize().y)); - setLayout(new GridLayout()); - addMouseListener(this); - - addShellListener(new ShellAdapter() { - private static final long serialVersionUID = 5178980294808435833L; - - @Override - public void shellDeactivated(ShellEvent e) { - close(); - dispose(); - } - }); - - } - - public void notifyException(Throwable exception) { - Composite pane = this; - - Label lbl = new Label(pane, SWT.NONE); - lbl.setText(exception.getLocalizedMessage() - + (exception instanceof CmsException ? "" : "(" - + exception.getClass().getName() + ")") + "\n"); - lbl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); - lbl.addMouseListener(this); - if (exception.getCause() != null) - appendCause(pane, exception.getCause()); - - StringBuilder mailToUrl = new StringBuilder("mailto:?"); - try { - mailToUrl.append("subject=").append( - URLEncoder.encode( - "Exception " - + new SimpleDateFormat("yyyy-MM-dd hh:mm") - .format(new Date()), "UTF-8") - .replace("+", "%20")); - - StringWriter sw = new StringWriter(); - exception.printStackTrace(new PrintWriter(sw)); - IOUtils.closeQuietly(sw); - - // see - // http://stackoverflow.com/questions/4737841/urlencoder-not-able-to-translate-space-character - String encoded = URLEncoder.encode(sw.toString(), "UTF-8").replace( - "+", "%20"); - mailToUrl.append("&body=").append(encoded); - } catch (UnsupportedEncodingException e) { - mailToUrl.append("&body=").append("Could not encode: ") - .append(e.getMessage()); - } - Label mailTo = new Label(pane, SWT.NONE); - CmsSwtUtils.markup(mailTo); - mailTo.setText("Send details"); - mailTo.setLayoutData(new GridData(SWT.END, SWT.FILL, true, false)); - - pack(); - layout(); - - setLocation(source.toDisplay(source.getSize().x - getSize().x, - source.getSize().y - getSize().y)); - open(); - } - - private void appendCause(Composite parent, Throwable e) { - Label lbl = new Label(parent, SWT.NONE); - lbl.setText(" caused by: " + e.getLocalizedMessage() + " (" - + e.getClass().getName() + ")" + "\n"); - lbl.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, false)); - lbl.addMouseListener(this); - if (e.getCause() != null) - appendCause(parent, e.getCause()); - } - - @Override - public void mouseDoubleClick(MouseEvent e) { - } - - @Override - public void mouseDown(MouseEvent e) { - close(); - dispose(); - } - - @Override - public void mouseUp(MouseEvent e) { - } - -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/UserMenu.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/UserMenu.java deleted file mode 100644 index 316cb51c0..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/UserMenu.java +++ /dev/null @@ -1,55 +0,0 @@ -package org.argeo.cms.ui.util; - -import javax.jcr.Node; - -import org.argeo.cms.CmsException; -import org.argeo.cms.swt.auth.CmsLoginShell; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.ShellAdapter; -import org.eclipse.swt.events.ShellEvent; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Shell; - -/** The site-related user menu */ -public class UserMenu extends CmsLoginShell { - private final Control source; - private final Node context; - - public UserMenu(Control source, Node context) { - super(CmsUiUtils.getCmsView()); - this.context = context; - createUi(); - if (source == null) - throw new CmsException("Source control cannot be null."); - this.source = source; - open(); - } - - @Override - protected Shell createShell() { - return new Shell(Display.getCurrent(), SWT.NO_TRIM | SWT.BORDER | SWT.ON_TOP); - } - - @Override - public void open() { - Shell shell = getShell(); - shell.pack(); - shell.layout(); - shell.setLocation(source.toDisplay(source.getSize().x - shell.getSize().x, source.getSize().y)); - shell.addShellListener(new ShellAdapter() { - private static final long serialVersionUID = 5178980294808435833L; - - @Override - public void shellDeactivated(ShellEvent e) { - closeShell(); - } - }); - super.open(); - } - - protected Node getContext() { - return context; - } - -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/UserMenuLink.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/UserMenuLink.java deleted file mode 100644 index 317a7b55b..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/UserMenuLink.java +++ /dev/null @@ -1,84 +0,0 @@ -package org.argeo.cms.ui.util; - -import javax.jcr.Node; - -import org.argeo.cms.CmsMsg; -import org.argeo.cms.auth.CurrentUser; -import org.argeo.cms.swt.CmsStyles; -import org.argeo.cms.swt.auth.CmsLoginShell; -import org.eclipse.swt.events.DisposeEvent; -import org.eclipse.swt.events.DisposeListener; -import org.eclipse.swt.events.MouseEvent; -import org.eclipse.swt.events.MouseListener; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; - -/** Open the user menu when clicked */ -public class UserMenuLink extends MenuLink { - - public UserMenuLink() { - setCustom(CmsStyles.CMS_USER_MENU_LINK); - } - - @Override - public Control createUi(Composite parent, Node context) { - if (CurrentUser.isAnonymous()) - setLabel(CmsMsg.login.lead()); - else { - setLabel(CurrentUser.getDisplayName()); - } - Label link = (Label) ((Composite) super.createUi(parent, context)).getChildren()[0]; - link.addMouseListener(new UserMenuLinkController(context)); - return link.getParent(); - } - - protected CmsLoginShell createUserMenu(Control source, Node context) { - return new UserMenu(source.getParent(), context); - } - - private class UserMenuLinkController implements MouseListener, DisposeListener { - private static final long serialVersionUID = 3634864186295639792L; - - private CmsLoginShell userMenu = null; - private long lastDisposeTS = 0l; - - private final Node context; - - public UserMenuLinkController(Node context) { - this.context = context; - } - - // - // MOUSE LISTENER - // - @Override - public void mouseDown(MouseEvent e) { - if (e.button == 1) { - Control source = (Control) e.getSource(); - if (userMenu == null) { - long durationSinceLastDispose = System.currentTimeMillis() - lastDisposeTS; - // avoid to reopen the menu, if one has clicked gain - if (durationSinceLastDispose > 200) { - userMenu = createUserMenu(source, context); - userMenu.getShell().addDisposeListener(this); - } - } - } - } - - @Override - public void mouseDoubleClick(MouseEvent e) { - } - - @Override - public void mouseUp(MouseEvent e) { - } - - @Override - public void widgetDisposed(DisposeEvent event) { - userMenu = null; - lastDisposeTS = System.currentTimeMillis(); - } - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/VerticalMenu.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/VerticalMenu.java deleted file mode 100644 index 7f846c932..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/VerticalMenu.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.argeo.cms.ui.util; - -import java.util.ArrayList; -import java.util.List; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.CmsUiProvider; -import org.eclipse.swt.SWT; -import org.eclipse.swt.layout.GridData; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; - -public class VerticalMenu implements CmsUiProvider { - private List items = new ArrayList(); - - @Override - public Control createUi(Composite parent, Node context) throws RepositoryException { - Composite part = new Composite(parent, SWT.NONE); - part.setLayoutData(new GridData(SWT.LEAD, SWT.TOP, false, false)); -// part.setData(RWT.CUSTOM_VARIANT, custom); - part.setLayout(CmsSwtUtils.noSpaceGridLayout()); - for (CmsUiProvider uiProvider : items) { - Control subPart = uiProvider.createUi(part, context); - subPart.setLayoutData(new GridData(SWT.LEAD, SWT.TOP, false, false)); - } - return part; - } - - public void add(CmsUiProvider uiProvider) { - items.add(uiProvider); - } - - public List getItems() { - return items; - } - - public void setItems(List items) { - this.items = items; - } - -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/package-info.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/util/package-info.java deleted file mode 100644 index 566df883c..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/util/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Argeo CMS UI utilities. */ -package org.argeo.cms.ui.util; \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/AbstractPageViewer.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/AbstractPageViewer.java deleted file mode 100644 index ef24ee0d5..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/AbstractPageViewer.java +++ /dev/null @@ -1,350 +0,0 @@ -package org.argeo.cms.ui.viewers; - -import java.security.AccessControlContext; -import java.security.AccessController; -import java.security.PrivilegedAction; -import java.util.Observable; -import java.util.Observer; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.security.auth.Subject; - -import org.argeo.api.cms.CmsEditable; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.ui.widgets.ScrolledPage; -import org.argeo.jcr.JcrException; -import org.eclipse.jface.viewers.ContentViewer; -import org.eclipse.jface.viewers.ISelection; -import org.eclipse.jface.viewers.StructuredSelection; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.FocusEvent; -import org.eclipse.swt.events.FocusListener; -import org.eclipse.swt.events.MouseAdapter; -import org.eclipse.swt.events.MouseListener; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Widget; -import org.xml.sax.SAXParseException; - -/** Base class for viewers related to a page */ -public abstract class AbstractPageViewer extends ContentViewer implements Observer { - private static final long serialVersionUID = 5438688173410341485L; - - private final static CmsLog log = CmsLog.getLog(AbstractPageViewer.class); - - private final boolean readOnly; - /** The basis for the layouts, typically a ScrolledPage. */ - private final Composite page; - private final CmsEditable cmsEditable; - - private MouseListener mouseListener; - private FocusListener focusListener; - - private EditablePart edited; - private ISelection selection = StructuredSelection.EMPTY; - - private AccessControlContext accessControlContext; - - protected AbstractPageViewer(Section parent, int style, CmsEditable cmsEditable) { - // read only at UI level - readOnly = SWT.READ_ONLY == (style & SWT.READ_ONLY); - - this.cmsEditable = cmsEditable == null ? CmsEditable.NON_EDITABLE : cmsEditable; - if (this.cmsEditable instanceof Observable) - ((Observable) this.cmsEditable).addObserver(this); - - if (cmsEditable.canEdit()) { - mouseListener = createMouseListener(); - focusListener = createFocusListener(); - } - page = findPage(parent); - accessControlContext = AccessController.getContext(); - } - - /** - * Can be called to simplify the called to isModelInitialized() and initModel() - */ - protected void initModelIfNeeded(Node node) { - try { - if (!isModelInitialized(node)) - if (getCmsEditable().canEdit()) { - initModel(node); - node.getSession().save(); - } - } catch (RepositoryException e) { - throw new JcrException("Cannot initialize model", e); - } - } - - /** Called if user can edit and model is not initialized */ - protected Boolean isModelInitialized(Node node) throws RepositoryException { - return true; - } - - /** Called if user can edit and model is not initialized */ - protected void initModel(Node node) throws RepositoryException { - } - - /** Create (retrieve) the MouseListener to use. */ - protected MouseListener createMouseListener() { - return new MouseAdapter() { - private static final long serialVersionUID = 1L; - }; - } - - /** Create (retrieve) the FocusListener to use. */ - protected FocusListener createFocusListener() { - return new FocusListener() { - private static final long serialVersionUID = 1L; - - @Override - public void focusLost(FocusEvent event) { - } - - @Override - public void focusGained(FocusEvent event) { - } - }; - } - - protected Composite findPage(Composite composite) { - if (composite instanceof ScrolledPage) { - return (ScrolledPage) composite; - } else { - if (composite.getParent() == null) - return composite; - return findPage(composite.getParent()); - } - } - - public void layoutPage() { - if (page != null) - page.layout(true, true); - } - - protected void showControl(Control control) { - if (page != null && (page instanceof ScrolledPage)) - ((ScrolledPage) page).showControl(control); - } - - @Override - public void update(Observable o, Object arg) { - if (o == cmsEditable) - editingStateChanged(cmsEditable); - } - - /** To be overridden in order to provide the actual refresh */ - protected void refresh(Control control) throws RepositoryException { - } - - /** To be overridden.Save the edited part. */ - protected void save(EditablePart part) throws RepositoryException { - } - - /** Prepare the edited part */ - protected void prepare(EditablePart part, Object caretPosition) { - } - - /** Notified when the editing state changed. Does nothing, to be overridden */ - protected void editingStateChanged(CmsEditable cmsEditable) { - } - - @Override - public void refresh() { - // TODO check actual context in order to notice a discrepancy - Subject viewerSubject = getViewerSubject(); - Subject.doAs(viewerSubject, (PrivilegedAction) () -> { - try { - if (cmsEditable.canEdit() && !readOnly) - mouseListener = createMouseListener(); - else - mouseListener = null; - refresh(getControl()); - // layout(getControl()); - if (!getControl().isDisposed()) - layoutPage(); - } catch (RepositoryException e) { - throw new JcrException("Cannot refresh", e); - } - return null; - }); - } - - @Override - public void setSelection(ISelection selection, boolean reveal) { - this.selection = selection; - } - - protected void updateContent(EditablePart part) throws RepositoryException { - } - - // LOW LEVEL EDITION - protected void edit(EditablePart part, Object caretPosition) { - try { - if (edited == part) - return; - - if (edited != null && edited != part) { - EditablePart previouslyEdited = edited; - try { - stopEditing(true); - } catch (Exception e) { - notifyEditionException(e); - edit(previouslyEdited, caretPosition); - return; - } - } - - part.startEditing(); - edited = part; - updateContent(part); - prepare(part, caretPosition); - edited.getControl().addFocusListener(new FocusListener() { - private static final long serialVersionUID = 6883521812717097017L; - - @Override - public void focusLost(FocusEvent event) { - stopEditing(true); - } - - @Override - public void focusGained(FocusEvent event) { - } - }); - - layout(part.getControl()); - showControl(part.getControl()); - } catch (RepositoryException e) { - throw new JcrException("Cannot edit " + part, e); - } - } - - protected void stopEditing(Boolean save) { - if (edited instanceof Widget && ((Widget) edited).isDisposed()) { - edited = null; - return; - } - - assert edited != null; - if (edited == null) { - if (log.isTraceEnabled()) - log.warn("Told to stop editing while not editing anything"); - return; - } - - try { - if (save) - save(edited); - - edited.stopEditing(); - EditablePart editablePart = edited; - Control control = ((EditablePart) edited).getControl(); - edited = null; - // TODO make edited state management more robust - updateContent(editablePart); - layout(control); - } catch (RepositoryException e) { - throw new JcrException("Cannot stop editing", e); - } finally { - edited = null; - } - } - - // METHODS AVAILABLE TO EXTENDING CLASSES - protected void saveEdit() { - if (edited != null) - stopEditing(true); - } - - protected void cancelEdit() { - if (edited != null) - stopEditing(false); - } - - /** Layout this controls from the related base page. */ - public void layout(Control... controls) { - page.layout(controls); - } - - /** - * Find the first {@link EditablePart} in the parents hierarchy of this control - */ - protected EditablePart findDataParent(Control parent) { - if (parent instanceof EditablePart) { - return (EditablePart) parent; - } - if (parent.getParent() != null) - return findDataParent(parent.getParent()); - else - throw new IllegalStateException("No data parent found"); - } - - // UTILITIES - /** Check whether the edited part is in a proper state */ - protected void checkEdited() { - if (edited == null || (edited instanceof Widget) && ((Widget) edited).isDisposed()) - throw new IllegalStateException("Edited should not be null or disposed at this stage"); - } - - /** Persist all changes. */ - protected void persistChanges(Session session) throws RepositoryException { - session.save(); - session.refresh(false); - // TODO notify that changes have been persisted - } - - /** Convenience method using a Node in order to save the underlying session. */ - protected void persistChanges(Node anyNode) throws RepositoryException { - persistChanges(anyNode.getSession()); - } - - /** Notify edition exception */ - protected void notifyEditionException(Throwable e) { - Throwable eToLog = e; - if (e instanceof IllegalArgumentException) - if (e.getCause() instanceof SAXParseException) - eToLog = e.getCause(); - log.error(eToLog.getMessage(), eToLog); -// if (log.isTraceEnabled()) -// log.trace("Full stack of " + eToLog.getMessage(), e); - // TODO Light error notification popup - } - - protected Subject getViewerSubject() { - Subject res = null; - if (accessControlContext != null) { - res = Subject.getSubject(accessControlContext); - } - if (res == null) - throw new IllegalStateException("No subject associated with this viewer"); - return res; - } - - // GETTERS / SETTERS - public boolean isReadOnly() { - return readOnly; - } - - protected EditablePart getEdited() { - return edited; - } - - public MouseListener getMouseListener() { - return mouseListener; - } - - public FocusListener getFocusListener() { - return focusListener; - } - - public CmsEditable getCmsEditable() { - return cmsEditable; - } - - @Override - public ISelection getSelection() { - return selection; - } -} \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/EditablePart.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/EditablePart.java deleted file mode 100644 index 3967c97fb..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/EditablePart.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.argeo.cms.ui.viewers; - -import org.eclipse.swt.widgets.Control; - -/** Manages whether an editable or non editable control is shown. */ -public interface EditablePart { - public void startEditing(); - - public void stopEditing(); - - public Control getControl(); -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/ItemPart.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/ItemPart.java deleted file mode 100644 index 4ca45d191..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/ItemPart.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.argeo.cms.ui.viewers; - -import javax.jcr.Item; -import javax.jcr.RepositoryException; - -/** An editable part related to a JCR Item */ -public interface ItemPart { - public Item getItem() throws RepositoryException; -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/JcrVersionCmsEditable.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/JcrVersionCmsEditable.java deleted file mode 100644 index 11162e87f..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/JcrVersionCmsEditable.java +++ /dev/null @@ -1,101 +0,0 @@ -package org.argeo.cms.ui.viewers; - -import java.util.Observable; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.nodetype.NodeType; -import javax.jcr.version.VersionManager; - -import org.argeo.api.cms.CmsEditable; -import org.argeo.cms.CmsException; -import org.argeo.cms.ui.CmsEditionEvent; -import org.eclipse.rap.rwt.RWT; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Display; -import org.eclipse.swt.widgets.Event; -import org.eclipse.swt.widgets.Listener; - -/** Provides the CmsEditable semantic based on JCR versioning. */ -public class JcrVersionCmsEditable extends Observable implements CmsEditable { - private final String nodePath;// cache - private final VersionManager versionManager; - private final Boolean canEdit; - - public JcrVersionCmsEditable(Node node) throws RepositoryException { - this.nodePath = node.getPath(); - if (node.getSession().hasPermission(node.getPath(), - Session.ACTION_SET_PROPERTY)) { - // was Session.ACTION_ADD_NODE - canEdit = true; - if (!node.isNodeType(NodeType.MIX_VERSIONABLE)) { - node.addMixin(NodeType.MIX_VERSIONABLE); - node.getSession().save(); - } - versionManager = node.getSession().getWorkspace() - .getVersionManager(); - } else { - canEdit = false; - versionManager = null; - } - - // bind keys - if (canEdit) { - Display display = Display.getCurrent(); - display.setData(RWT.ACTIVE_KEYS, new String[] { "CTRL+RETURN", - "CTRL+E" }); - display.addFilter(SWT.KeyDown, new Listener() { - private static final long serialVersionUID = -4378653870463187318L; - - public void handleEvent(Event e) { - boolean ctrlPressed = (e.stateMask & SWT.CTRL) != 0; - if (ctrlPressed && e.keyCode == '\r') - stopEditing(); - else if (ctrlPressed && e.keyCode == 'E') - stopEditing(); - } - }); - } - } - - @Override - public Boolean canEdit() { - return canEdit; - } - - public Boolean isEditing() { - try { - if (!canEdit()) - return false; - return versionManager.isCheckedOut(nodePath); - } catch (RepositoryException e) { - throw new CmsException("Cannot check whether " + nodePath - + " is editing", e); - } - } - - @Override - public void startEditing() { - try { - versionManager.checkout(nodePath); - setChanged(); - } catch (RepositoryException e1) { - throw new CmsException("Cannot publish " + nodePath); - } - notifyObservers(new CmsEditionEvent(nodePath, - CmsEditionEvent.START_EDITING)); - } - - @Override - public void stopEditing() { - try { - versionManager.checkin(nodePath); - setChanged(); - } catch (RepositoryException e1) { - throw new CmsException("Cannot publish " + nodePath, e1); - } - notifyObservers(new CmsEditionEvent(nodePath, - CmsEditionEvent.STOP_EDITING)); - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/NodePart.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/NodePart.java deleted file mode 100644 index b51d4fcba..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/NodePart.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.argeo.cms.ui.viewers; - -import javax.jcr.Node; - -/** An editable part related to a node */ -public interface NodePart extends ItemPart { - public Node getNode(); -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/PropertyPart.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/PropertyPart.java deleted file mode 100644 index 793079e20..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/PropertyPart.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.argeo.cms.ui.viewers; - -import javax.jcr.Property; - -/** An editable part related to a JCR Property */ -public interface PropertyPart extends ItemPart { - public Property getProperty(); -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/Section.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/Section.java deleted file mode 100644 index d282eebbe..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/Section.java +++ /dev/null @@ -1,165 +0,0 @@ -package org.argeo.cms.ui.viewers; - -import java.util.Collections; -import java.util.LinkedHashMap; -import java.util.Map; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.widgets.JcrComposite; -import org.eclipse.swt.SWT; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; - -/** A structured UI related to a JCR context. */ -public class Section extends JcrComposite { - private static final long serialVersionUID = -5933796173755739207L; - - private final Section parentSection; - private Composite sectionHeader; - private final Integer relativeDepth; - - public Section(Composite parent, int style, Node node) { - this(parent, findSection(parent), style, node); - } - - public Section(Section section, int style, Node node) { - this(section, section, style, node); - } - - protected Section(Composite parent, Section parentSection, int style, Node node) { - super(parent, style, node); - try { - this.parentSection = parentSection; - if (parentSection != null) { - relativeDepth = getNode().getDepth() - parentSection.getNode().getDepth(); - } else { - relativeDepth = 0; - } - setLayout(CmsSwtUtils.noSpaceGridLayout()); - } catch (RepositoryException e) { - throw new IllegalStateException("Cannot create section from " + node, e); - } - } - - public Map getSubSections() throws RepositoryException { - LinkedHashMap result = new LinkedHashMap(); - for (Control child : getChildren()) { - if (child instanceof Composite) { - collectDirectSubSections((Composite) child, result); - } - } - return Collections.unmodifiableMap(result); - } - - private void collectDirectSubSections(Composite composite, LinkedHashMap subSections) - throws RepositoryException { - if (composite == sectionHeader || composite instanceof EditablePart) - return; - if (composite instanceof Section) { - Section section = (Section) composite; - subSections.put(section.getNodeId(), section); - return; - } - - for (Control child : composite.getChildren()) - if (child instanceof Composite) - collectDirectSubSections((Composite) child, subSections); - } - - public Composite createHeader() { - return createHeader(this); - } - - public Composite createHeader(Composite parent) { - if (sectionHeader != null) - sectionHeader.dispose(); - - sectionHeader = new Composite(parent, SWT.NONE); - sectionHeader.setLayoutData(CmsSwtUtils.fillWidth()); - sectionHeader.setLayout(CmsSwtUtils.noSpaceGridLayout()); - // sectionHeader.moveAbove(null); - // layout(); - return sectionHeader; - } - - public Composite getHeader() { - if (sectionHeader != null && sectionHeader.isDisposed()) - sectionHeader = null; - return sectionHeader; - } - - // SECTION PARTS - public SectionPart getSectionPart(String partId) { - for (Control child : getChildren()) { - if (child instanceof SectionPart) { - SectionPart sectionPart = (SectionPart) child; - if (sectionPart.getPartId().equals(partId)) - return sectionPart; - } - } - return null; - } - - public SectionPart nextSectionPart(SectionPart sectionPart) { - Control[] children = getChildren(); - for (int i = 0; i < children.length; i++) { - if (sectionPart == children[i]) { - for (int j = i + 1; j < children.length; j++) { - if (children[i + 1] instanceof SectionPart) { - return (SectionPart) children[i + 1]; - } - } - -// if (i + 1 < children.length) { -// Composite next = (Composite) children[i + 1]; -// return (SectionPart) next; -// } else { -// // next section -// } - } - } - return null; - } - - public SectionPart previousSectionPart(SectionPart sectionPart) { - Control[] children = getChildren(); - for (int i = 0; i < children.length; i++) { - if (sectionPart == children[i]) - if (i != 0) { - Composite previous = (Composite) children[i - 1]; - return (SectionPart) previous; - } else { - // previous section - } - } - return null; - } - - @Override - public String toString() { - if (parentSection == null) - return "Main section " + getNode(); - return "Section " + getNode(); - } - - public Section getParentSection() { - return parentSection; - } - - public Integer getRelativeDepth() { - return relativeDepth; - } - - /** Recursively finds the related section in the parents (can be itself) */ - public static Section findSection(Control control) { - if (control == null) - return null; - if (control instanceof Section) - return (Section) control; - else - return findSection(control.getParent()); - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/SectionPart.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/SectionPart.java deleted file mode 100644 index f0b367f5a..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/SectionPart.java +++ /dev/null @@ -1,9 +0,0 @@ -package org.argeo.cms.ui.viewers; - - -/** An editable part dynamically related to a Section */ -public interface SectionPart extends EditablePart, NodePart { - public String getPartId(); - - public Section getSection(); -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/package-info.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/package-info.java deleted file mode 100644 index 2f0793127..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/viewers/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Argeo CMS generic viewers, based on JFace. */ -package org.argeo.cms.ui.viewers; \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/ContextOverlay.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/ContextOverlay.java deleted file mode 100644 index 7bc0f79b5..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/ContextOverlay.java +++ /dev/null @@ -1,113 +0,0 @@ -package org.argeo.cms.ui.widgets; - -import org.argeo.cms.swt.CmsSwtUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.ShellAdapter; -import org.eclipse.swt.events.ShellEvent; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Shell; - -/** - * Manages a lightweight shell which is related to a {@link Control}, typically - * in order to reproduce a dropdown semantic, but with more flexibility. - */ -public class ContextOverlay extends ScrolledPage { - private static final long serialVersionUID = 6702077429573324009L; - -// private Shell shell; - private Control control; - - private int maxHeight = 400; - - public ContextOverlay(Control control, int style) { - super(createShell(control, style), SWT.NONE); - Shell shell = getShell(); - setLayoutData(CmsSwtUtils.fillAll()); - // TODO make autohide configurable? - //shell.addShellListener(new AutoHideShellListener()); - this.control = control; - control.addDisposeListener((e) -> { - dispose(); - shell.dispose(); - }); - } - - private static Composite createShell(Control control, int style) { - if (control == null) - throw new IllegalArgumentException("Control cannot be null"); - if (control.isDisposed()) - throw new IllegalArgumentException("Control is disposed"); - Shell shell = new Shell(control.getShell(), SWT.NO_TRIM); - shell.setLayout(CmsSwtUtils.noSpaceGridLayout()); - Composite placeholder = new Composite(shell, SWT.BORDER); - placeholder.setLayoutData(CmsSwtUtils.fillAll()); - placeholder.setLayout(CmsSwtUtils.noSpaceGridLayout()); - return placeholder; - } - - public void show() { - Point relativeControlLocation = control.getLocation(); - Point controlLocation = control.toDisplay(relativeControlLocation.x, relativeControlLocation.y); - - int controlWidth = control.getBounds().width; - - Shell shell = getShell(); - - layout(true, true); - shell.pack(); - shell.layout(true, true); - int targetShellWidth = shell.getSize().x < controlWidth ? controlWidth : shell.getSize().x; - if (shell.getSize().y > maxHeight) { - shell.setSize(targetShellWidth, maxHeight); - } else { - shell.setSize(targetShellWidth, shell.getSize().y); - } - - int shellHeight = shell.getSize().y; - int controlHeight = control.getBounds().height; - Point shellLocation = new Point(controlLocation.x, controlLocation.y + controlHeight); - int displayHeight = shell.getDisplay().getBounds().height; - if (shellLocation.y + shellHeight > displayHeight) {// bottom of page - shellLocation = new Point(controlLocation.x, controlLocation.y - shellHeight); - } - shell.setLocation(shellLocation); - - if (getChildren().length != 0) - shell.open(); - if (!control.isDisposed()) - control.setFocus(); - } - - public void hide() { - getShell().setVisible(false); - onHide(); - } - - public boolean isShellVisible() { - if (isDisposed()) - return false; - return getShell().isVisible(); - } - - /** to be overridden */ - protected void onHide() { - // does nothing by default. - } - - private class AutoHideShellListener extends ShellAdapter { - private static final long serialVersionUID = 7743287433907938099L; - - @Override - public void shellDeactivated(ShellEvent e) { - try { - Thread.sleep(1000); - } catch (InterruptedException e1) { - // silent - } - if (!control.isDisposed() && !control.isFocusControl()) - hide(); - } - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/EditableImage.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/EditableImage.java deleted file mode 100644 index c2393f267..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/EditableImage.java +++ /dev/null @@ -1,112 +0,0 @@ -package org.argeo.cms.ui.widgets; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.api.cms.Cms2DSize; -import org.argeo.api.cms.CmsLog; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.util.CmsUiUtils; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; - -/** A stylable and editable image. */ -public abstract class EditableImage extends StyledControl { - private static final long serialVersionUID = -5689145523114022890L; - private final static CmsLog log = CmsLog.getLog(EditableImage.class); - - private Cms2DSize preferredImageSize; - private Boolean loaded = false; - - public EditableImage(Composite parent, int swtStyle) { - super(parent, swtStyle); - } - - public EditableImage(Composite parent, int swtStyle, Cms2DSize preferredImageSize) { - super(parent, swtStyle); - this.preferredImageSize = preferredImageSize; - } - - public EditableImage(Composite parent, int style, Node node, boolean cacheImmediately, Cms2DSize preferredImageSize) - throws RepositoryException { - super(parent, style, node, cacheImmediately); - this.preferredImageSize = preferredImageSize; - } - - @Override - protected void setContainerLayoutData(Composite composite) { - // composite.setLayoutData(fillWidth()); - } - - @Override - protected void setControlLayoutData(Control control) { - // control.setLayoutData(fillWidth()); - } - - /** To be overriden. */ - protected String createImgTag() throws RepositoryException { - return CmsUiUtils - .noImg(preferredImageSize != null ? preferredImageSize : new Cms2DSize(getSize().x, getSize().y)); - } - - protected Label createLabel(Composite box, String style) { - Label lbl = new Label(box, getStyle()); - // lbl.setLayoutData(CmsUiUtils.fillWidth()); - CmsSwtUtils.markup(lbl); - CmsSwtUtils.style(lbl, style); - if (mouseListener != null) - lbl.addMouseListener(mouseListener); - load(lbl); - return lbl; - } - - /** To be overriden. */ - protected synchronized Boolean load(Control control) { - String imgTag; - try { - imgTag = createImgTag(); - } catch (Exception e) { - // throw new CmsException("Cannot retrieve image", e); - log.error("Cannot retrieve image", e); - imgTag = CmsUiUtils.noImg(preferredImageSize); - loaded = false; - } - - if (imgTag == null) { - loaded = false; - imgTag = CmsUiUtils.noImg(preferredImageSize); - } else - loaded = true; - if (control != null) { - ((Label) control).setText(imgTag); - control.setSize(preferredImageSize != null - ? new Point(preferredImageSize.getWidth(), preferredImageSize.getHeight()) - : getSize()); - } else { - loaded = false; - } - getParent().layout(); - return loaded; - } - - public void setPreferredSize(Cms2DSize size) { - this.preferredImageSize = size; - if (!loaded) { - load((Label) getControl()); - } - } - - protected Text createText(Composite box, String style) { - Text text = new Text(box, getStyle()); - CmsSwtUtils.style(text, style); - return text; - } - - public Cms2DSize getPreferredImageSize() { - return preferredImageSize; - } - -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/EditableText.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/EditableText.java deleted file mode 100644 index e3499ac4b..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/EditableText.java +++ /dev/null @@ -1,145 +0,0 @@ -package org.argeo.cms.ui.widgets; - -import javax.jcr.Item; -import javax.jcr.RepositoryException; - -import org.argeo.cms.swt.CmsSwtUtils; -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.Control; -import org.eclipse.swt.widgets.Label; -import org.eclipse.swt.widgets.Text; - -/** Editable text part displaying styled text. */ -public class EditableText extends StyledControl { - private static final long serialVersionUID = -6372283442330912755L; - - private boolean editable = true; - - private Color highlightColor; - private Composite highlight; - - private boolean useTextAsLabel = false; - - public EditableText(Composite parent, int style) { - super(parent, style); - editable = !(SWT.READ_ONLY == (style & SWT.READ_ONLY)); - highlightColor = parent.getDisplay().getSystemColor(SWT.COLOR_GRAY); - } - - public EditableText(Composite parent, int style, Item item) throws RepositoryException { - this(parent, style, item, false); - } - - public EditableText(Composite parent, int style, Item item, boolean cacheImmediately) throws RepositoryException { - super(parent, style, item, cacheImmediately); - editable = !(SWT.READ_ONLY == (style & SWT.READ_ONLY)); - highlightColor = parent.getDisplay().getSystemColor(SWT.COLOR_GRAY); - } - - @Override - protected Control createControl(Composite box, String style) { - if (isEditing() && getEditable()) { - return createText(box, style, true); - } else { - if (useTextAsLabel) { - return createTextLabel(box, style); - } else { - return createLabel(box, style); - } - } - } - - protected Label createLabel(Composite box, String style) { - Label lbl = new Label(box, getStyle() | SWT.WRAP); - lbl.setLayoutData(CmsSwtUtils.fillWidth()); - if (style != null) - CmsSwtUtils.style(lbl, style); - CmsSwtUtils.markup(lbl); - if (mouseListener != null) - lbl.addMouseListener(mouseListener); - return lbl; - } - - protected Text createTextLabel(Composite box, String style) { - Text lbl = new Text(box, getStyle() | SWT.MULTI); - lbl.setEditable(false); - lbl.setLayoutData(CmsSwtUtils.fillWidth()); - if (style != null) - CmsSwtUtils.style(lbl, style); - CmsSwtUtils.markup(lbl); - if (mouseListener != null) - lbl.addMouseListener(mouseListener); - return lbl; - } - - protected Text createText(Composite box, String style, boolean editable) { - highlight = new Composite(box, SWT.NONE); - highlight.setBackground(highlightColor); - GridData highlightGd = new GridData(SWT.FILL, SWT.FILL, false, false); - highlightGd.widthHint = 5; - highlightGd.heightHint = 3; - highlight.setLayoutData(highlightGd); - - final Text text = new Text(box, getStyle() | SWT.MULTI | SWT.WRAP); - text.setEditable(editable); - GridData textLayoutData = CmsSwtUtils.fillWidth(); - // textLayoutData.heightHint = preferredHeight; - text.setLayoutData(textLayoutData); - if (style != null) - CmsSwtUtils.style(text, style); - text.setFocus(); - return text; - } - - @Override - protected void clear(boolean deep) { - if (highlight != null) - highlight.dispose(); - super.clear(deep); - } - - public void setText(String text) { - Control child = getControl(); - if (child instanceof Label) - ((Label) child).setText(text); - else if (child instanceof Text) - ((Text) child).setText(text); - } - - public Text getAsText() { - return (Text) getControl(); - } - - public Label getAsLabel() { - return (Label) getControl(); - } - - public String getText() { - Control child = getControl(); - - if (child instanceof Label) - return ((Label) child).getText(); - else if (child instanceof Text) - return ((Text) child).getText(); - else - throw new IllegalStateException("Unsupported control " + child.getClass()); - } - - /** @deprecated Use {@link #isEditable()} instead. */ - @Deprecated - public boolean getEditable() { - return isEditable(); - } - - public boolean isEditable() { - return editable; - } - - public void setUseTextAsLabel(boolean useTextAsLabel) { - this.useTextAsLabel = useTextAsLabel; - } - -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/Img.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/Img.java deleted file mode 100644 index 3a4a60c9f..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/Img.java +++ /dev/null @@ -1,155 +0,0 @@ -package org.argeo.cms.ui.widgets; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.api.cms.Cms2DSize; -import org.argeo.api.cms.CmsImageManager; -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.internal.JcrFileUploadReceiver; -import org.argeo.cms.ui.viewers.NodePart; -import org.argeo.cms.ui.viewers.Section; -import org.argeo.cms.ui.viewers.SectionPart; -import org.argeo.jcr.Jcr; -import org.argeo.jcr.JcrException; -import org.eclipse.rap.fileupload.FileUploadHandler; -import org.eclipse.rap.fileupload.FileUploadListener; -import org.eclipse.rap.fileupload.FileUploadReceiver; -import org.eclipse.rap.rwt.service.ServerPushSession; -import org.eclipse.rap.rwt.widgets.FileUpload; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; - -/** An image within the Argeo Text framework */ -public class Img extends EditableImage implements SectionPart, NodePart { - private static final long serialVersionUID = 6233572783968188476L; - - private final Section section; - - private final CmsImageManager imageManager; - private FileUploadHandler currentUploadHandler = null; - private FileUploadListener fileUploadListener; - - public Img(Composite parent, int swtStyle, Node imgNode, Cms2DSize preferredImageSize) throws RepositoryException { - this(Section.findSection(parent), parent, swtStyle, imgNode, preferredImageSize, null); - setStyle(TextStyles.TEXT_IMAGE); - } - - public Img(Composite parent, int swtStyle, Node imgNode) throws RepositoryException { - this(Section.findSection(parent), parent, swtStyle, imgNode, null, null); - setStyle(TextStyles.TEXT_IMAGE); - } - - public Img(Composite parent, int swtStyle, Node imgNode, CmsImageManager imageManager) - throws RepositoryException { - this(Section.findSection(parent), parent, swtStyle, imgNode, null, imageManager); - setStyle(TextStyles.TEXT_IMAGE); - } - - Img(Section section, Composite parent, int swtStyle, Node imgNode, Cms2DSize preferredImageSize, - CmsImageManager imageManager) throws RepositoryException { - super(parent, swtStyle, imgNode, false, preferredImageSize); - this.section = section; - this.imageManager = imageManager != null ? imageManager - : (CmsImageManager) CmsSwtUtils.getCmsView(section).getImageManager(); - CmsSwtUtils.style(this, TextStyles.TEXT_IMG); - } - - @Override - protected Control createControl(Composite box, String style) { - if (isEditing()) { - try { - return createImageChooser(box, style); - } catch (RepositoryException e) { - throw new JcrException("Cannot create image chooser", e); - } - } else { - return createLabel(box, style); - } - } - - @Override - public synchronized void stopEditing() { - super.stopEditing(); - fileUploadListener = null; - } - - @Override - protected synchronized Boolean load(Control lbl) { - Node imgNode = getNode(); - boolean loaded = imageManager.load(imgNode, lbl, getPreferredImageSize()); - // getParent().layout(); - return loaded; - } - - protected Node getUploadFolder() { - return Jcr.getParent(getNode()); - } - - protected String getUploadName() { - Node node = getNode(); - return Jcr.getName(node) + '[' + Jcr.getIndex(node) + ']'; - } - - protected CmsImageManager getImageManager() { - return imageManager; - } - - protected Control createImageChooser(Composite box, String style) throws RepositoryException { - JcrFileUploadReceiver receiver = new JcrFileUploadReceiver(this, getUploadFolder(), getUploadName(), - imageManager); - if (currentUploadHandler != null) - currentUploadHandler.dispose(); - currentUploadHandler = prepareUpload(receiver); - final ServerPushSession pushSession = new ServerPushSession(); - final FileUpload fileUpload = new FileUpload(box, SWT.NONE); - CmsSwtUtils.style(fileUpload, style); - fileUpload.addSelectionListener(new SelectionAdapter() { - private static final long serialVersionUID = -9158471843941668562L; - - @Override - public void widgetSelected(SelectionEvent e) { - pushSession.start(); - fileUpload.submit(currentUploadHandler.getUploadUrl()); - } - }); - return fileUpload; - } - - protected FileUploadHandler prepareUpload(FileUploadReceiver receiver) { - final FileUploadHandler uploadHandler = new FileUploadHandler(receiver); - if (fileUploadListener != null) - uploadHandler.addUploadListener(fileUploadListener); - return uploadHandler; - } - - @Override - public Section getSection() { - return section; - } - - public void setFileUploadListener(FileUploadListener fileUploadListener) { - this.fileUploadListener = fileUploadListener; - if (currentUploadHandler != null) - currentUploadHandler.addUploadListener(fileUploadListener); - } - - @Override - public Node getItem() throws RepositoryException { - return getNode(); - } - - @Override - public String getPartId() { - return getNodeId(); - } - - @Override - public String toString() { - return "Img #" + getPartId(); - } - -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/JcrComposite.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/JcrComposite.java deleted file mode 100644 index 5d3576f27..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/JcrComposite.java +++ /dev/null @@ -1,213 +0,0 @@ -package org.argeo.cms.ui.widgets; - -import javax.jcr.Item; -import javax.jcr.ItemNotFoundException; -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.RepositoryException; -import javax.jcr.Session; - -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.jcr.JcrException; -import org.eclipse.swt.widgets.Composite; - -/** A composite which can (optionally) manage a JCR Item. */ -public class JcrComposite extends Composite { - private static final long serialVersionUID = -1447009015451153367L; - - private Session session; - - private String nodeId; - private String property = null; - private Node cache; - - /** Regular composite constructor. No layout is set. */ - public JcrComposite(Composite parent, int style) { - super(parent, style); - session = null; - nodeId = null; - } - - public JcrComposite(Composite parent, int style, Item item) { - this(parent, style, item, false); - } - - public JcrComposite(Composite parent, int style, Item item, boolean cacheImmediately) { - super(parent, style); - if (item != null) - try { - this.session = item.getSession(); -// if (!cacheImmediately && (SWT.READ_ONLY == (style & SWT.READ_ONLY))) { -// // (useless?) optimization: we only save a pointer to the session, -// // not even a reference to the item -// this.nodeId = null; -// } else { - Node node; - Property property = null; - if (item instanceof Node) { - node = (Node) item; - } else {// Property - property = (Property) item; - if (property.isMultiple())// TODO manage property index - throw new UnsupportedOperationException("Multiple properties not supported yet."); - this.property = property.getName(); - node = property.getParent(); - } - this.nodeId = node.getIdentifier(); - if (cacheImmediately) - this.cache = node; -// } - setLayout(CmsSwtUtils.noSpaceGridLayout()); - } catch (RepositoryException e) { - throw new IllegalStateException("Cannot create composite from " + item, e); - } - } - - public synchronized Node getNode() { - try { - if (!itemIsNode()) - throw new IllegalStateException("Item is not a Node"); - return getNodeInternal(); - } catch (RepositoryException e) { - throw new JcrException("Cannot get node " + nodeId, e); - } - } - - private synchronized Node getNodeInternal() throws RepositoryException { - if (cache != null) - return cache; - else if (session != null) - if (nodeId != null) - return session.getNodeByIdentifier(nodeId); - else - return null; - else - return null; - } - - public synchronized String getPropertyName() { - try { - return getProperty().getName(); - } catch (RepositoryException e) { - throw new JcrException("Cannot get property name", e); - } - } - - public synchronized Node getPropertyNode() { - try { - return getProperty().getNode(); - } catch (RepositoryException e) { - throw new JcrException("Cannot get property name", e); - } - } - - public synchronized Property getProperty() { - try { - if (itemIsNode()) - throw new IllegalStateException("Item is not a Property"); - Node node = getNodeInternal(); - if (!node.hasProperty(property)) - throw new IllegalStateException("Property " + property + " is not set on " + node); - return node.getProperty(property); - } catch (RepositoryException e) { - throw new JcrException("Cannot get property " + property + " from node " + nodeId, e); - } - } - - public synchronized boolean itemIsNode() { - return property == null; - } - - public synchronized boolean itemExists() { - if (session == null) - return false; - try { - Node n = session.getNodeByIdentifier(nodeId); - if (!itemIsNode()) - return n.hasProperty(property); - else - return true; - } catch (ItemNotFoundException e) { - return false; - } catch (RepositoryException e) { - throw new JcrException("Cannot check whether node exists", e); - } - } - - /** Set/update the cache or change the node */ - public synchronized void setNode(Node node) { - if (!itemIsNode()) - throw new IllegalArgumentException("Cannot set a Node on a Property"); - - if (node == null) {// clear cache - this.cache = null; - return; - } - - try { -// if (session != null || session != node.getSession())// check session -// throw new IllegalArgumentException("Uncompatible session"); -// if (session == null) - session = node.getSession(); - if (nodeId == null || !nodeId.equals(node.getIdentifier())) { - nodeId = node.getIdentifier(); - cache = node; - itemUpdated(); - } else { - cache = node;// set/update cache - } - } catch (RepositoryException e) { - throw new IllegalStateException(e); - } - } - - /** Set/update the cache or change the property */ - public synchronized void setProperty(Property prop) { - if (itemIsNode()) - throw new IllegalArgumentException("Cannot set a Property on a Node"); - - if (prop == null) {// clear cache - this.cache = null; - return; - } - - try { - if (session == null || session != prop.getSession())// check session - throw new IllegalArgumentException("Uncompatible session"); - - Node node = prop.getNode(); - if (nodeId == null || !nodeId.equals(node.getIdentifier()) || !property.equals(prop.getName())) { - nodeId = node.getIdentifier(); - property = prop.getName(); - cache = node; - itemUpdated(); - } else { - cache = node;// set/update cache - } - } catch (RepositoryException e) { - throw new IllegalStateException(e); - } - } - - public synchronized String getNodeId() { - return nodeId; - } - - /** Change the node, does nothing if same. */ - public synchronized void setNodeId(String nodeId) throws RepositoryException { - if (this.nodeId != null && this.nodeId.equals(nodeId)) - return; - this.nodeId = nodeId; - if (cache != null) - cache = session.getNodeByIdentifier(this.nodeId); - itemUpdated(); - } - - protected synchronized void itemUpdated() { - layout(); - } - - public Session getSession() { - return session; - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/ScrolledPage.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/ScrolledPage.java deleted file mode 100644 index 517e796e9..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/ScrolledPage.java +++ /dev/null @@ -1,74 +0,0 @@ -package org.argeo.cms.ui.widgets; - -import org.eclipse.swt.SWT; -import org.eclipse.swt.custom.ScrolledComposite; -import org.eclipse.swt.events.ControlEvent; -import org.eclipse.swt.graphics.Point; -import org.eclipse.swt.graphics.Rectangle; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; - -/** - * A composite that can be scrolled vertically. It wraps a - * {@link ScrolledComposite} (and is being wrapped by it), simplifying its - * configuration. - */ -public class ScrolledPage extends Composite { - private static final long serialVersionUID = 1593536965663574437L; - - private ScrolledComposite scrolledComposite; - - public ScrolledPage(Composite parent, int style) { - this(parent, style, false); - } - - public ScrolledPage(Composite parent, int style, boolean alwaysShowScroll) { - super(createScrolledComposite(parent, alwaysShowScroll), style); - scrolledComposite = (ScrolledComposite) getParent(); - scrolledComposite.setContent(this); - - scrolledComposite.setExpandVertical(true); - scrolledComposite.setExpandHorizontal(true); - scrolledComposite.addControlListener(new ScrollControlListener()); - } - - private static ScrolledComposite createScrolledComposite(Composite parent, boolean alwaysShowScroll) { - ScrolledComposite scrolledComposite = new ScrolledComposite(parent, SWT.V_SCROLL); - scrolledComposite.setAlwaysShowScrollBars(alwaysShowScroll); - return scrolledComposite; - } - - @Override - public void layout(boolean changed, boolean all) { - updateScroll(); - super.layout(changed, all); - } - - public void showControl(Control control) { - scrolledComposite.showControl(control); - } - - protected void updateScroll() { - Rectangle r = scrolledComposite.getClientArea(); - Point preferredSize = computeSize(r.width, SWT.DEFAULT); - scrolledComposite.setMinHeight(preferredSize.y); - } - - // public ScrolledComposite getScrolledComposite() { - // return this.scrolledComposite; - // } - - /** Set it on the wrapping scrolled composite */ - @Override - public void setLayoutData(Object layoutData) { - scrolledComposite.setLayoutData(layoutData); - } - - private class ScrollControlListener extends org.eclipse.swt.events.ControlAdapter { - private static final long serialVersionUID = -3586986238567483316L; - - public void controlResized(ControlEvent e) { - updateScroll(); - } - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/StyledControl.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/StyledControl.java deleted file mode 100644 index e3a5cb473..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/StyledControl.java +++ /dev/null @@ -1,153 +0,0 @@ -package org.argeo.cms.ui.widgets; - -import javax.jcr.Item; - -import org.argeo.cms.swt.CmsSwtUtils; -import org.argeo.cms.ui.CmsUiConstants; -import org.argeo.eclipse.ui.specific.EclipseUiSpecificUtils; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.FocusListener; -import org.eclipse.swt.events.MouseListener; -import org.eclipse.swt.widgets.Composite; -import org.eclipse.swt.widgets.Control; - -/** Editable text part displaying styled text. */ -public abstract class StyledControl extends JcrComposite implements CmsUiConstants { - private static final long serialVersionUID = -6372283442330912755L; - private Control control; - - private Composite container; - private Composite box; - - protected MouseListener mouseListener; - protected FocusListener focusListener; - - private Boolean editing = Boolean.FALSE; - - private Composite ancestorToLayout; - - public StyledControl(Composite parent, int swtStyle) { - super(parent, swtStyle); - setLayout(CmsSwtUtils.noSpaceGridLayout()); - } - - public StyledControl(Composite parent, int style, Item item) { - super(parent, style, item); - } - - public StyledControl(Composite parent, int style, Item item, boolean cacheImmediately) { - super(parent, style, item, cacheImmediately); - } - - protected abstract Control createControl(Composite box, String style); - - protected Composite createBox() { - Composite box = new Composite(container, SWT.INHERIT_DEFAULT); - setContainerLayoutData(box); - box.setLayout(CmsSwtUtils.noSpaceGridLayout(3)); - return box; - } - - protected Composite createContainer() { - Composite container = new Composite(this, SWT.INHERIT_DEFAULT); - setContainerLayoutData(container); - container.setLayout(CmsSwtUtils.noSpaceGridLayout()); - return container; - } - - public Control getControl() { - return control; - } - - protected synchronized Boolean isEditing() { - return editing; - } - - public synchronized void startEditing() { - assert !isEditing(); - editing = true; - // int height = control.getSize().y; - String style = (String) EclipseUiSpecificUtils.getStyleData(control); - clear(false); - refreshControl(style); - - // add the focus listener to the newly created edition control - if (focusListener != null) - control.addFocusListener(focusListener); - } - - public synchronized void stopEditing() { - assert isEditing(); - editing = false; - String style = (String) EclipseUiSpecificUtils.getStyleData(control); - clear(false); - refreshControl(style); - } - - protected void refreshControl(String style) { - control = createControl(box, style); - setControlLayoutData(control); - if (ancestorToLayout != null) - ancestorToLayout.layout(true, true); - else - getParent().layout(true, true); - } - - public void setStyle(String style) { - Object currentStyle = null; - if (control != null) - currentStyle = EclipseUiSpecificUtils.getStyleData(control); - if (currentStyle != null && currentStyle.equals(style)) - return; - - clear(true); - refreshControl(style); - - if (style != null) { - CmsSwtUtils.style(box, style + "_box"); - CmsSwtUtils.style(container, style + "_container"); - } - } - - /** To be overridden */ - protected void setControlLayoutData(Control control) { - control.setLayoutData(CmsSwtUtils.fillWidth()); - } - - /** To be overridden */ - protected void setContainerLayoutData(Composite composite) { - composite.setLayoutData(CmsSwtUtils.fillWidth()); - } - - protected void clear(boolean deep) { - if (deep) { - for (Control control : getChildren()) - control.dispose(); - container = createContainer(); - box = createBox(); - } else { - control.dispose(); - } - } - - public void setMouseListener(MouseListener mouseListener) { - if (this.mouseListener != null && control != null) - control.removeMouseListener(this.mouseListener); - this.mouseListener = mouseListener; - if (control != null && this.mouseListener != null) - control.addMouseListener(mouseListener); - } - - public void setFocusListener(FocusListener focusListener) { - if (this.focusListener != null && control != null) - control.removeFocusListener(this.focusListener); - this.focusListener = focusListener; - if (control != null && this.focusListener != null) - control.addFocusListener(focusListener); - } - - public void setAncestorToLayout(Composite ancestorToLayout) { - this.ancestorToLayout = ancestorToLayout; - } - -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/TextStyles.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/TextStyles.java deleted file mode 100644 index e461ed0df..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/TextStyles.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.argeo.cms.ui.widgets; - -/** Styles references in the CSS. */ -public interface TextStyles { - /** The whole page area */ - public final static String TEXT_AREA = "text_area"; - /** Area providing controls for editing text */ - public final static String TEXT_EDITOR_HEADER = "text_editor_header"; - /** The styled composite for editing the text */ - public final static String TEXT_STYLED_COMPOSITE = "text_styled_composite"; - /** A section */ - public final static String TEXT_SECTION = "text_section"; - /** A paragraph */ - public final static String TEXT_PARAGRAPH = "text_paragraph"; - /** An image */ - public final static String TEXT_IMG = "text_img"; - /** The dialog to edit styled paragraph */ - public final static String TEXT_STYLED_TOOLS_DIALOG = "text_styled_tools_dialog"; - - /* - * DEFAULT TEXT STYLES - */ - /** Default style for text body */ - public final static String TEXT_DEFAULT = "text_default"; - /** Fixed-width, typically code */ - public final static String TEXT_PRE = "text_pre"; - /** Quote */ - public final static String TEXT_QUOTE = "text_quote"; - /** Title */ - public final static String TEXT_TITLE = "text_title"; - /** Header (to be dynamically completed with the depth, e.g. text_h1) */ - public final static String TEXT_H = "text_h"; - - /** Default style for images */ - public final static String TEXT_IMAGE = "text_image"; - -} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/package-info.java b/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/package-info.java deleted file mode 100644 index 514f75380..000000000 --- a/org.argeo.cms.ui/src/org/argeo/cms/ui/widgets/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Argeo CMS generic widgets, based on SWT. */ -package org.argeo.cms.ui.widgets; \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/AbstractNodeContentProvider.java b/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/AbstractNodeContentProvider.java deleted file mode 100644 index fdafa982e..000000000 --- a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/AbstractNodeContentProvider.java +++ /dev/null @@ -1,138 +0,0 @@ -package org.argeo.eclipse.ui.jcr; - -import java.util.ArrayList; -import java.util.List; - -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.RepositoryException; -import javax.jcr.Session; - -import org.argeo.api.cms.CmsLog; -import org.argeo.eclipse.ui.AbstractTreeContentProvider; -import org.argeo.eclipse.ui.EclipseUiException; - -/** Canonical implementation of tree content provider manipulating JCR nodes. */ -public abstract class AbstractNodeContentProvider extends - AbstractTreeContentProvider { - private static final long serialVersionUID = -4905836490027272569L; - - private final static CmsLog log = CmsLog - .getLog(AbstractNodeContentProvider.class); - - private Session session; - - public AbstractNodeContentProvider(Session session) { - this.session = session; - } - - /** - * Whether this path is a base path (and thus has no parent). By default it - * returns true if path is '/' (root node) - */ - protected Boolean isBasePath(String path) { - // root node - return path.equals("/"); - } - - @Override - public Object[] getChildren(Object element) { - Object[] children; - if (element instanceof Node) { - try { - Node node = (Node) element; - children = getChildren(node); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot get children of " + element, e); - } - } else if (element instanceof WrappedNode) { - WrappedNode wrappedNode = (WrappedNode) element; - try { - children = getChildren(wrappedNode.getNode()); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot get children of " - + wrappedNode, e); - } - } else if (element instanceof NodesWrapper) { - NodesWrapper node = (NodesWrapper) element; - children = node.getChildren(); - } else { - children = super.getChildren(element); - } - - children = sort(element, children); - return children; - } - - /** Do not sort by default. To be overidden to provide custom sort. */ - protected Object[] sort(Object parent, Object[] children) { - return children; - } - - /** - * To be overridden in order to filter out some nodes. Does nothing by - * default. The provided list is a temporary one and can thus be modified - * directly . (e.g. via an iterator) - */ - protected List filterChildren(List children) - throws RepositoryException { - return children; - } - - protected Object[] getChildren(Node node) throws RepositoryException { - List nodes = new ArrayList(); - for (NodeIterator nit = node.getNodes(); nit.hasNext();) - nodes.add(nit.nextNode()); - nodes = filterChildren(nodes); - return nodes.toArray(); - } - - @Override - public Object getParent(Object element) { - if (element instanceof Node) { - Node node = (Node) element; - try { - String path = node.getPath(); - if (isBasePath(path)) - return null; - else - return node.getParent(); - } catch (RepositoryException e) { - log.warn("Cannot get parent of " + element + ": " + e); - return null; - } - } else if (element instanceof WrappedNode) { - WrappedNode wrappedNode = (WrappedNode) element; - return wrappedNode.getParent(); - } else if (element instanceof NodesWrapper) { - NodesWrapper nodesWrapper = (NodesWrapper) element; - return this.getParent(nodesWrapper.getNode()); - } - return super.getParent(element); - } - - @Override - public boolean hasChildren(Object element) { - try { - if (element instanceof Node) { - Node node = (Node) element; - return node.hasNodes(); - } else if (element instanceof WrappedNode) { - WrappedNode wrappedNode = (WrappedNode) element; - return wrappedNode.getNode().hasNodes(); - } else if (element instanceof NodesWrapper) { - NodesWrapper nodesWrapper = (NodesWrapper) element; - return nodesWrapper.hasChildren(); - } - - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot check whether " + element - + " has children", e); - } - return super.hasChildren(element); - } - - public Session getSession() { - return session; - } -} \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/AsyncUiEventListener.java b/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/AsyncUiEventListener.java deleted file mode 100644 index b880a63c5..000000000 --- a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/AsyncUiEventListener.java +++ /dev/null @@ -1,83 +0,0 @@ -package org.argeo.eclipse.ui.jcr; - -import java.util.ArrayList; -import java.util.List; - -import javax.jcr.RepositoryException; -import javax.jcr.observation.Event; -import javax.jcr.observation.EventIterator; -import javax.jcr.observation.EventListener; - -import org.argeo.api.cms.CmsLog; -import org.argeo.eclipse.ui.EclipseUiException; -import org.eclipse.swt.widgets.Display; - -/** - * {@link EventListener} which simplifies running actions within the UI thread. - */ -public abstract class AsyncUiEventListener implements EventListener { - // private final static Log logSuper = LogFactory - // .getLog(AsyncUiEventListener.class); - private final CmsLog logThis = CmsLog.getLog(getClass()); - - private final Display display; - - public AsyncUiEventListener(Display display) { - super(); - this.display = display; - } - - /** Called asynchronously in the UI thread. */ - protected abstract void onEventInUiThread(List events) throws RepositoryException; - - /** - * Whether these events should be processed in the UI or skipped with no UI - * job created. - */ - protected Boolean willProcessInUiThread(List events) throws RepositoryException { - return true; - } - - protected CmsLog getLog() { - return logThis; - } - - public final void onEvent(final EventIterator eventIterator) { - final List events = new ArrayList(); - while (eventIterator.hasNext()) - events.add(eventIterator.nextEvent()); - - if (logThis.isTraceEnabled()) - logThis.trace("Received " + events.size() + " events"); - - try { - if (!willProcessInUiThread(events)) - return; - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot test skip events " + events, e); - } - - // Job job = new Job("JCR Events") { - // protected IStatus run(IProgressMonitor monitor) { - // if (display.isDisposed()) { - // logSuper.warn("Display is disposed cannot update UI"); - // return Status.CANCEL_STATUS; - // } - - if (!display.isDisposed()) - display.asyncExec(new Runnable() { - public void run() { - try { - onEventInUiThread(events); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot process events " + events, e); - } - } - }); - - // return Status.OK_STATUS; - // } - // }; - // job.schedule(); - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/DefaultNodeLabelProvider.java b/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/DefaultNodeLabelProvider.java deleted file mode 100644 index 22ffeafc6..000000000 --- a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/DefaultNodeLabelProvider.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.argeo.eclipse.ui.jcr; - -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.RepositoryException; -import javax.jcr.nodetype.NodeType; - -import org.argeo.eclipse.ui.EclipseUiException; -import org.eclipse.jface.viewers.ColumnLabelProvider; -import org.eclipse.swt.graphics.Image; - -/** - * Default label provider to manage node and corresponding UI objects. It - * provides reasonable overwrite-able default for known JCR types. - */ -public class DefaultNodeLabelProvider extends ColumnLabelProvider { - private static final long serialVersionUID = 1216182332792151235L; - - public String getText(Object element) { - try { - if (element instanceof Node) { - return getText((Node) element); - } else if (element instanceof WrappedNode) { - return getText(((WrappedNode) element).getNode()); - } else if (element instanceof NodesWrapper) { - return getText(((NodesWrapper) element).getNode()); - } - return super.getText(element); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot get text for of " + element, e); - } - } - - protected String getText(Node node) throws RepositoryException { - if (node.isNodeType(NodeType.MIX_TITLE) - && node.hasProperty(Property.JCR_TITLE)) - return node.getProperty(Property.JCR_TITLE).getString(); - else - return node.getName(); - } - - @Override - public Image getImage(Object element) { - try { - if (element instanceof Node) { - return getImage((Node) element); - } else if (element instanceof WrappedNode) { - return getImage(((WrappedNode) element).getNode()); - } else if (element instanceof NodesWrapper) { - return getImage(((NodesWrapper) element).getNode()); - } - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot retrieve image for " + element, e); - } - return super.getImage(element); - } - - protected Image getImage(Node node) throws RepositoryException { - // FIXME who uses that? - return null; - } - - @Override - public String getToolTipText(Object element) { - try { - if (element instanceof Node) { - return getToolTipText((Node) element); - } else if (element instanceof WrappedNode) { - return getToolTipText(((WrappedNode) element).getNode()); - } else if (element instanceof NodesWrapper) { - return getToolTipText(((NodesWrapper) element).getNode()); - } - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot get tooltip for " + element, e); - } - return super.getToolTipText(element); - } - - protected String getToolTipText(Node node) throws RepositoryException { - return null; - } -} \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/EclipseJcrMonitor.java b/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/EclipseJcrMonitor.java deleted file mode 100644 index b83aaa25a..000000000 --- a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/EclipseJcrMonitor.java +++ /dev/null @@ -1,44 +0,0 @@ -package org.argeo.eclipse.ui.jcr; - -import org.argeo.jcr.JcrMonitor; -import org.eclipse.core.runtime.IProgressMonitor; - -/** - * Wraps an Eclipse {@link IProgressMonitor} so that it can be passed to - * framework agnostic Argeo routines. - */ -public class EclipseJcrMonitor implements JcrMonitor { - private final IProgressMonitor progressMonitor; - - public EclipseJcrMonitor(IProgressMonitor progressMonitor) { - this.progressMonitor = progressMonitor; - } - - public void beginTask(String name, int totalWork) { - progressMonitor.beginTask(name, totalWork); - } - - public void done() { - progressMonitor.done(); - } - - public boolean isCanceled() { - return progressMonitor.isCanceled(); - } - - public void setCanceled(boolean value) { - progressMonitor.setCanceled(value); - } - - public void setTaskName(String name) { - progressMonitor.setTaskName(name); - } - - public void subTask(String name) { - progressMonitor.subTask(name); - } - - public void worked(int work) { - progressMonitor.worked(work); - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/JcrUiUtils.java b/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/JcrUiUtils.java deleted file mode 100644 index 420154b83..000000000 --- a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/JcrUiUtils.java +++ /dev/null @@ -1,149 +0,0 @@ -package org.argeo.eclipse.ui.jcr; - -import java.util.Calendar; - -import javax.jcr.Node; -import javax.jcr.PropertyType; -import javax.jcr.RepositoryException; - -import org.argeo.eclipse.ui.EclipseUiException; -import org.argeo.eclipse.ui.jcr.lists.NodeViewerComparator; -import org.argeo.eclipse.ui.jcr.lists.RowViewerComparator; -import org.eclipse.jface.viewers.TableViewer; -import org.eclipse.swt.SWT; -import org.eclipse.swt.events.SelectionAdapter; -import org.eclipse.swt.events.SelectionEvent; -import org.eclipse.swt.widgets.Table; - -/** Utility methods to simplify UI development using SWT (or RWT), jface and JCR. */ -public class JcrUiUtils { - - /** - * Centralizes management of updating property value. Among other to avoid - * infinite loop when the new value is the same as the ones that is already - * stored in JCR. - * - * @return true if the value as changed - */ - public static boolean setJcrProperty(Node node, String propName, - int propertyType, Object value) { - try { - switch (propertyType) { - case PropertyType.STRING: - if ("".equals((String) value) - && (!node.hasProperty(propName) || node - .hasProperty(propName) - && "".equals(node.getProperty(propName) - .getString()))) - // workaround the fact that the Text widget value cannot be - // set to null - return false; - else if (node.hasProperty(propName) - && node.getProperty(propName).getString() - .equals((String) value)) - // nothing changed yet - return false; - else { - node.setProperty(propName, (String) value); - return true; - } - case PropertyType.BOOLEAN: - if (node.hasProperty(propName) - && node.getProperty(propName).getBoolean() == (Boolean) value) - // nothing changed yet - return false; - else { - node.setProperty(propName, (Boolean) value); - return true; - } - case PropertyType.DATE: - if (node.hasProperty(propName) - && node.getProperty(propName).getDate() - .equals((Calendar) value)) - // nothing changed yet - return false; - else { - node.setProperty(propName, (Calendar) value); - return true; - } - case PropertyType.LONG: - Long lgValue = (Long) value; - - if (lgValue == null) - lgValue = 0L; - - if (node.hasProperty(propName) - && node.getProperty(propName).getLong() == lgValue) - // nothing changed yet - return false; - else { - node.setProperty(propName, lgValue); - return true; - } - - default: - throw new EclipseUiException("Unimplemented property save"); - } - } catch (RepositoryException re) { - throw new EclipseUiException("Unexpected error while setting property", - re); - } - } - - /** - * Creates a new selection adapter in order to provide sorting abitily on a - * SWT Table that display a row list - **/ - public static SelectionAdapter getRowSelectionAdapter(final int index, - final int propertyType, final String selectorName, - final String propertyName, final RowViewerComparator comparator, - final TableViewer viewer) { - SelectionAdapter selectionAdapter = new SelectionAdapter() { - private static final long serialVersionUID = -5738918304901437720L; - - @Override - public void widgetSelected(SelectionEvent e) { - Table table = viewer.getTable(); - comparator.setColumn(propertyType, selectorName, propertyName); - int dir = table.getSortDirection(); - if (table.getSortColumn() == table.getColumn(index)) { - dir = dir == SWT.UP ? SWT.DOWN : SWT.UP; - } else { - dir = SWT.DOWN; - } - table.setSortDirection(dir); - table.setSortColumn(table.getColumn(index)); - viewer.refresh(); - } - }; - return selectionAdapter; - } - - /** - * Creates a new selection adapter in order to provide sorting abitily on a - * swt table that display a row list - **/ - public static SelectionAdapter getNodeSelectionAdapter(final int index, - final int propertyType, final String propertyName, - final NodeViewerComparator comparator, final TableViewer viewer) { - SelectionAdapter selectionAdapter = new SelectionAdapter() { - private static final long serialVersionUID = -1683220869195484625L; - - @Override - public void widgetSelected(SelectionEvent e) { - Table table = viewer.getTable(); - comparator.setColumn(propertyType, propertyName); - int dir = table.getSortDirection(); - if (table.getSortColumn() == table.getColumn(index)) { - dir = dir == SWT.UP ? SWT.DOWN : SWT.UP; - } else { - dir = SWT.DOWN; - } - table.setSortDirection(dir); - table.setSortColumn(table.getColumn(index)); - viewer.refresh(); - } - }; - return selectionAdapter; - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeColumnLabelProvider.java b/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeColumnLabelProvider.java deleted file mode 100644 index 7e12becd8..000000000 --- a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeColumnLabelProvider.java +++ /dev/null @@ -1,123 +0,0 @@ -package org.argeo.eclipse.ui.jcr; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.eclipse.jface.viewers.ColumnLabelProvider; -import org.eclipse.swt.graphics.Color; -import org.eclipse.swt.graphics.Font; -import org.eclipse.swt.graphics.Image; - -/** Simplifies writing JCR-based column label provider. */ -public class NodeColumnLabelProvider extends ColumnLabelProvider { - private static final long serialVersionUID = -6586692836928505358L; - - protected String getNodeText(Node node) throws RepositoryException { - return super.getText(node); - } - - protected String getNodeToolTipText(Node node) throws RepositoryException { - return super.getToolTipText(node); - } - - protected Image getNodeImage(Node node) throws RepositoryException { - return super.getImage(node); - } - - protected Font getNodeFont(Node node) throws RepositoryException { - return super.getFont(node); - } - - public Color getNodeBackground(Node node) throws RepositoryException { - return super.getBackground(node); - } - - public Color getNodeForeground(Node node) throws RepositoryException { - return super.getForeground(node); - } - - @Override - public String getText(Object element) { - try { - if (element instanceof Node) - return getNodeText((Node) element); - else if (element instanceof NodeElement) - return getNodeText(((NodeElement) element).getNode()); - else - throw new IllegalArgumentException("Unsupported element type " + element.getClass()); - } catch (RepositoryException e) { - throw new IllegalStateException("Repository exception when accessing " + element, e); - } - } - - @Override - public Image getImage(Object element) { - try { - if (element instanceof Node) - return getNodeImage((Node) element); - else if (element instanceof NodeElement) - return getNodeImage(((NodeElement) element).getNode()); - else - throw new IllegalArgumentException("Unsupported element type " + element.getClass()); - } catch (RepositoryException e) { - throw new IllegalStateException("Repository exception when accessing " + element, e); - } - } - - @Override - public String getToolTipText(Object element) { - try { - if (element instanceof Node) - return getNodeToolTipText((Node) element); - else if (element instanceof NodeElement) - return getNodeToolTipText(((NodeElement) element).getNode()); - else - throw new IllegalArgumentException("Unsupported element type " + element.getClass()); - } catch (RepositoryException e) { - throw new IllegalStateException("Repository exception when accessing " + element, e); - } - } - - @Override - public Font getFont(Object element) { - try { - if (element instanceof Node) - return getNodeFont((Node) element); - else if (element instanceof NodeElement) - return getNodeFont(((NodeElement) element).getNode()); - else - throw new IllegalArgumentException("Unsupported element type " + element.getClass()); - } catch (RepositoryException e) { - throw new IllegalStateException("Repository exception when accessing " + element, e); - } - } - - @Override - public Color getBackground(Object element) { - try { - if (element instanceof Node) - return getNodeBackground((Node) element); - else if (element instanceof NodeElement) - return getNodeBackground(((NodeElement) element).getNode()); - else - throw new IllegalArgumentException("Unsupported element type " + element.getClass()); - } catch (RepositoryException e) { - throw new IllegalStateException("Repository exception when accessing " + element, e); - } - } - - @Override - public Color getForeground(Object element) { - try { - if (element instanceof Node) - return getNodeForeground((Node) element); - else if (element instanceof NodeElement) - return getNodeForeground(((NodeElement) element).getNode()); - else - throw new IllegalArgumentException("Unsupported element type " + element.getClass()); - } catch (RepositoryException e) { - throw new IllegalStateException("Repository exception when accessing " + element, e); - } - } - -} diff --git a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeElement.java b/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeElement.java deleted file mode 100644 index 787c92ed5..000000000 --- a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeElement.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.argeo.eclipse.ui.jcr; - -import javax.jcr.Node; - -/** An element which is related to a JCR {@link Node}. */ -public interface NodeElement { - Node getNode(); -} diff --git a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeElementComparer.java b/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeElementComparer.java deleted file mode 100644 index 2f3d64d6b..000000000 --- a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodeElementComparer.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.argeo.eclipse.ui.jcr; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.eclipse.ui.EclipseUiException; -import org.eclipse.jface.viewers.IElementComparer; - -/** Element comparer for JCR node, to be used in JFace viewers. */ -public class NodeElementComparer implements IElementComparer { - - public boolean equals(Object a, Object b) { - try { - if ((a instanceof Node) && (b instanceof Node)) { - Node nodeA = (Node) a; - Node nodeB = (Node) b; - return nodeA.getIdentifier().equals(nodeB.getIdentifier()); - } else { - return a.equals(b); - } - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot compare nodes", e); - } - } - - public int hashCode(Object element) { - try { - if (element instanceof Node) - return ((Node) element).getIdentifier().hashCode(); - return element.hashCode(); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot get hash code", e); - } - } - -} diff --git a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodesWrapper.java b/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodesWrapper.java deleted file mode 100644 index 2f808a51f..000000000 --- a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/NodesWrapper.java +++ /dev/null @@ -1,71 +0,0 @@ -package org.argeo.eclipse.ui.jcr; - -import java.util.ArrayList; -import java.util.List; - -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.RepositoryException; - -import org.argeo.eclipse.ui.EclipseUiException; - -/** - * Element of tree which is based on a node, but whose children are not - * necessarily this node children. - */ -public class NodesWrapper { - private final Node node; - - public NodesWrapper(Node node) { - super(); - this.node = node; - } - - protected NodeIterator getNodeIterator() throws RepositoryException { - return node.getNodes(); - } - - protected List getWrappedNodes() throws RepositoryException { - List nodes = new ArrayList(); - for (NodeIterator nit = getNodeIterator(); nit.hasNext();) - nodes.add(new WrappedNode(this, nit.nextNode())); - return nodes; - } - - public Object[] getChildren() { - try { - return getWrappedNodes().toArray(); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot get wrapped children", e); - } - } - - /** - * @return true by default because we don't want to compute the wrapped - * nodes twice - */ - public Boolean hasChildren() { - return true; - } - - public Node getNode() { - return node; - } - - @Override - public int hashCode() { - return node.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof NodesWrapper) - return node.equals(((NodesWrapper) obj).getNode()); - else - return false; - } - - public String toString() { - return "nodes wrapper based on " + node; - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/QueryTableContentProvider.java b/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/QueryTableContentProvider.java deleted file mode 100644 index 934fa6781..000000000 --- a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/QueryTableContentProvider.java +++ /dev/null @@ -1,35 +0,0 @@ -package org.argeo.eclipse.ui.jcr; - -import javax.jcr.NodeIterator; -import javax.jcr.RepositoryException; -import javax.jcr.query.Query; - -import org.argeo.jcr.JcrException; -import org.argeo.jcr.JcrUtils; -import org.eclipse.jface.viewers.IStructuredContentProvider; -import org.eclipse.jface.viewers.Viewer; - -/** Content provider based on a JCR {@link Query}. */ -public class QueryTableContentProvider implements IStructuredContentProvider { - private static final long serialVersionUID = 760371460907204722L; - - @Override - public void dispose() { - } - - @Override - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - } - - @Override - public Object[] getElements(Object inputElement) { - Query query = (Query) inputElement; - try { - NodeIterator nit = query.execute().getNodes(); - return JcrUtils.nodeIteratorToList(nit).toArray(); - } catch (RepositoryException e) { - throw new JcrException(e); - } - } - -} diff --git a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/SimpleNodeContentProvider.java b/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/SimpleNodeContentProvider.java deleted file mode 100644 index cb235d76b..000000000 --- a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/SimpleNodeContentProvider.java +++ /dev/null @@ -1,59 +0,0 @@ -package org.argeo.eclipse.ui.jcr; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; -import javax.jcr.Session; - -import org.argeo.eclipse.ui.EclipseUiException; -import org.argeo.jcr.JcrUtils; - -/** Simple JCR node content provider taking a list of String as base path. */ -public class SimpleNodeContentProvider extends AbstractNodeContentProvider { - private static final long serialVersionUID = -8245193308831384269L; - private final List basePaths; - private Boolean mkdirs = false; - - public SimpleNodeContentProvider(Session session, String... basePaths) { - this(session, Arrays.asList(basePaths)); - } - - public SimpleNodeContentProvider(Session session, List basePaths) { - super(session); - this.basePaths = basePaths; - } - - @Override - protected Boolean isBasePath(String path) { - if (basePaths.contains(path)) - return true; - return super.isBasePath(path); - } - - public Object[] getElements(Object inputElement) { - try { - List baseNodes = new ArrayList(); - for (String basePath : basePaths) - if (mkdirs && !getSession().itemExists(basePath)) - baseNodes.add(JcrUtils.mkdirs(getSession(), basePath)); - else - baseNodes.add(getSession().getNode(basePath)); - return baseNodes.toArray(); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot get base nodes for " + basePaths, - e); - } - } - - public List getBasePaths() { - return basePaths; - } - - public void setMkdirs(Boolean mkdirs) { - this.mkdirs = mkdirs; - } - -} diff --git a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/VersionColumnLabelProvider.java b/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/VersionColumnLabelProvider.java deleted file mode 100644 index 1ce315429..000000000 --- a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/VersionColumnLabelProvider.java +++ /dev/null @@ -1,80 +0,0 @@ -package org.argeo.eclipse.ui.jcr; - -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.RepositoryException; -import javax.jcr.version.Version; - -import org.eclipse.jface.viewers.ColumnLabelProvider; -import org.eclipse.swt.graphics.Image; - -/** Simplifies writing JCR-based column label provider. */ -public class VersionColumnLabelProvider extends ColumnLabelProvider { - private static final long serialVersionUID = -6117690082313161159L; - - protected String getVersionText(Version version) throws RepositoryException { - return super.getText(version); - } - - protected String getVersionToolTipText(Version version) throws RepositoryException { - return super.getToolTipText(version); - } - - protected Image getVersionImage(Version version) throws RepositoryException { - return super.getImage(version); - } - - protected String getUserName(Version version) throws RepositoryException { - Node node = version.getFrozenNode(); - if(node.hasProperty(Property.JCR_LAST_MODIFIED_BY)) - return node.getProperty(Property.JCR_LAST_MODIFIED_BY).getString(); - if(node.hasProperty(Property.JCR_CREATED_BY)) - return node.getProperty(Property.JCR_CREATED_BY).getString(); - return null; - } - -// protected String getActivityTitle(Version version) throws RepositoryException { -// Node activity = getActivity(version); -// if (activity == null) -// return null; -// if (activity.hasProperty("jcr:activityTitle")) -// return activity.getProperty("jcr:activityTitle").getString(); -// else -// return activity.getName(); -// } -// -// protected Node getActivity(Version version) throws RepositoryException { -// if (version.hasProperty(Property.JCR_ACTIVITY)) { -// return version.getProperty(Property.JCR_ACTIVITY).getNode(); -// } else -// return null; -// } - - @Override - public String getText(Object element) { - try { - return getVersionText((Version) element); - } catch (RepositoryException e) { - throw new RuntimeException("Runtime repository exception when accessing " + element, e); - } - } - - @Override - public Image getImage(Object element) { - try { - return getVersionImage((Version) element); - } catch (RepositoryException e) { - throw new RuntimeException("Runtime repository exception when accessing " + element, e); - } - } - - @Override - public String getToolTipText(Object element) { - try { - return getVersionToolTipText((Version) element); - } catch (RepositoryException e) { - throw new RuntimeException("Runtime repository exception when accessing " + element, e); - } - } - -} diff --git a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/VersionHistoryContentProvider.java b/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/VersionHistoryContentProvider.java deleted file mode 100644 index 32e5d30c1..000000000 --- a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/VersionHistoryContentProvider.java +++ /dev/null @@ -1,27 +0,0 @@ -package org.argeo.eclipse.ui.jcr; - -import javax.jcr.version.VersionHistory; - -import org.argeo.jcr.Jcr; -import org.eclipse.jface.viewers.IStructuredContentProvider; -import org.eclipse.jface.viewers.Viewer; - -/** Content provider based on a {@link VersionHistory}. */ -public class VersionHistoryContentProvider implements IStructuredContentProvider { - private static final long serialVersionUID = -4921107883428887012L; - - @Override - public void dispose() { - } - - @Override - public void inputChanged(Viewer viewer, Object oldInput, Object newInput) { - } - - @Override - public Object[] getElements(Object inputElement) { - VersionHistory versionHistory = (VersionHistory) inputElement; - return Jcr.getLinearVersions(versionHistory).toArray(); - } - -} diff --git a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/WrappedNode.java b/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/WrappedNode.java deleted file mode 100644 index 43df1fe01..000000000 --- a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/WrappedNode.java +++ /dev/null @@ -1,41 +0,0 @@ -package org.argeo.eclipse.ui.jcr; - -import javax.jcr.Node; - -/** Wrap a node (created from a {@link NodesWrapper}) */ -public class WrappedNode { - private final NodesWrapper parent; - private final Node node; - - public WrappedNode(NodesWrapper parent, Node node) { - super(); - this.parent = parent; - this.node = node; - } - - public NodesWrapper getParent() { - return parent; - } - - public Node getNode() { - return node; - } - - public String toString() { - return "wrapped " + node; - } - - @Override - public int hashCode() { - return node.hashCode(); - } - - @Override - public boolean equals(Object obj) { - if (obj instanceof WrappedNode) - return node.equals(((WrappedNode) obj).getNode()); - else - return false; - } - -} diff --git a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/JcrColumnDefinition.java b/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/JcrColumnDefinition.java deleted file mode 100644 index c5dd73317..000000000 --- a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/JcrColumnDefinition.java +++ /dev/null @@ -1,116 +0,0 @@ -package org.argeo.eclipse.ui.jcr.lists; - -import javax.jcr.Node; -import javax.jcr.query.Row; - -import org.argeo.eclipse.ui.ColumnDefinition; - -/** - * Utility object to manage column in various tables and extracts displaying - * data from JCR - */ -public class JcrColumnDefinition extends ColumnDefinition { - private final static int DEFAULT_COLUMN_SIZE = 120; - - private String selectorName; - private String propertyName; - private int propertyType; - private int columnSize; - - /** - * Use this kind of columns to configure a table that displays JCR - * {@link Row} - * - * @param selectorName - * @param propertyName - * @param propertyType - * @param headerLabel - */ - public JcrColumnDefinition(String selectorName, String propertyName, - int propertyType, String headerLabel) { - super(new SimpleJcrRowLabelProvider(selectorName, propertyName), - headerLabel); - this.selectorName = selectorName; - this.propertyName = propertyName; - this.propertyType = propertyType; - this.columnSize = DEFAULT_COLUMN_SIZE; - } - - /** - * Use this kind of columns to configure a table that displays JCR - * {@link Row} - * - * @param selectorName - * @param propertyName - * @param propertyType - * @param headerLabel - * @param columnSize - */ - public JcrColumnDefinition(String selectorName, String propertyName, - int propertyType, String headerLabel, int columnSize) { - super(new SimpleJcrRowLabelProvider(selectorName, propertyName), - headerLabel, columnSize); - this.selectorName = selectorName; - this.propertyName = propertyName; - this.propertyType = propertyType; - this.columnSize = columnSize; - } - - /** - * Use this kind of columns to configure a table that displays JCR - * {@link Node} - * - * @param propertyName - * @param propertyType - * @param headerLabel - * @param columnSize - */ - public JcrColumnDefinition(String propertyName, int propertyType, - String headerLabel, int columnSize) { - super(new SimpleJcrNodeLabelProvider(propertyName), headerLabel, - columnSize); - this.propertyName = propertyName; - this.propertyType = propertyType; - this.columnSize = columnSize; - } - - public String getSelectorName() { - return selectorName; - } - - public void setSelectorName(String selectorName) { - this.selectorName = selectorName; - } - - public String getPropertyName() { - return propertyName; - } - - public void setPropertyName(String propertyName) { - this.propertyName = propertyName; - } - - public int getPropertyType() { - return propertyType; - } - - public void setPropertyType(int propertyType) { - this.propertyType = propertyType; - } - - public int getColumnSize() { - return columnSize; - } - - public void setColumnSize(int columnSize) { - this.columnSize = columnSize; - } - - public String getHeaderLabel() { - return super.getLabel(); - } - - public void setHeaderLabel(String headerLabel) { - super.setLabel(headerLabel); - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/NodeViewerComparator.java b/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/NodeViewerComparator.java deleted file mode 100644 index 341b3abee..000000000 --- a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/NodeViewerComparator.java +++ /dev/null @@ -1,190 +0,0 @@ -package org.argeo.eclipse.ui.jcr.lists; - -import java.math.BigDecimal; -import java.util.Calendar; - -import javax.jcr.Node; -import javax.jcr.PropertyType; -import javax.jcr.RepositoryException; -import javax.jcr.Value; -import javax.jcr.ValueFormatException; - -import org.argeo.eclipse.ui.EclipseUiException; -import org.eclipse.jface.viewers.Viewer; -import org.eclipse.jface.viewers.ViewerComparator; - -/** - * Base comparator to enable ordering on Table or Tree viewer that display Jcr - * Nodes. - * - * Note that the following snippet must be added before setting the comparator - * to the corresponding control: - * // IMPORTANT: initialize comparator before setting it - * JcrColumnDefinition firstCol = colDefs.get(0); - * comparator.setColumn(firstCol.getPropertyType(), - * firstCol.getPropertyName()); - * viewer.setComparator(comparator); - */ -public class NodeViewerComparator extends ViewerComparator { - private static final long serialVersionUID = -7782916140737279027L; - - protected String propertyName; - - protected int propertyType; - public static final int ASCENDING = 0, DESCENDING = 1; - protected int direction = DESCENDING; - - public NodeViewerComparator() { - } - - /** - * e1 and e2 must both be Jcr nodes. - * - * @param viewer - * @param e1 - * @param e2 - * @return - */ - @Override - public int compare(Viewer viewer, Object e1, Object e2) { - int rc = 0; - long lc = 0; - - try { - Node n1 = (Node) e1; - Node n2 = (Node) e2; - - Value v1 = null; - Value v2 = null; - if (n1.hasProperty(propertyName)) - v1 = n1.getProperty(propertyName).getValue(); - if (n2.hasProperty(propertyName)) - v2 = n2.getProperty(propertyName).getValue(); - - if (v2 == null && v1 == null) - return 0; - else if (v2 == null) - return -1; - else if (v1 == null) - return 1; - - switch (propertyType) { - case PropertyType.STRING: - rc = v1.getString().compareTo(v2.getString()); - break; - case PropertyType.BOOLEAN: - boolean b1 = v1.getBoolean(); - boolean b2 = v2.getBoolean(); - if (b1 == b2) - rc = 0; - else - // we assume true is greater than false - rc = b1 ? 1 : -1; - break; - case PropertyType.DATE: - Calendar c1 = v1.getDate(); - Calendar c2 = v2.getDate(); - if (c1 == null || c2 == null) - // log.trace("undefined date"); - ; - lc = c1.getTimeInMillis() - c2.getTimeInMillis(); - if (lc < Integer.MIN_VALUE) - rc = -1; - else if (lc > Integer.MAX_VALUE) - rc = 1; - else - rc = (int) lc; - break; - case PropertyType.LONG: - long l1; - long l2; - // TODO Sometimes an empty string is set instead of a long - try { - l1 = v1.getLong(); - } catch (ValueFormatException ve) { - l1 = 0; - } - try { - l2 = v2.getLong(); - } catch (ValueFormatException ve) { - l2 = 0; - } - - lc = l1 - l2; - if (lc < Integer.MIN_VALUE) - rc = -1; - else if (lc > Integer.MAX_VALUE) - rc = 1; - else - rc = (int) lc; - break; - case PropertyType.DECIMAL: - BigDecimal bd1 = v1.getDecimal(); - BigDecimal bd2 = v2.getDecimal(); - rc = bd1.compareTo(bd2); - break; - case PropertyType.DOUBLE: - Double d1 = v1.getDouble(); - Double d2 = v2.getDouble(); - rc = d1.compareTo(d2); - break; - default: - throw new EclipseUiException( - "Unimplemented comparaison for PropertyType " - + propertyType); - } - // If descending order, flip the direction - if (direction == DESCENDING) { - rc = -rc; - } - - } catch (RepositoryException re) { - throw new EclipseUiException("Unexpected error " - + "while comparing nodes", re); - } - return rc; - } - - /** - * @param propertyType - * Corresponding JCR type - * @param propertyName - * name of the property to use. - */ - public void setColumn(int propertyType, String propertyName) { - if (this.propertyName != null && this.propertyName.equals(propertyName)) { - // Same column as last sort; toggle the direction - direction = 1 - direction; - } else { - // New column; do an ascending sort - this.propertyType = propertyType; - this.propertyName = propertyName; - direction = ASCENDING; - } - } - - // Getters and setters - protected String getPropertyName() { - return propertyName; - } - - protected void setPropertyName(String propertyName) { - this.propertyName = propertyName; - } - - protected int getPropertyType() { - return propertyType; - } - - protected void setPropertyType(int propertyType) { - this.propertyType = propertyType; - } - - protected int getDirection() { - return direction; - } - - protected void setDirection(int direction) { - this.direction = direction; - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/RowViewerComparator.java b/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/RowViewerComparator.java deleted file mode 100644 index 455fb0dcd..000000000 --- a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/RowViewerComparator.java +++ /dev/null @@ -1,62 +0,0 @@ -package org.argeo.eclipse.ui.jcr.lists; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; -import javax.jcr.query.Row; - -import org.argeo.eclipse.ui.EclipseUiException; -import org.eclipse.jface.viewers.Viewer; - -/** - * Base comparator to enable ordering on Table or Tree viewer that display Jcr - * rows - */ -public class RowViewerComparator extends NodeViewerComparator { - private static final long serialVersionUID = 7020939505172625113L; - protected String selectorName; - - public RowViewerComparator() { - } - - /** - * e1 and e2 must both be Jcr rows. - * - * @param viewer - * @param e1 - * @param e2 - * @return - */ - @Override - public int compare(Viewer viewer, Object e1, Object e2) { - try { - Node n1 = ((Row) e1).getNode(selectorName); - Node n2 = ((Row) e2).getNode(selectorName); - return super.compare(viewer, n1, n2); - } catch (RepositoryException re) { - throw new EclipseUiException("Unexpected error " - + "while comparing nodes", re); - } - } - - /** - * @param propertyType - * Corresponding JCR type - * @param propertyName - * name of the property to use. - */ - public void setColumn(int propertyType, String selectorName, - String propertyName) { - if (this.selectorName != null && getPropertyName() != null - && this.selectorName.equals(selectorName) - && this.getPropertyName().equals(propertyName)) { - // Same column as last sort; toggle the direction - setDirection(1 - getDirection()); - } else { - // New column; do a descending sort - setPropertyType(propertyType); - setPropertyName(propertyName); - this.selectorName = selectorName; - setDirection(NodeViewerComparator.ASCENDING); - } - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/SimpleJcrNodeLabelProvider.java b/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/SimpleJcrNodeLabelProvider.java deleted file mode 100644 index aa2e3375c..000000000 --- a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/SimpleJcrNodeLabelProvider.java +++ /dev/null @@ -1,120 +0,0 @@ -package org.argeo.eclipse.ui.jcr.lists; - -import java.text.DateFormat; -import java.text.DecimalFormat; -import java.text.NumberFormat; -import java.text.SimpleDateFormat; - -import javax.jcr.Node; -import javax.jcr.PropertyType; -import javax.jcr.RepositoryException; -import javax.jcr.Value; - -import org.argeo.eclipse.ui.EclipseUiException; -import org.eclipse.jface.viewers.ColumnLabelProvider; - -/** Base implementation of a label provider for controls that display JCR Nodes */ -public class SimpleJcrNodeLabelProvider extends ColumnLabelProvider { - private static final long serialVersionUID = -5215787695436221993L; - - private final static String DEFAULT_DATE_FORMAT = "EEE, dd MMM yyyy"; - private final static String DEFAULT_NUMBER_FORMAT = "#,##0.0"; - - private DateFormat dateFormat; - private NumberFormat numberFormat; - - final private String propertyName; - - /** - * Default Label provider for a given property of a node. Using default - * pattern for date and number formating - */ - public SimpleJcrNodeLabelProvider(String propertyName) { - this.propertyName = propertyName; - dateFormat = new SimpleDateFormat(DEFAULT_DATE_FORMAT); - numberFormat = DecimalFormat.getInstance(); - ((DecimalFormat) numberFormat).applyPattern(DEFAULT_NUMBER_FORMAT); - } - - /** - * Label provider for a given property of a node optionally precising date - * and/or number format patterns - */ - public SimpleJcrNodeLabelProvider(String propertyName, - String dateFormatPattern, String numberFormatPattern) { - this.propertyName = propertyName; - dateFormat = new SimpleDateFormat( - dateFormatPattern == null ? DEFAULT_DATE_FORMAT - : dateFormatPattern); - numberFormat = DecimalFormat.getInstance(); - ((DecimalFormat) numberFormat) - .applyPattern(numberFormatPattern == null ? DEFAULT_NUMBER_FORMAT - : numberFormatPattern); - } - - @Override - public String getText(Object element) { - try { - Node currNode = (Node) element; - - if (currNode.hasProperty(propertyName)) { - if (currNode.getProperty(propertyName).isMultiple()) { - StringBuilder builder = new StringBuilder(); - for (Value value : currNode.getProperty(propertyName) - .getValues()) { - String currStr = getSingleValueAsString(value); - if (notEmptyString(currStr)) - builder.append(currStr).append("; "); - } - if (builder.length() > 0) - builder.deleteCharAt(builder.length() - 2); - - return builder.toString(); - } else - return getSingleValueAsString(currNode.getProperty( - propertyName).getValue()); - } else - return ""; - } catch (RepositoryException re) { - throw new EclipseUiException("Unable to get text from row", re); - } - } - - private String getSingleValueAsString(Value value) - throws RepositoryException { - switch (value.getType()) { - case PropertyType.STRING: - return value.getString(); - case PropertyType.BOOLEAN: - return "" + value.getBoolean(); - case PropertyType.DATE: - return dateFormat.format(value.getDate().getTime()); - case PropertyType.LONG: - return "" + value.getLong(); - case PropertyType.DECIMAL: - return numberFormat.format(value.getDecimal()); - case PropertyType.DOUBLE: - return numberFormat.format(value.getDouble()); - case PropertyType.NAME: - return value.getString(); - default: - throw new EclipseUiException("Unimplemented label provider " - + "for property type " + value.getType() - + " while getting property " + propertyName + " - value: " - + value.getString()); - - } - } - - private boolean notEmptyString(String string) { - return string != null && !"".equals(string.trim()); - } - - public void setDateFormat(String dateFormatPattern) { - dateFormat = new SimpleDateFormat(dateFormatPattern); - } - - public void setNumberFormat(String numberFormatPattern) { - ((DecimalFormat) numberFormat).applyPattern(numberFormatPattern); - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/SimpleJcrRowLabelProvider.java b/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/SimpleJcrRowLabelProvider.java deleted file mode 100644 index 5d421f64b..000000000 --- a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/SimpleJcrRowLabelProvider.java +++ /dev/null @@ -1,47 +0,0 @@ -package org.argeo.eclipse.ui.jcr.lists; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; -import javax.jcr.query.Row; - -import org.argeo.eclipse.ui.EclipseUiException; - -/** - * Base implementation of a label provider for widgets that display JCR Rows. - */ -public class SimpleJcrRowLabelProvider extends SimpleJcrNodeLabelProvider { - private static final long serialVersionUID = -3414654948197181740L; - - final private String selectorName; - - /** - * Default Label provider for a given property of a row. Using default - * pattern for date and number formating - */ - public SimpleJcrRowLabelProvider(String selectorName, String propertyName) { - super(propertyName); - this.selectorName = selectorName; - } - - /** - * Label provider for a given property of a node optionally precising date - * and/or number format patterns - */ - public SimpleJcrRowLabelProvider(String selectorName, String propertyName, - String dateFormatPattern, String numberFormatPattern) { - super(propertyName, dateFormatPattern, numberFormatPattern); - this.selectorName = selectorName; - } - - @Override - public String getText(Object element) { - try { - Row currRow = (Row) element; - Node currNode = currRow.getNode(selectorName); - return super.getText(currNode); - } catch (RepositoryException re) { - throw new EclipseUiException("Unable to get Node " + selectorName - + " from row " + element, re); - } - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/package-info.java b/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/package-info.java deleted file mode 100644 index 3678aab88..000000000 --- a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/lists/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Generic SWT/JFace JCR utilities for lists. */ -package org.argeo.eclipse.ui.jcr.lists; \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/package-info.java b/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/package-info.java deleted file mode 100644 index 19e3cc3ff..000000000 --- a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Generic SWT/JFace JCR utilities. */ -package org.argeo.eclipse.ui.jcr; \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/JcrFileProvider.java b/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/JcrFileProvider.java deleted file mode 100644 index c82e666d1..000000000 --- a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/JcrFileProvider.java +++ /dev/null @@ -1,129 +0,0 @@ -package org.argeo.eclipse.ui.jcr.util; - -import java.io.InputStream; - -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.RepositoryException; -import javax.jcr.nodetype.NodeType; - -import org.apache.commons.io.IOUtils; -import org.argeo.eclipse.ui.EclipseUiException; -import org.argeo.eclipse.ui.FileProvider; - -/** - * Implements a FileProvider for UI purposes. Note that it might not be very - * reliable as long as we have not fixed login and multi repository issues that - * will be addressed in the next version. - * - * NOTE: id used here is the real id of the JCR Node, not the JCR Path - * - * Relies on common approach for JCR file handling implementation. - * - */ -@SuppressWarnings("deprecation") -public class JcrFileProvider implements FileProvider { - - // private Object[] rootNodes; - private Node refNode; - - /** - * Must be set in order for the provider to be able to get current session - * and thus have the ability to get the file node corresponding to a given - * file ID - * - * @param refNode - */ - public void setReferenceNode(Node refNode) { - // FIXME : this introduces some concurrency ISSUES. - this.refNode = refNode; - } - - public byte[] getByteArrayFileFromId(String fileId) { - InputStream fis = null; - byte[] ba = null; - Node child = getFileNodeFromId(fileId); - try { - fis = (InputStream) child.getProperty(Property.JCR_DATA).getBinary().getStream(); - ba = IOUtils.toByteArray(fis); - - } catch (Exception e) { - throw new EclipseUiException("Stream error while opening file", e); - } finally { - IOUtils.closeQuietly(fis); - } - return ba; - } - - public InputStream getInputStreamFromFileId(String fileId) { - try { - InputStream fis = null; - - Node child = getFileNodeFromId(fileId); - fis = (InputStream) child.getProperty(Property.JCR_DATA).getBinary().getStream(); - return fis; - } catch (RepositoryException re) { - throw new EclipseUiException("Cannot get stream from file node for Id " + fileId, re); - } - } - - /** - * Throws an exception if the node is not found in the current repository (a - * bit like a FileNotFoundException) - * - * @param fileId - * @return Returns the child node of the nt:file node. It is the child node - * that have the jcr:data property where actual file is stored. - * never null - */ - private Node getFileNodeFromId(String fileId) { - try { - Node result = refNode.getSession().getNodeByIdentifier(fileId); - - // rootNodes: for (int j = 0; j < rootNodes.length; j++) { - // // in case we have a classic JCR Node - // if (rootNodes[j] instanceof Node) { - // Node curNode = (Node) rootNodes[j]; - // if (result != null) - // break rootNodes; - // } // Case of a repository Node - // else if (rootNodes[j] instanceof RepositoryNode) { - // Object[] nodes = ((RepositoryNode) rootNodes[j]) - // .getChildren(); - // for (int i = 0; i < nodes.length; i++) { - // Node node = (Node) nodes[i]; - // result = node.getSession().getNodeByIdentifier(fileId); - // if (result != null) - // break rootNodes; - // } - // } - // } - - // Sanity checks - if (result == null) - throw new EclipseUiException("File node not found for ID" + fileId); - - Node child = null; - - boolean isValid = true; - if (!result.isNodeType(NodeType.NT_FILE)) - // useless: mandatory child node - // || !result.hasNode(Property.JCR_CONTENT)) - isValid = false; - else { - child = result.getNode(Property.JCR_CONTENT); - if (!(child.isNodeType(NodeType.NT_RESOURCE) || child.hasProperty(Property.JCR_DATA))) - isValid = false; - } - - if (!isValid) - throw new EclipseUiException("ERROR: In the current implemented model, '" + NodeType.NT_FILE - + "' file node must have a child node named jcr:content " - + "that has a BINARY Property named jcr:data " + "where the actual data is stored"); - return child; - - } catch (RepositoryException re) { - throw new EclipseUiException("Erreur while getting file node of ID " + fileId, re); - } - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/JcrItemsComparator.java b/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/JcrItemsComparator.java deleted file mode 100644 index fb123992f..000000000 --- a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/JcrItemsComparator.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.argeo.eclipse.ui.jcr.util; - -import java.util.Comparator; - -import javax.jcr.Item; -import javax.jcr.RepositoryException; - -import org.argeo.eclipse.ui.EclipseUiException; - -/** Compares two JCR items (node or properties) based on their names. */ -public class JcrItemsComparator implements Comparator { - public int compare(Item o1, Item o2) { - try { - // TODO: put folder before files - return o1.getName().toLowerCase().compareTo(o2.getName().toLowerCase()); - } catch (RepositoryException e) { - throw new EclipseUiException("Cannot compare " + o1 + " and " + o2, e); - } - } - -} diff --git a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/NodeViewerComparer.java b/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/NodeViewerComparer.java deleted file mode 100644 index 54b795f3b..000000000 --- a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/NodeViewerComparer.java +++ /dev/null @@ -1,36 +0,0 @@ -package org.argeo.eclipse.ui.jcr.util; - -import javax.jcr.Node; -import javax.jcr.RepositoryException; - -import org.argeo.eclipse.ui.EclipseUiException; -import org.eclipse.jface.viewers.IElementComparer; - -/** Compare JCR nodes based on their JCR identifiers, for use in JFace viewers. */ -public class NodeViewerComparer implements IElementComparer { - - // force comparison on Node IDs only. - public boolean equals(Object elementA, Object elementB) { - if (!(elementA instanceof Node) || !(elementB instanceof Node)) { - return elementA == null ? elementB == null : elementA - .equals(elementB); - } else { - - boolean result = false; - try { - String idA = ((Node) elementA).getIdentifier(); - String idB = ((Node) elementB).getIdentifier(); - result = idA == null ? idB == null : idA.equals(idB); - } catch (RepositoryException re) { - throw new EclipseUiException("cannot compare nodes", re); - } - - return result; - } - } - - public int hashCode(Object element) { - // TODO enhanced this method. - return element.getClass().toString().hashCode(); - } -} \ No newline at end of file diff --git a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/SingleSessionFileProvider.java b/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/SingleSessionFileProvider.java deleted file mode 100644 index 291d579ac..000000000 --- a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/SingleSessionFileProvider.java +++ /dev/null @@ -1,98 +0,0 @@ -package org.argeo.eclipse.ui.jcr.util; - -import java.io.InputStream; - -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.nodetype.NodeType; - -import org.apache.commons.io.IOUtils; -import org.argeo.eclipse.ui.EclipseUiException; -import org.argeo.eclipse.ui.FileProvider; - -/** - * Implements a FileProvider for UI purposes. Unlike the - * JcrFileProvider , it relies on a single session and manages - * nodes with path only. - * - * Note that considered id is the JCR path - * - * Relies on common approach for JCR file handling implementation. - */ -@SuppressWarnings("deprecation") -public class SingleSessionFileProvider implements FileProvider { - - private Session session; - - public SingleSessionFileProvider(Session session) { - this.session = session; - } - - public byte[] getByteArrayFileFromId(String fileId) { - InputStream fis = null; - byte[] ba = null; - Node child = getFileNodeFromId(fileId); - try { - fis = (InputStream) child.getProperty(Property.JCR_DATA) - .getBinary().getStream(); - ba = IOUtils.toByteArray(fis); - - } catch (Exception e) { - throw new EclipseUiException("Stream error while opening file", e); - } finally { - IOUtils.closeQuietly(fis); - } - return ba; - } - - public InputStream getInputStreamFromFileId(String fileId) { - try { - InputStream fis = null; - - Node child = getFileNodeFromId(fileId); - fis = (InputStream) child.getProperty(Property.JCR_DATA) - .getBinary().getStream(); - return fis; - } catch (RepositoryException re) { - throw new EclipseUiException("Cannot get stream from file node for Id " - + fileId, re); - } - } - - /** - * - * @param fileId - * @return Returns the child node of the nt:file node. It is the child node - * that have the jcr:data property where actual file is stored. - * never null - */ - private Node getFileNodeFromId(String fileId) { - try { - Node result = null; - result = session.getNode(fileId); - - // Sanity checks - if (result == null) - throw new EclipseUiException("File node not found for ID" + fileId); - - // Ensure that the node have the correct type. - if (!result.isNodeType(NodeType.NT_FILE)) - throw new EclipseUiException( - "Cannot open file children Node that are not of " - + NodeType.NT_RESOURCE + " type."); - - Node child = result.getNodes().nextNode(); - if (child == null || !child.isNodeType(NodeType.NT_RESOURCE)) - throw new EclipseUiException( - "ERROR: IN the current implemented model, " - + NodeType.NT_FILE - + " file node must have one and only one child of the nt:ressource, where actual data is stored"); - return child; - } catch (RepositoryException re) { - throw new EclipseUiException("Erreur while getting file node of ID " - + fileId, re); - } - } -} diff --git a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/package-info.java b/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/package-info.java deleted file mode 100644 index 016348cda..000000000 --- a/org.argeo.cms.ui/src/org/argeo/eclipse/ui/jcr/util/package-info.java +++ /dev/null @@ -1,2 +0,0 @@ -/** Generic SWT/JFace JCR helpers. */ -package org.argeo.eclipse.ui.jcr.util; \ No newline at end of file diff --git a/pom.xml b/pom.xml index 0a265cf64..9221b4720 100644 --- a/pom.xml +++ b/pom.xml @@ -30,12 +30,10 @@ org.argeo.cms.tp org.argeo.cms org.argeo.cms.pgsql - org.argeo.cms.servlet - org.argeo.cms.jcr - - org.argeo.cms.swt - org.argeo.cms.ui - org.argeo.cms.e4 + + eclipse + + jcr rcp rap