From: Mathieu Baudier Date: Thu, 23 Mar 2023 04:18:06 +0000 (+0100) Subject: Move Eclipse E4 components from Argeo Commons to Argeo SLC X-Git-Tag: v2.3.9~7 X-Git-Url: https://git.argeo.org/?p=gpl%2Fargeo-slc.git;a=commitdiff_plain;h=b65cc641c711d646821a9cce3ab4b40d42af9cea Move Eclipse E4 components from Argeo Commons to Argeo SLC --- diff --git a/Makefile b/Makefile index 786928a83..f73c664d7 100644 --- a/Makefile +++ b/Makefile @@ -18,7 +18,9 @@ org.argeo.slc.rpmfactory \ org.argeo.slc.jcr \ lib/linux/org.argeo.slc.systemd \ swt/org.argeo.tool.swt \ +swt/org.argeo.cms.e4 \ swt/org.argeo.tool.devops.e4 \ +swt/rap/org.argeo.cms.e4.rap \ swt/rap/org.argeo.tool.rap.cli \ swt/rap/org.argeo.tool.server \ diff --git a/Makefile-rcp.mk b/Makefile-rcp.mk index 8f19b1af2..eb3fc7042 100644 --- a/Makefile-rcp.mk +++ b/Makefile-rcp.mk @@ -6,18 +6,23 @@ all: osgi A2_CATEGORY = org.argeo.slc BUNDLES = \ +swt/rcp/org.argeo.cms.e4.rcp \ swt/rcp/org.argeo.tool.rcp.cli \ swt/rcp/org.argeo.tool.desktop \ DEP_CATEGORIES = \ org.argeo.tp \ osgi/api/org.argeo.tp.osgi \ +osgi/equinox/org.argeo.tp.eclipse \ swt/rcp/org.argeo.tp.swt \ lib/linux/x86_64/swt/rcp/org.argeo.tp.swt \ swt/rcp/org.argeo.tp.swt.workbench \ org.argeo.cms \ swt/org.argeo.cms \ swt/rcp/org.argeo.cms \ +$(A2_CATEGORY) \ +swt/$(A2_CATEGORY) \ +swt/rcp/$(A2_CATEGORY) \ clean: rm -rf $(BUILD_BASE) diff --git a/swt/org.argeo.cms.e4/.classpath b/swt/org.argeo.cms.e4/.classpath new file mode 100644 index 000000000..81fe078c2 --- /dev/null +++ b/swt/org.argeo.cms.e4/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/swt/org.argeo.cms.e4/.project b/swt/org.argeo.cms.e4/.project new file mode 100644 index 000000000..0c0406952 --- /dev/null +++ b/swt/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/swt/org.argeo.cms.e4/OSGI-INF/defaultCallbackHandler.xml b/swt/org.argeo.cms.e4/OSGI-INF/defaultCallbackHandler.xml new file mode 100644 index 000000000..fcd3ae5cb --- /dev/null +++ b/swt/org.argeo.cms.e4/OSGI-INF/defaultCallbackHandler.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/swt/org.argeo.cms.e4/bnd.bnd b/swt/org.argeo.cms.e4/bnd.bnd new file mode 100644 index 000000000..8839805c1 --- /dev/null +++ b/swt/org.argeo.cms.e4/bnd.bnd @@ -0,0 +1,20 @@ +Service-Component: OSGI-INF/defaultCallbackHandler.xml +Bundle-ActivationPolicy: lazy + +Import-Package: \ +org.argeo.api.acr,\ +org.eclipse.swt,\ +org.eclipse.swt.widgets;version="0.0.0",\ +org.eclipse.e4.ui.model.application.ui;resolution:=optional,\ +org.eclipse.e4.ui.model.application;resolution:=optional,\ +org.argeo.cms,\ +org.eclipse.core.commands.common,\ +org.eclipse.jface.window,\ +org.eclipse.jface.dialogs,\ +org.argeo.cms.swt.auth,\ +org.argeo.cms.ux.widgets,\ +javax.servlet.*;version="[3,5)",\ +org.eclipse.*;resolution:=optional,\ +javax.*;resolution:=optional,\ +* + diff --git a/swt/org.argeo.cms.e4/build.properties b/swt/org.argeo.cms.e4/build.properties new file mode 100644 index 000000000..e46a7baee --- /dev/null +++ b/swt/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/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/CmsE4Utils.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/CmsE4Utils.java new file mode 100644 index 000000000..a997de748 --- /dev/null +++ b/swt/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.swt.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/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/OsgiFilterContextFunction.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/OsgiFilterContextFunction.java new file mode 100644 index 000000000..1e3e75cec --- /dev/null +++ b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/OsgiFilterContextFunction.java @@ -0,0 +1,33 @@ +package org.argeo.cms.e4; + +import org.argeo.cms.swt.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/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/PrivilegedJob.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/PrivilegedJob.java new file mode 100644 index 000000000..89055d2ff --- /dev/null +++ b/swt/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/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/AuthAddon.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/AuthAddon.java new file mode 100644 index 000000000..66a5ec8c7 --- /dev/null +++ b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/AuthAddon.java @@ -0,0 +1,106 @@ +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.CurrentUser; +import org.argeo.cms.swt.CmsException; +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); + } + + // FIXME make it more generic + HttpServletRequest request = org.argeo.eclipse.ui.specific.UiContext.getHttpRequest(); + if (request != null) + request.getSession().setMaxInactiveInterval(0); + } + +} diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/LocaleAddon.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/LocaleAddon.java new file mode 100644 index 000000000..5bc0d6936 --- /dev/null +++ b/swt/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/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/package-info.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/addons/package-info.java new file mode 100644 index 000000000..6367b42d5 --- /dev/null +++ b/swt/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/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangeLanguage.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangeLanguage.java new file mode 100644 index 000000000..416df7df1 --- /dev/null +++ b/swt/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/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangePassword.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangePassword.java new file mode 100644 index 000000000..9624c2d70 --- /dev/null +++ b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/ChangePassword.java @@ -0,0 +1,136 @@ +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.api.cms.keyring.CryptoKeyring; +import org.argeo.api.cms.transaction.WorkTransaction; +import org.argeo.cms.CurrentUser; +import org.argeo.cms.swt.dialogs.CmsFeedback; +import org.argeo.cms.swt.dialogs.CmsMessageDialog; +import org.argeo.cms.ux.widgets.CmsDialog; +import org.eclipse.e4.core.di.annotations.Execute; +import org.eclipse.e4.core.di.annotations.Optional; +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() == CmsDialog.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 IllegalArgumentException("Invalid user dn " + name, e); + } + User user = (User) userAdmin.getRole(dn.toString()); + if (!user.hasCredential(null, oldPassword)) + throw new IllegalArgumentException("Invalid password"); + if (Arrays.equals(newPassword, new char[0])) + throw new IllegalArgumentException("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 IllegalStateException("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 IllegalArgumentException("New passwords are different"); + changePassword(oldPassword.getTextChars(), newPassword1.getTextChars()); + closeShell(CmsDialog.OK); + } catch (Exception e) { + CmsFeedback.error("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/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseAllParts.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseAllParts.java new file mode 100644 index 000000000..d11c0412c --- /dev/null +++ b/swt/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/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseWorkbench.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseWorkbench.java new file mode 100644 index 000000000..cce18020d --- /dev/null +++ b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/CloseWorkbench.java @@ -0,0 +1,26 @@ +package org.argeo.cms.e4.handlers; + +import javax.security.auth.Subject; + +import org.argeo.cms.CurrentUser; +import org.argeo.cms.util.CurrentSubject; +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 = CurrentSubject.current(); + try { + CurrentUser.logoutCmsSession(subject); + } catch (Exception e) { + throw new IllegalStateException("Cannot log out", e); + } + } + +} diff --git a/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/DoNothing.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/DoNothing.java new file mode 100644 index 000000000..358494c5b --- /dev/null +++ b/swt/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/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/LanguageMenuContribution.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/LanguageMenuContribution.java new file mode 100644 index 000000000..ac825bb0d --- /dev/null +++ b/swt/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/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/OpenPerspective.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/OpenPerspective.java new file mode 100644 index 000000000..ac544b107 --- /dev/null +++ b/swt/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/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SaveAllParts.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SaveAllParts.java new file mode 100644 index 000000000..3b60abd7e --- /dev/null +++ b/swt/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/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SavePart.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/SavePart.java new file mode 100644 index 000000000..73486f363 --- /dev/null +++ b/swt/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/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/package-info.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/handlers/package-info.java new file mode 100644 index 000000000..a44ca9056 --- /dev/null +++ b/swt/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/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/package-info.java b/swt/org.argeo.cms.e4/src/org/argeo/cms/e4/package-info.java new file mode 100644 index 000000000..233119c0d --- /dev/null +++ b/swt/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/swt/rap/org.argeo.cms.e4.rap/.classpath b/swt/rap/org.argeo.cms.e4.rap/.classpath new file mode 100644 index 000000000..81fe078c2 --- /dev/null +++ b/swt/rap/org.argeo.cms.e4.rap/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/swt/rap/org.argeo.cms.e4.rap/.project b/swt/rap/org.argeo.cms.e4.rap/.project new file mode 100644 index 000000000..40c9e013f --- /dev/null +++ b/swt/rap/org.argeo.cms.e4.rap/.project @@ -0,0 +1,33 @@ + + + org.argeo.cms.e4.rap + + + + + + 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/swt/rap/org.argeo.cms.e4.rap/META-INF/.gitignore b/swt/rap/org.argeo.cms.e4.rap/META-INF/.gitignore new file mode 100644 index 000000000..4854a41b9 --- /dev/null +++ b/swt/rap/org.argeo.cms.e4.rap/META-INF/.gitignore @@ -0,0 +1 @@ +/MANIFEST.MF diff --git a/swt/rap/org.argeo.cms.e4.rap/bnd.bnd b/swt/rap/org.argeo.cms.e4.rap/bnd.bnd new file mode 100644 index 000000000..6db081fc2 --- /dev/null +++ b/swt/rap/org.argeo.cms.e4.rap/bnd.bnd @@ -0,0 +1,9 @@ +Import-Package: \ +org.argeo.api.acr, \ +org.eclipse.swt,\ +org.eclipse.swt.graphics,\ +org.eclipse.e4.ui.workbench,\ +org.eclipse.rap.rwt.client,\ +org.eclipse.nebula.widgets.richtext;resolution:=optional,\ +org.eclipse.*;resolution:=optional,\ +* diff --git a/swt/rap/org.argeo.cms.e4.rap/build.properties b/swt/rap/org.argeo.cms.e4.rap/build.properties new file mode 100644 index 000000000..c58ea2178 --- /dev/null +++ b/swt/rap/org.argeo.cms.e4.rap/build.properties @@ -0,0 +1,5 @@ +source.. = src/ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + OSGI-INF/ diff --git a/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/AbstractRapE4App.java b/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/AbstractRapE4App.java new file mode 100644 index 000000000..81326f345 --- /dev/null +++ b/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/AbstractRapE4App.java @@ -0,0 +1,139 @@ +package org.argeo.cms.e4.rap; + +import java.util.HashMap; +import java.util.Map; + +import org.argeo.cms.swt.dialogs.CmsFeedback; +import org.eclipse.rap.e4.E4ApplicationConfig; +import org.eclipse.rap.rwt.application.Application; +import org.eclipse.rap.rwt.application.Application.OperationMode; +import org.eclipse.rap.rwt.application.ApplicationConfiguration; +import org.eclipse.rap.rwt.application.ExceptionHandler; +import org.eclipse.rap.rwt.client.WebClient; +import org.osgi.framework.BundleContext; + +/** Base class for CMS RAP applications. */ +public abstract class AbstractRapE4App implements ApplicationConfiguration { + private String e4Xmi; + private String path; + private String lifeCycleUri = "bundleclass://org.argeo.cms.e4.rap/org.argeo.cms.e4.rap.CmsLoginLifecycle"; + + private Map baseProperties = new HashMap(); + + private BundleContext bundleContext; + public final static String CONTEXT_NAME_PROPERTY = "contextName"; + private String contextName; + + /** + * To be overridden in order to add multiple entry points, directly or using + * {@link #addE4EntryPoint(Application, String, String, Map)}. + */ + protected void addEntryPoints(Application application) { + } + + public void configure(Application application) { + application.setExceptionHandler(new ExceptionHandler() { + + @Override + public void handleException(Throwable throwable) { + CmsFeedback.error("Unexpected RWT exception", throwable); + } + }); + + if (e4Xmi != null) {// backward compatibility + addE4EntryPoint(application, path, e4Xmi, getBaseProperties()); + } else { + addEntryPoints(application); + } + } + + protected Map getBaseProperties() { + return baseProperties; + } + +// protected void addEntryPoint(Application application, E4ApplicationConfig config, Map properties) { +// CmsE4EntryPointFactory entryPointFactory = new CmsE4EntryPointFactory(config); +// application.addEntryPoint(path, entryPointFactory, properties); +// application.setOperationMode(OperationMode.SWT_COMPATIBILITY); +// } + + protected void addE4EntryPoint(Application application, String path, String e4Xmi, Map properties) { + E4ApplicationConfig config = createE4ApplicationConfig(e4Xmi); + CmsE4EntryPointFactory entryPointFactory = new CmsE4EntryPointFactory(config); + application.addEntryPoint(path, entryPointFactory, properties); + application.setOperationMode(OperationMode.SWT_COMPATIBILITY); + } + + /** + * To be overridden for further configuration. + * + * @see E4ApplicationConfig + */ + protected E4ApplicationConfig createE4ApplicationConfig(String e4Xmi) { + return new E4ApplicationConfig(e4Xmi, lifeCycleUri, null, null, false, true, true); + } + + @Deprecated + public void setPageTitle(String pageTitle) { + if (pageTitle != null) + baseProperties.put(WebClient.PAGE_TITLE, pageTitle); + } + + /** Returns a new map used to customise and entry point. */ + public Map customise(String pageTitle) { + Map custom = new HashMap<>(getBaseProperties()); + if (pageTitle != null) + custom.put(WebClient.PAGE_TITLE, pageTitle); + return custom; + } + + @Deprecated + public void setE4Xmi(String e4Xmi) { + this.e4Xmi = e4Xmi; + } + + @Deprecated + public void setPath(String path) { + this.path = path; + } + + public void setLifeCycleUri(String lifeCycleUri) { + this.lifeCycleUri = lifeCycleUri; + } + + protected BundleContext getBundleContext() { + return bundleContext; + } + + protected void setBundleContext(BundleContext bundleContext) { + this.bundleContext = bundleContext; + } + + public String getContextName() { + return contextName; + } + + public void setContextName(String contextName) { + this.contextName = contextName; + } + + public void init(BundleContext bundleContext, Map properties) { + this.bundleContext = bundleContext; + for (String key : properties.keySet()) { + Object value = properties.get(key); + if (value != null) + baseProperties.put(key, value.toString()); + } + + if (properties.containsKey(CONTEXT_NAME_PROPERTY)) { + assert properties.get(CONTEXT_NAME_PROPERTY) != null; + contextName = properties.get(CONTEXT_NAME_PROPERTY).toString(); + } else { + contextName = ""; + } + } + + public void destroy(Map properties) { + + } +} diff --git a/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4EntryPointFactory.java b/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4EntryPointFactory.java new file mode 100644 index 000000000..a5a32348e --- /dev/null +++ b/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsE4EntryPointFactory.java @@ -0,0 +1,76 @@ +package org.argeo.cms.e4.rap; + +import java.security.PrivilegedAction; + +import javax.security.auth.Subject; + +import org.eclipse.rap.e4.E4ApplicationConfig; +import org.eclipse.rap.e4.E4EntryPointFactory; +import org.eclipse.rap.rwt.RWT; +import org.eclipse.rap.rwt.application.EntryPoint; +import org.eclipse.rap.rwt.client.service.JavaScriptExecutor; + +public class CmsE4EntryPointFactory extends E4EntryPointFactory { + public final static String DEFAULT_LIFECYCLE_URI = "bundleclass://org.argeo.cms.e4.rap/org.argeo.cms.e4.rap.CmsLoginLifecycle"; + + public CmsE4EntryPointFactory(E4ApplicationConfig config) { + super(config); + } + + public CmsE4EntryPointFactory(String e4Xmi, String lifeCycleUri) { + super(defaultConfig(e4Xmi, lifeCycleUri)); + } + + public CmsE4EntryPointFactory(String e4Xmi) { + this(e4Xmi, DEFAULT_LIFECYCLE_URI); + } + + public static E4ApplicationConfig defaultConfig(String e4Xmi, String lifeCycleUri) { + E4ApplicationConfig config = new E4ApplicationConfig(e4Xmi, lifeCycleUri, null, null, false, true, true); + return config; + } + + @Override + public EntryPoint create() { + EntryPoint ep = createEntryPoint(); + EntryPoint authEp = new EntryPoint() { + + @Override + public int createUI() { + Subject subject = new Subject(); + return Subject.doAs(subject, new PrivilegedAction() { + + @Override + public Integer run() { + // SPNEGO + // HttpServletRequest request = RWT.getRequest(); + // String authorization = request.getHeader(HEADER_AUTHORIZATION); + // if (authorization == null || !authorization.startsWith("Negotiate")) { + // HttpServletResponse response = RWT.getResponse(); + // response.setStatus(401); + // response.setHeader(HEADER_WWW_AUTHENTICATE, "Negotiate"); + // 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"); + // } + + JavaScriptExecutor jsExecutor = RWT.getClient().getService(JavaScriptExecutor.class); + Integer exitCode = ep.createUI(); + jsExecutor.execute("location.reload()"); + return exitCode; + } + + }); + } + }; + return authEp; + } + + protected EntryPoint createEntryPoint() { + return super.create(); + } +} diff --git a/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java b/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java new file mode 100644 index 000000000..cdd87fd3f --- /dev/null +++ b/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/CmsLoginLifecycle.java @@ -0,0 +1,193 @@ +package org.argeo.cms.e4.rap; + +import java.security.AccessController; +import java.util.UUID; +import java.util.concurrent.Callable; + +import javax.inject.Inject; +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.CmsContext; +import org.argeo.api.cms.CmsLog; +import org.argeo.api.cms.ux.CmsImageManager; +import org.argeo.api.cms.ux.CmsView; +import org.argeo.api.cms.ux.UxContext; +import org.argeo.cms.CurrentUser; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.swt.SimpleSwtUxContext; +import org.argeo.cms.swt.acr.AcrSwtImageManager; +import org.argeo.cms.swt.auth.CmsLoginShell; +import org.argeo.cms.swt.dialogs.CmsFeedback; +import org.eclipse.e4.core.services.events.IEventBroker; +import org.eclipse.e4.ui.workbench.UIEvents; +import org.eclipse.e4.ui.workbench.lifecycle.PostContextCreate; +import org.eclipse.e4.ui.workbench.lifecycle.PreSave; +import org.eclipse.rap.rwt.RWT; +import org.eclipse.rap.rwt.client.service.BrowserNavigation; +import org.eclipse.rap.rwt.client.service.BrowserNavigationEvent; +import org.eclipse.rap.rwt.client.service.BrowserNavigationListener; +import org.eclipse.swt.widgets.Display; +import org.osgi.service.event.Event; +import org.osgi.service.event.EventHandler; + +@SuppressWarnings("restriction") +public class CmsLoginLifecycle implements CmsView { + private final static CmsLog log = CmsLog.getLog(CmsLoginLifecycle.class); + + @Inject + private CmsContext cmsContext; + + private UxContext uxContext; + private CmsImageManager imageManager; + + private LoginContext loginContext; + private BrowserNavigation browserNavigation; + + private String state = null; + private String uid; + + @PostContextCreate + boolean login(final IEventBroker eventBroker) { + uid = UUID.randomUUID().toString(); + browserNavigation = RWT.getClient().getService(BrowserNavigation.class); + if (browserNavigation != null) + browserNavigation.addBrowserNavigationListener(new BrowserNavigationListener() { + private static final long serialVersionUID = -3668136623771902865L; + + @Override + public void navigated(BrowserNavigationEvent event) { + state = event.getState(); + if (uxContext != null)// is logged in + stateChanged(); + } + }); + + Subject subject = Subject.getSubject(AccessController.getContext()); + Display display = Display.getCurrent(); +// UiContext.setData(CmsView.KEY, this); + CmsLoginShell loginShell = new CmsLoginShell(this, cmsContext); + CmsSwtUtils.registerCmsView(loginShell.getShell(), this); + loginShell.setSubject(subject); + try { + // try pre-auth + loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_USER, subject, loginShell); + loginContext.login(); + } catch (LoginException e) { + loginShell.createUi(); + loginShell.open(); + + while (!loginShell.getShell().isDisposed()) { + if (!display.readAndDispatch()) + display.sleep(); + } + } + if (CurrentUser.getUsername(getSubject()) == null) + return false; + uxContext = new SimpleSwtUxContext(); + imageManager = (CmsImageManager) new AcrSwtImageManager(); + + eventBroker.subscribe(UIEvents.UILifeCycle.APP_STARTUP_COMPLETE, new EventHandler() { + @Override + public void handleEvent(Event event) { + startupComplete(); + eventBroker.unsubscribe(this); + } + }); + + // lcs.changeApplicationLocale(Locale.FRENCH); + return true; + } + + @PreSave + void destroy() { + // logout(); + } + + @Override + public UxContext getUxContext() { + return uxContext; + } + + @Override + public void navigateTo(String state) { + browserNavigation.pushState(state, state); + } + + @Override + public void authChange(LoginContext loginContext) { + if (loginContext == null) + throw new IllegalArgumentException("Login context cannot be null"); + // logout previous login context + // if (this.loginContext != null) + // try { + // this.loginContext.logout(); + // } catch (LoginException e1) { + // System.err.println("Could not log out: " + e1); + // } + this.loginContext = loginContext; + } + + @Override + public void logout() { + if (loginContext == null) + throw new IllegalStateException("Login context should not be null"); + try { + CurrentUser.logoutCmsSession(loginContext.getSubject()); + loginContext.logout(); + } catch (LoginException e) { + throw new IllegalStateException("Cannot log out", e); + } + } + + @Override + public void exception(Throwable e) { + String msg = "Unexpected exception in Eclipse 4 RAP"; + log.error(msg, e); + CmsFeedback.error(msg, e); + } + + @Override + public CmsImageManager getImageManager() { + return imageManager; + } + + protected Subject getSubject() { + return loginContext.getSubject(); + } + + @Override + public boolean isAnonymous() { + return CurrentUser.isAnonymous(getSubject()); + } + + @Override + public String getUid() { + return uid; + } + + // CALLBACKS + protected void startupComplete() { + } + + protected void stateChanged() { + + } + + // GETTERS + protected BrowserNavigation getBrowserNavigation() { + return browserNavigation; + } + + protected String getState() { + return state; + } + + @Override + public T doAs(Callable action) { + throw new UnsupportedOperationException(); + } + +} diff --git a/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/SimpleRapE4App.java b/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/SimpleRapE4App.java new file mode 100644 index 000000000..764234e18 --- /dev/null +++ b/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/SimpleRapE4App.java @@ -0,0 +1,32 @@ +package org.argeo.cms.e4.rap; + +import java.util.Enumeration; + +import org.argeo.api.cms.CmsLog; +import org.eclipse.rap.rwt.application.Application; +import org.osgi.framework.Bundle; + +/** Simple RAP app which loads all e4xmi files. */ +public class SimpleRapE4App extends AbstractRapE4App { + private final static CmsLog log = CmsLog.getLog(SimpleRapE4App.class); + + private String baseE4xmi = "/e4xmi"; + + @Override + protected void addEntryPoints(Application application) { + Bundle bundle = getBundleContext().getBundle(); + Enumeration paths = bundle.getEntryPaths(baseE4xmi); + while (paths.hasMoreElements()) { + String p = paths.nextElement(); + if (p.endsWith(".e4xmi")) { + String e4xmiPath = bundle.getSymbolicName() + '/' + p; + // FIXME deal with base name + String name=null;// = '/' + FilenameUtils.removeExtension(FilenameUtils.getName(p)); + addE4EntryPoint(application, name, e4xmiPath, getBaseProperties()); + if (log.isDebugEnabled()) + log.debug("Registered " + e4xmiPath + " as " + getContextName() + name); + } + } + } + +} diff --git a/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/package-info.java b/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/package-info.java new file mode 100644 index 000000000..1122f1922 --- /dev/null +++ b/swt/rap/org.argeo.cms.e4.rap/src/org/argeo/cms/e4/rap/package-info.java @@ -0,0 +1,2 @@ +/** Eclipse 4 RAP specific extensions. */ +package org.argeo.cms.e4.rap; \ No newline at end of file diff --git a/swt/rcp/org.argeo.cms.e4.rcp/.classpath b/swt/rcp/org.argeo.cms.e4.rcp/.classpath new file mode 100644 index 000000000..81fe078c2 --- /dev/null +++ b/swt/rcp/org.argeo.cms.e4.rcp/.classpath @@ -0,0 +1,7 @@ + + + + + + + diff --git a/swt/rcp/org.argeo.cms.e4.rcp/.gitignore b/swt/rcp/org.argeo.cms.e4.rcp/.gitignore new file mode 100644 index 000000000..710cd6893 --- /dev/null +++ b/swt/rcp/org.argeo.cms.e4.rcp/.gitignore @@ -0,0 +1,3 @@ +/bin/ +/target/ +/exec diff --git a/swt/rcp/org.argeo.cms.e4.rcp/.project b/swt/rcp/org.argeo.cms.e4.rcp/.project new file mode 100644 index 000000000..64d561913 --- /dev/null +++ b/swt/rcp/org.argeo.cms.e4.rcp/.project @@ -0,0 +1,28 @@ + + + org.argeo.cms.e4.rcp + + + + + + 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/swt/rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.jdt.core.prefs b/swt/rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 000000000..0c68a61dc --- /dev/null +++ b/swt/rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,7 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/swt/rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.pde.core.prefs b/swt/rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.pde.core.prefs new file mode 100644 index 000000000..f29e940a0 --- /dev/null +++ b/swt/rcp/org.argeo.cms.e4.rcp/.settings/org.eclipse.pde.core.prefs @@ -0,0 +1,3 @@ +eclipse.preferences.version=1 +pluginProject.extensions=false +resolve.requirebundle=false diff --git a/swt/rcp/org.argeo.cms.e4.rcp/META-INF/.gitignore b/swt/rcp/org.argeo.cms.e4.rcp/META-INF/.gitignore new file mode 100644 index 000000000..4854a41b9 --- /dev/null +++ b/swt/rcp/org.argeo.cms.e4.rcp/META-INF/.gitignore @@ -0,0 +1 @@ +/MANIFEST.MF diff --git a/swt/rcp/org.argeo.cms.e4.rcp/argeo-companion.e4xmi b/swt/rcp/org.argeo.cms.e4.rcp/argeo-companion.e4xmi new file mode 100644 index 000000000..6743a4e07 --- /dev/null +++ b/swt/rcp/org.argeo.cms.e4.rcp/argeo-companion.e4xmi @@ -0,0 +1,26 @@ + + + + + + + + + + + + editorArea + + + + + + + + + + + + + + diff --git a/swt/rcp/org.argeo.cms.e4.rcp/argeo-companion.properties b/swt/rcp/org.argeo.cms.e4.rcp/argeo-companion.properties new file mode 100644 index 000000000..0a0da7581 --- /dev/null +++ b/swt/rcp/org.argeo.cms.e4.rcp/argeo-companion.properties @@ -0,0 +1,23 @@ +argeo.osgi.start.2.node=\ +org.eclipse.equinox.http.servlet,\ +org.eclipse.equinox.metatype,\ +org.eclipse.equinox.cm,\ +org.argeo.init + +argeo.osgi.start.3.node=\ +org.argeo.cms,\ +org.argeo.cms.jcr,\ + +applicationXMI=org.argeo.cms.e4.rcp/argeo-companion.e4xmi +lifeCycleURI=bundleclass://org.argeo.cms.e4.rcp/org.argeo.cms.e4.rcp.CmsRcpLifeCycle +clearPersistedState=true +#argeo.cms.desktop.inTray=true + +# Remote node: +#argeo.node.repo.labeledUri=http://root:demo@localhost:7070/jcr/node + +# Logging +log.org.argeo=DEBUG + +argeo.node.useradmin.uris=os:/// +eclipse.application=org.argeo.cms.e4.rcp.CmsE4Application diff --git a/swt/rcp/org.argeo.cms.e4.rcp/bnd.bnd b/swt/rcp/org.argeo.cms.e4.rcp/bnd.bnd new file mode 100644 index 000000000..eaa51adec --- /dev/null +++ b/swt/rcp/org.argeo.cms.e4.rcp/bnd.bnd @@ -0,0 +1,8 @@ +Bundle-SymbolicName: org.argeo.cms.e4.rcp;singleton=true + +Require-Bundle: org.eclipse.core.runtime + +Import-Package: !org.eclipse.core.runtime,\ +org.eclipse.swt,\ +org.eclipse.*;resolution:=optional,\ +* diff --git a/swt/rcp/org.argeo.cms.e4.rcp/build.properties b/swt/rcp/org.argeo.cms.e4.rcp/build.properties new file mode 100644 index 000000000..355413e4f --- /dev/null +++ b/swt/rcp/org.argeo.cms.e4.rcp/build.properties @@ -0,0 +1,5 @@ +output.. = bin/ +bin.includes = META-INF/,\ + .,\ + argeo-companion.e4xmi +source.. = src/ diff --git a/swt/rcp/org.argeo.cms.e4.rcp/log4j.properties b/swt/rcp/org.argeo.cms.e4.rcp/log4j.properties new file mode 100644 index 000000000..13f949ff5 --- /dev/null +++ b/swt/rcp/org.argeo.cms.e4.rcp/log4j.properties @@ -0,0 +1,32 @@ +log4j.rootLogger=WARN, development + +## Levels +log4j.logger.org.argeo=DEBUG +log4j.logger.org.argeo.jackrabbit.remote.ExtendedDispatcherServlet=WARN +log4j.logger.org.argeo.server.webextender.TomcatDeployer=INFO + +#log4j.logger.org.springframework.security=DEBUG +#log4j.logger.org.apache.commons.exec=DEBUG +#log4j.logger.org.apache.jackrabbit.webdav=DEBUG +#log4j.logger.org.apache.jackrabbit.remote=DEBUG +#log4j.logger.org.apache.jackrabbit.core.observation=DEBUG + +log4j.logger.org.apache.catalina=INFO +log4j.logger.org.apache.coyote=INFO + +log4j.logger.org.apache.directory=INFO +log4j.logger.org.apache.directory.server=ERROR +log4j.logger.org.apache.jackrabbit.core.query.lucene=ERROR + +## Appenders +# console is set to be a ConsoleAppender. +log4j.appender.console=org.apache.log4j.ConsoleAppender + +# console uses PatternLayout. +log4j.appender.console.layout=org.apache.log4j.PatternLayout +log4j.appender.console.layout.ConversionPattern= %-5p %d{ISO8601} %m - %c - [%t]%n + +# development appender (slow!) +log4j.appender.development=org.apache.log4j.ConsoleAppender +log4j.appender.development.layout=org.apache.log4j.PatternLayout +log4j.appender.development.layout.ConversionPattern=%d{HH:mm:ss} [%16.16t] %5p %m (%F:%L) %c%n diff --git a/swt/rcp/org.argeo.cms.e4.rcp/plugin.xml b/swt/rcp/org.argeo.cms.e4.rcp/plugin.xml new file mode 100644 index 000000000..3e6896beb --- /dev/null +++ b/swt/rcp/org.argeo.cms.e4.rcp/plugin.xml @@ -0,0 +1,15 @@ + + + + + + + + + diff --git a/swt/rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsE4Application.java b/swt/rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsE4Application.java new file mode 100644 index 000000000..3861597aa --- /dev/null +++ b/swt/rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsE4Application.java @@ -0,0 +1,212 @@ +package org.argeo.cms.e4.rcp; + +import java.security.PrivilegedExceptionAction; +import java.util.UUID; +import java.util.concurrent.Callable; + +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.ux.CmsImageManager; +import org.argeo.api.cms.ux.CmsView; +import org.argeo.api.cms.ux.UxContext; +import org.argeo.cms.CurrentUser; +import org.argeo.cms.swt.CmsSwtUtils; +import org.argeo.cms.swt.SimpleSwtUxContext; +import org.argeo.cms.swt.auth.CmsLoginShell; +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.IExtension; +import org.eclipse.core.runtime.Platform; +import org.eclipse.equinox.app.IApplication; +import org.eclipse.equinox.app.IApplicationContext; +import org.eclipse.swt.widgets.Display; + +public class CmsE4Application implements IApplication, CmsView { + private LoginContext loginContext; + private IApplication e4Application; + private UxContext uxContext; + private String uid; + + @Override + public Object start(IApplicationContext context) throws Exception { + // TODO wait for CMS to be ready + Thread.sleep(5000); + + uid = UUID.randomUUID().toString(); + Subject subject = new Subject(); + Display display = createDisplay(); + CmsLoginShell loginShell = new CmsLoginShell(this, null); + // TODO customize CmsLoginShell to be smaller and centered + loginShell.setSubject(subject); + try { + // try pre-auth + loginContext = new LoginContext(CmsAuth.LOGIN_CONTEXT_SINGLE_USER, subject, loginShell); + loginContext.login(); + } catch (LoginException e) { + e.printStackTrace(); + loginShell.createUi(); + loginShell.open(); + + while (!loginShell.getShell().isDisposed()) { + if (!display.readAndDispatch()) + display.sleep(); + } + } + if (CurrentUser.getUsername(getSubject()) == null) + throw new IllegalStateException("Cannot log in"); + + // try { + // CallbackHandler callbackHandler = new DefaultLoginDialog( + // display.getActiveShell()); + // loginContext = new LoginContext( + // NodeConstants.LOGIN_CONTEXT_SINGLE_USER, subject, + // callbackHandler); + // } catch (LoginException e1) { + // throw new CmsException("Cannot initialize login context", e1); + // } + // + // // login + // try { + // loginContext.login(); + // subject = loginContext.getSubject(); + // } catch (LoginException e) { + // e.printStackTrace(); + // display.dispose(); + // try { + // Thread.sleep(2000); + // } catch (InterruptedException e1) { + // // silent + // } + // return null; + // } + + uxContext = new SimpleSwtUxContext(); + // UiContext.setData(CmsView.KEY, this); + CmsSwtUtils.registerCmsView(loginShell.getShell(), this); + e4Application = getApplication(null); + Object res = Subject.doAs(subject, new PrivilegedExceptionAction() { + + @Override + public Object run() throws Exception { + return e4Application.start(context); + } + + }); + return res; + } + + @Override + public void stop() { + if (e4Application != null) + e4Application.stop(); + } + + static IApplication getApplication(String[] args) { + IExtension extension = Platform.getExtensionRegistry().getExtension(Platform.PI_RUNTIME, + Platform.PT_APPLICATIONS, "org.eclipse.e4.ui.workbench.swt.E4Application"); + try { + IConfigurationElement[] elements = extension.getConfigurationElements(); + if (elements.length > 0) { + IConfigurationElement[] runs = elements[0].getChildren("run"); + if (runs.length > 0) { + Object runnable; + runnable = runs[0].createExecutableExtension("class"); + if (runnable instanceof IApplication) + return (IApplication) runnable; + } + } + } catch (Exception e) { + throw new IllegalStateException("Cannot find e4 application", e); + } + throw new IllegalStateException("Cannot find e4 application"); + } + + public static Display createDisplay() { + Display.setAppName("Argeo CMS RCP"); + + // create the display + Display newDisplay = Display.getCurrent(); + if (newDisplay == null) { + newDisplay = new Display(); + } + // Set the priority higher than normal so as to be higher + // than the JobManager. + Thread.currentThread().setPriority(Math.min(Thread.MAX_PRIORITY, Thread.NORM_PRIORITY + 1)); + return newDisplay; + } + + // + // CMS VIEW + // + + @Override + public UxContext getUxContext() { + return uxContext; + } + + @Override + public void navigateTo(String state) { + // TODO Auto-generated method stub + + } + + @Override + public void authChange(LoginContext loginContext) { + if (loginContext == null) + throw new IllegalStateException("Login context cannot be null"); + // logout previous login context + // if (this.loginContext != null) + // try { + // this.loginContext.logout(); + // } catch (LoginException e1) { + // System.err.println("Could not log out: " + e1); + // } + this.loginContext = loginContext; + } + + @Override + public void logout() { + if (loginContext == null) + throw new IllegalStateException("Login context should not bet null"); + try { + CurrentUser.logoutCmsSession(loginContext.getSubject()); + loginContext.logout(); + } catch (LoginException e) { + throw new IllegalStateException("Cannot log out", e); + } + } + + @Override + public void exception(Throwable e) { + // TODO Auto-generated method stub + + } + + @Override + public CmsImageManager getImageManager() { + // TODO Auto-generated method stub + return null; + } + + protected Subject getSubject() { + return loginContext.getSubject(); + } + + @Override + public boolean isAnonymous() { + return CurrentUser.isAnonymous(getSubject()); + } + + @Override + public String getUid() { + return uid; + } + + @Override + public T doAs(Callable action) { + throw new UnsupportedOperationException(); + } + +} diff --git a/swt/rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsRcpLifeCycle.java b/swt/rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsRcpLifeCycle.java new file mode 100644 index 000000000..1d38fe718 --- /dev/null +++ b/swt/rcp/org.argeo.cms.e4.rcp/src/org/argeo/cms/e4/rcp/CmsRcpLifeCycle.java @@ -0,0 +1,27 @@ +package org.argeo.cms.e4.rcp; + +import org.eclipse.e4.core.contexts.IEclipseContext; +import org.eclipse.e4.ui.workbench.lifecycle.PostContextCreate; +import org.eclipse.e4.ui.workbench.lifecycle.PreSave; +import org.eclipse.e4.ui.workbench.lifecycle.ProcessAdditions; +import org.eclipse.e4.ui.workbench.lifecycle.ProcessRemovals; + +@SuppressWarnings("restriction") +public class CmsRcpLifeCycle { + + @PostContextCreate + void postContextCreate(IEclipseContext workbenchContext) { + } + + @PreSave + void preSave(IEclipseContext workbenchContext) { + } + + @ProcessAdditions + void processAdditions(IEclipseContext workbenchContext) { + } + + @ProcessRemovals + void processRemovals(IEclipseContext workbenchContext) { + } +}