Start making theming separate
authorMathieu Baudier <mbaudier@argeo.org>
Sat, 26 Aug 2017 13:52:57 +0000 (15:52 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Sat, 26 Aug 2017 13:52:57 +0000 (15:52 +0200)
org.argeo.cms.ui/src/org/argeo/cms/ui/AbstractCmsEntryPoint.java
org.argeo.cms.ui/src/org/argeo/cms/ui/UxContext.java
org.argeo.cms.ui/src/org/argeo/cms/util/BundleResourceLoader.java
org.argeo.cms.ui/src/org/argeo/cms/util/SimpleApp.java
org.argeo.cms.ui/src/org/argeo/cms/util/SimpleErgonomics.java
org.argeo.cms.ui/src/org/argeo/cms/util/SimpleUxContext.java
org.argeo.cms.ui/src/org/argeo/cms/util/StyleSheetResourceLoader.java
org.argeo.cms/src/org/argeo/cms/internal/backup/SimpleBackupPurge.java
org.argeo.eclipse.ui/src/org/argeo/eclipse/ui/jcr/AsyncUiEventListener.java

index 2d83f8ded0ab2c60f80fcb9641c2f09e53b8edbc..43906fae73e7914aaa0797d06aa7746e76da93ad 100644 (file)
@@ -1,5 +1,6 @@
 package org.argeo.cms.ui;
 
+import java.io.IOException;
 import java.security.PrivilegedAction;
 import java.util.HashMap;
 import java.util.Map;
@@ -12,6 +13,11 @@ import javax.jcr.RepositoryException;
 import javax.jcr.Session;
 import javax.jcr.nodetype.NodeType;
 import javax.security.auth.Subject;
+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.UnsupportedCallbackException;
 import javax.security.auth.login.LoginContext;
 import javax.security.auth.login.LoginException;
 import javax.servlet.http.HttpServletRequest;
@@ -54,7 +60,6 @@ public abstract class AbstractCmsEntryPoint extends AbstractEntryPoint implement
        private Node node;
        private String nodePath;// useful when changing auth
        private String state;
-       private String page;
        private Throwable exception;
 
        // Client services
@@ -133,7 +138,7 @@ public abstract class AbstractCmsEntryPoint extends AbstractEntryPoint implement
         * The node to return when no node was found (for authenticated users and
         * anonymous)
         */
-       protected Node getDefaultNode(Session session) throws RepositoryException {
+       private Node getDefaultNode(Session session) throws RepositoryException {
                if (!session.hasPermission(defaultPath, "read")) {
                        String userId = session.getUserID();
                        if (userId.equals(NodeConstants.ROLE_ANONYMOUS))
@@ -248,8 +253,8 @@ public abstract class AbstractCmsEntryPoint extends AbstractEntryPoint implement
        protected synchronized String setState(String newState) {
                String previousState = this.state;
 
-               Node node = null;
-               page = null;
+               String newNodePath = null;
+               String prefix = null;
                this.state = newState;
                if (newState.equals("~"))
                        this.state = "";
@@ -257,25 +262,53 @@ public abstract class AbstractCmsEntryPoint extends AbstractEntryPoint implement
                try {
                        int firstSlash = state.indexOf('/');
                        if (firstSlash == 0) {
-                               node = session.getNode(state);
-                               page = "";
+                               newNodePath = state;
+                               prefix = "";
                        } else if (firstSlash > 0) {
-                               String prefix = state.substring(0, firstSlash);
-                               String path = state.substring(firstSlash);
-                               if (session.nodeExists(path))
-                                       node = session.getNode(path);
-                               else
-                                       throw new CmsException("Data " + path + " does not exist");
-                               page = prefix;
+                               prefix = state.substring(0, firstSlash);
+                               newNodePath = state.substring(firstSlash);
                        } else {
-                               node = getDefaultNode(session);
-                               page = state;
+                               newNodePath = defaultPath;
+                               prefix = state;
+
+                       }
+
+                       // auth
+                       int colonIndex = prefix.indexOf(':');
+                       if (colonIndex > 0) {
+                               String user = prefix.substring(0, colonIndex);
+                               // if (isAnonymous()) {
+                               String token = prefix.substring(colonIndex + 1);
+                               LoginContext lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, new CallbackHandler() {
+
+                                       @Override
+                                       public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+                                               for (Callback callback : callbacks) {
+                                                       if (callback instanceof NameCallback)
+                                                               ((NameCallback) callback).setName(user);
+                                                       else if (callback instanceof PasswordCallback)
+                                                               ((PasswordCallback) callback).setPassword(token.toCharArray());
+                                               }
+
+                                       }
+                               });
+                               lc.login();
+                               authChange(lc);// sets the node as well
+                               // } else {
+                               // // TODO check consistency
+                               // }
+                       } else {
+                               Node newNode = null;
+                               if (session.nodeExists(newNodePath))
+                                       newNode = session.getNode(newNodePath);
+                               else
+                                       throw new CmsException("Data " + newNodePath + " does not exist");
+                               setNode(newNode);
                        }
-                       setNode(node);
-                       String title = publishMetaData(node);
+                       String title = publishMetaData(getNode());
 
                        if (log.isTraceEnabled())
-                               log.trace("node=" + node + ", state=" + state + " (page=" + page + ")");
+                               log.trace("node=" + newNodePath + ", state=" + state + " (prefix=" + prefix + ")");
 
                        return title;
                } catch (Exception e) {
index f03b88bbb23ee860f8901dca6743a07625f7b67f..42d7ab3efe8170e41936ce7f8045b3024745f025 100644 (file)
@@ -2,8 +2,17 @@ package org.argeo.cms.ui;
 
 public interface UxContext {
        boolean isPortrait();
+
        boolean isLandscape();
+
        boolean isSquare();
-       
+
        boolean isSmall();
+
+       /**
+        * Is a production environment (must be false by default, and be explicitly
+        * set during the CMS deployment). When false, it can activate additional UI
+        * capabilities in order to facilitate QA.
+        */
+       boolean isMasterData();
 }
index cda00efcdeac88f239ca33f2b933e038fe1efef9..c8fb8a40cdb5c1db652b2c04b9bc0e52ea365d5d 100644 (file)
@@ -7,40 +7,20 @@ import java.net.URL;
 import org.argeo.cms.CmsException;
 import org.eclipse.rap.rwt.service.ResourceLoader;
 import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
 
 /** {@link ResourceLoader} implementation wrapping an {@link Bundle}. */
-public class BundleResourceLoader implements ResourceLoader {
-       private final BundleContext bundleContext;
+class BundleResourceLoader implements ResourceLoader {
+       private final Bundle bundle;
 
-       public BundleResourceLoader(BundleContext bundleContext) {
-               this.bundleContext = bundleContext;
+       public BundleResourceLoader(Bundle bundle) {
+               this.bundle = bundle;
        }
 
        @Override
-       public InputStream getResourceAsStream(String resourceName)
-                       throws IOException {
-               // 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);
-               // }
-               // }
+       public InputStream getResourceAsStream(String resourceName) throws IOException {
                URL res = bundle.getResource(resourceName);
                if (res == null)
-                       throw new CmsException("Resource " + resourceName
-                                       + " not found in bundle " + bundle.getSymbolicName());
+                       throw new CmsException("Resource " + resourceName + " not found in bundle " + bundle.getSymbolicName());
                return res.openStream();
        }
 
index 780a01d184c4dfbd3dded5fb74f4c094aca2d343..b75c700078d7bff8a81840390a94f3a99c2c30da 100644 (file)
@@ -2,13 +2,17 @@ package org.argeo.cms.util;
 
 import java.io.IOException;
 import java.io.InputStream;
+import java.net.URL;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Enumeration;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Hashtable;
 import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 
 import javax.jcr.Repository;
 import javax.jcr.RepositoryException;
@@ -40,6 +44,7 @@ import org.eclipse.swt.events.SelectionEvent;
 import org.eclipse.swt.layout.FillLayout;
 import org.eclipse.swt.widgets.Button;
 import org.eclipse.swt.widgets.Composite;
+import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceRegistration;
 
@@ -71,8 +76,7 @@ public class SimpleApp implements CmsConstants, ApplicationConfiguration {
 
        public void configure(Application application) {
                try {
-                       StyleSheetResourceLoader styleSheetRL = new StyleSheetResourceLoader(bundleContext);
-                       BundleResourceLoader bundleRL = new BundleResourceLoader(bundleContext);
+                       BundleResourceLoader bundleRL = new BundleResourceLoader(bundleContext.getBundle());
 
                        application.setOperationMode(OperationMode.SWT_COMPATIBILITY);
                        // application.setOperationMode(OperationMode.JEE_COMPATIBILITY);
@@ -93,6 +97,7 @@ public class SimpleApp implements CmsConstants, ApplicationConfiguration {
                        Map<String, String> defaultBranding = null;
                        if (branding.containsKey("*"))
                                defaultBranding = branding.get("*");
+                       String defaultTheme = defaultBranding.get(WebClient.THEME_ID);
 
                        // entry points
                        for (String page : pages.keySet()) {
@@ -103,8 +108,11 @@ public class SimpleApp implements CmsConstants, ApplicationConfiguration {
                                }
                                // favicon
                                if (properties.containsKey(WebClient.FAVICON)) {
+                                       String themeId = defaultBranding.get(WebClient.THEME_ID);
+                                       Bundle themeBundle = findThemeBundle(themeId);
                                        String faviconRelPath = properties.get(WebClient.FAVICON);
-                                       application.addResource(faviconRelPath, new BundleResourceLoader(bundleContext));
+                                       application.addResource(faviconRelPath,
+                                                       new BundleResourceLoader(themeBundle != null ? themeBundle : bundleContext.getBundle()));
                                        if (log.isTraceEnabled())
                                                log.trace("Favicon " + faviconRelPath);
 
@@ -128,8 +136,14 @@ public class SimpleApp implements CmsConstants, ApplicationConfiguration {
                                log.info("Page /" + page);
                        }
 
-                       // stylesheets
+                       // stylesheets and themes
+                       Set<Bundle> themeBundles = new HashSet<>();
                        for (String themeId : styleSheets.keySet()) {
+                               Bundle themeBundle = findThemeBundle(themeId);
+                               StyleSheetResourceLoader styleSheetRL = new StyleSheetResourceLoader(
+                                               themeBundle != null ? themeBundle : bundleContext.getBundle());
+                               if (themeBundle != null)
+                                       themeBundles.add(themeBundle);
                                List<String> cssLst = styleSheets.get(themeId);
                                if (log.isDebugEnabled())
                                        log.debug("Theme " + themeId);
@@ -140,6 +154,12 @@ public class SimpleApp implements CmsConstants, ApplicationConfiguration {
                                }
 
                        }
+                       for (Bundle themeBundle : themeBundles) {
+                               BundleResourceLoader themeBRL = new BundleResourceLoader(themeBundle);
+                               addThemeResources(application, themeBundle, themeBRL, "*.png");
+                               addThemeResources(application, themeBundle, themeBRL, "*.gif");
+                               addThemeResources(application, themeBundle, themeBRL, "*.jpg");
+                       }
                } catch (RuntimeException e) {
                        // Easier access to initialisation errors
                        log.error("Unexpected exception when configuring RWT application.", e);
@@ -147,6 +167,41 @@ public class SimpleApp implements CmsConstants, ApplicationConfiguration {
                }
        }
 
+       private Bundle findThemeBundle(String themeId) {
+               if (themeId == null)
+                       return null;
+               // TODO optimize
+               // TODO deal with multiple versions
+               Bundle themeBundle = null;
+               if (themeId != null) {
+                       for (Bundle bundle : bundleContext.getBundles())
+                               if (themeId.equals(bundle.getSymbolicName())) {
+                                       themeBundle = bundle;
+                                       break;
+                               }
+               }
+               return themeBundle;
+       }
+
+       private void addThemeResources(Application application, Bundle themeBundle, BundleResourceLoader themeBRL,
+                       String pattern) {
+               Enumeration<URL> themeResources = themeBundle.findEntries("/", pattern, true);
+               if (themeResources == null)
+                       return;
+               while (themeResources.hasMoreElements()) {
+                       String resource = themeResources.nextElement().getPath();
+                       // remove first '/' so that RWT registers it
+                       resource = resource.substring(1);
+                       if (!resource.endsWith("/")) {
+                               application.addResource(resource, themeBRL);
+                               if (log.isTraceEnabled())
+                                       log.trace("Registered " + resource + " from theme " + themeBundle);
+                       }
+
+               }
+
+       }
+
        public void init() throws RepositoryException {
                Session session = null;
                try {
index b7b76e4e686e05bbfc1b91875733abe8cd1dd471..505d482f3befd2e9de8a9ee35c3ce210c0d42661 100644 (file)
@@ -50,7 +50,9 @@ public class SimpleErgonomics extends AbstractCmsEntryPoint {
                parent.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
                parent.setLayout(CmsUtils.noSpaceGridLayout());
 
-               // createAdminArea(parent);
+               uxContext = new SimpleUxContext();
+               if (!getUxContext().isMasterData())
+                       createAdminArea(parent);
                headerArea = new Composite(parent, SWT.NONE);
                headerArea.setLayout(new FillLayout());
                GridData headerData = new GridData(SWT.FILL, SWT.FILL, false, false);
@@ -61,7 +63,6 @@ public class SimpleErgonomics extends AbstractCmsEntryPoint {
                bodyArea.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_BODY);
                bodyArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
                bodyArea.setLayout(CmsUtils.noSpaceGridLayout());
-               uxContext = new SimpleUxContext();
                uiInitialized = true;
                refresh();
        }
index 6cb37bde4f4a71ddf659d785a8d9b76e5657f485..a3844465800ad659604c5493f09ef2cde377a97f 100644 (file)
@@ -41,4 +41,9 @@ public class SimpleUxContext implements UxContext {
                return size.x <= small.x || size.y <= small.y;
        }
 
+       @Override
+       public boolean isMasterData() {
+               return false;
+       }
+
 }
index a7e3b6e846ef6a93e85114aae26ee62f316205f8..1df98d484963d7fad233283aff31a4e4972fc5e9 100644 (file)
@@ -12,24 +12,21 @@ import org.apache.commons.io.IOUtils;
 import org.argeo.cms.CmsException;
 import org.eclipse.rap.rwt.service.ResourceLoader;
 import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
 
 /** {@link ResourceLoader} caching stylesheets. */
-public class StyleSheetResourceLoader implements ResourceLoader {
-       private final BundleContext bundleContext;
-
+class StyleSheetResourceLoader implements ResourceLoader {
+       private Bundle themeBundle;
        private Map<String, StyleSheet> stylesheets = new LinkedHashMap<String, StyleSheet>();
 
-       public StyleSheetResourceLoader(BundleContext bundleContext) {
-               this.bundleContext = bundleContext;
+       public StyleSheetResourceLoader(Bundle themeBundle) {
+               this.themeBundle = themeBundle;
        }
 
        @Override
-       public InputStream getResourceAsStream(String resourceName)
-                       throws IOException {
+       public InputStream getResourceAsStream(String resourceName) throws IOException {
                if (!stylesheets.containsKey(resourceName)) {
                        // TODO deal with other bundles
-                       Bundle bundle = bundleContext.getBundle();
+                       // Bundle bundle = bundleContext.getBundle();
                        // String location =
                        // bundle.getLocation().substring("initial@reference:".length());
                        // if (location.startsWith("file:")) {
@@ -45,10 +42,11 @@ public class StyleSheetResourceLoader implements ResourceLoader {
                        // return Files.newInputStream(resourcePath);
                        // }
                        // }
-                       URL res = bundle.getResource(resourceName);
+
+                       URL res = themeBundle.getResource(resourceName);
                        if (res == null)
-                               throw new CmsException("Resource " + resourceName
-                                               + " not found in bundle " + bundle.getSymbolicName());
+                               throw new CmsException(
+                                               "Resource " + resourceName + " not found in bundle " + themeBundle.getSymbolicName());
                        ByteArrayOutputStream out = new ByteArrayOutputStream();
                        IOUtils.copy(res.openStream(), out);
                        stylesheets.put(resourceName, new StyleSheet(out.toByteArray()));
index 64863759fa35c6a349f7b35c4f6a769e3ec37f08..33b5b5ebee1c971f594d963fcbf94d2f35999938 100644 (file)
@@ -16,6 +16,9 @@
 package org.argeo.cms.internal.backup;
 
 import java.text.DateFormat;
+import java.time.Period;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
 import java.util.Date;
 import java.util.SortedMap;
 import java.util.TreeMap;
@@ -27,8 +30,6 @@ import org.apache.commons.vfs2.FileSystemManager;
 import org.apache.commons.vfs2.FileSystemOptions;
 import org.apache.commons.vfs2.Selectors;
 import org.argeo.cms.CmsException;
-import org.joda.time.DateTime;
-import org.joda.time.Period;
 
 /** Simple backup purge which keeps backups only for a given number of days */
 public class SimpleBackupPurge implements BackupPurge {
@@ -37,14 +38,13 @@ public class SimpleBackupPurge implements BackupPurge {
        private Integer daysKept = 30;
 
        @Override
-       public void purge(FileSystemManager fileSystemManager, String base,
-                       String name, DateFormat dateFormat, FileSystemOptions opts) {
+       public void purge(FileSystemManager fileSystemManager, String base, String name, DateFormat dateFormat,
+                       FileSystemOptions opts) {
                try {
-                       DateTime nowDt = new DateTime();
-                       FileObject baseFo = fileSystemManager.resolveFile(
-                                       base + '/' + name, opts);
+                       ZonedDateTime nowDt = ZonedDateTime.now();
+                       FileObject baseFo = fileSystemManager.resolveFile(base + '/' + name, opts);
 
-                       SortedMap<DateTime, FileObject> toDelete = new TreeMap<DateTime, FileObject>();
+                       SortedMap<ZonedDateTime, FileObject> toDelete = new TreeMap<ZonedDateTime, FileObject>();
                        int backupCount = 0;
 
                        // make sure base dir exists
@@ -55,9 +55,9 @@ public class SimpleBackupPurge implements BackupPurge {
                                String backupName = backupFo.getName().getBaseName();
                                Date backupDate = dateFormat.parse(backupName);
                                backupCount++;
-
-                               DateTime backupDt = new DateTime(backupDate.getTime());
-                               Period sinceThen = new Period(backupDt, nowDt);
+                               ZonedDateTime backupDt = ZonedDateTime.ofInstant(backupDate.toInstant(), ZoneId.systemDefault());
+                               Period sinceThen = Period.between(backupDt.toLocalDate(), nowDt.toLocalDate());
+                               // new Period(backupDt, nowDt);
                                int days = sinceThen.getDays();
                                // int days = sinceThen.getMinutes();
                                if (days > daysKept) {
@@ -68,11 +68,9 @@ public class SimpleBackupPurge implements BackupPurge {
                        if (toDelete.size() != 0 && toDelete.size() == backupCount) {
                                // all backups would be deleted
                                // but we want to keep at least one
-                               DateTime lastBackupDt = toDelete.firstKey();
+                               ZonedDateTime lastBackupDt = toDelete.firstKey();
                                FileObject keptFo = toDelete.remove(lastBackupDt);
-                               log.warn("Backup " + keptFo
-                                               + " kept although it is older than " + daysKept
-                                               + " days.");
+                               log.warn("Backup " + keptFo + " kept although it is older than " + daysKept + " days.");
                        }
 
                        // delete old backups
index 5d5a06f587b1b96132046bb3f2b904032f9a39b7..88119b8abbe663e2c33303e3e8a4666e1a288c2e 100644 (file)
@@ -28,10 +28,12 @@ import org.apache.commons.logging.LogFactory;
 import org.argeo.eclipse.ui.EclipseUiException;
 import org.eclipse.swt.widgets.Display;
 
-/** {@link EventListener} which simplifies running actions within the UI thread. */
+/**
+ * {@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 static Log logSuper = LogFactory
+       // .getLog(AsyncUiEventListener.class);
        private final Log logThis = LogFactory.getLog(getClass());
 
        private final Display display;
@@ -42,15 +44,13 @@ public abstract class AsyncUiEventListener implements EventListener {
        }
 
        /** Called asynchronously in the UI thread. */
-       protected abstract void onEventInUiThread(List<Event> events)
-                       throws RepositoryException;
+       protected abstract void onEventInUiThread(List<Event> events) throws RepositoryException;
 
        /**
         * Whether these events should be processed in the UI or skipped with no UI
         * job created.
         */
-       protected Boolean willProcessInUiThread(List<Event> events)
-                       throws RepositoryException {
+       protected Boolean willProcessInUiThread(List<Event> events) throws RepositoryException {
                return true;
        }
 
@@ -73,27 +73,27 @@ public abstract class AsyncUiEventListener implements EventListener {
                        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;
-//                             }
+               // 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;
+               // }
 
-                               display.asyncExec(new Runnable() {
-                                       public void run() {
-                                               try {
-                                                       onEventInUiThread(events);
-                                               } catch (RepositoryException e) {
-                                                       throw new EclipseUiException("Cannot process events "
-                                                                       + events, e);
-                                               }
+               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();
+               // return Status.OK_STATUS;
+               // }
+               // };
+               // job.schedule();
        }
 }