CMS Script
authorMathieu Baudier <mbaudier@argeo.org>
Sat, 15 Sep 2018 19:43:40 +0000 (21:43 +0200)
committerMathieu Baudier <mbaudier@argeo.org>
Sat, 15 Sep 2018 19:43:40 +0000 (21:43 +0200)
14 files changed:
org.argeo.cms.ui/src/org/argeo/cms/script/AppUi.java [new file with mode: 0644]
org.argeo.cms.ui/src/org/argeo/cms/script/Branding.java [new file with mode: 0644]
org.argeo.cms.ui/src/org/argeo/cms/script/CmsScriptApp.java [new file with mode: 0644]
org.argeo.cms.ui/src/org/argeo/cms/script/CmsScriptRwtApplication.java [new file with mode: 0644]
org.argeo.cms.ui/src/org/argeo/cms/script/ScriptAppActivator.java [new file with mode: 0644]
org.argeo.cms.ui/src/org/argeo/cms/script/ScriptUi.java [new file with mode: 0644]
org.argeo.cms.ui/src/org/argeo/cms/script/Theme.java [new file with mode: 0644]
org.argeo.cms.ui/src/org/argeo/cms/script/cms.js [new file with mode: 0644]
org.argeo.cms.ui/src/org/argeo/cms/util/BundleResourceLoader.java
org.argeo.cms.ui/src/org/argeo/cms/util/CmsPane.java [new file with mode: 0644]
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/StyleSheetResourceLoader.java
org.argeo.cms.ui/src/org/argeo/cms/util/ThemeUtils.java [new file with mode: 0644]

diff --git a/org.argeo.cms.ui/src/org/argeo/cms/script/AppUi.java b/org.argeo.cms.ui/src/org/argeo/cms/script/AppUi.java
new file mode 100644 (file)
index 0000000..cea57df
--- /dev/null
@@ -0,0 +1,244 @@
+package org.argeo.cms.script;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.jcr.Node;
+import javax.jcr.Repository;
+import javax.jcr.RepositoryException;
+import javax.script.Invocable;
+import javax.script.ScriptException;
+
+import org.argeo.cms.ui.CmsUiProvider;
+import org.argeo.cms.util.CmsPane;
+import org.argeo.cms.util.CmsUtils;
+import org.argeo.cms.util.SimpleErgonomics;
+import org.argeo.eclipse.ui.Selected;
+import org.eclipse.rap.rwt.RWT;
+import org.eclipse.rap.rwt.application.Application;
+import org.eclipse.rap.rwt.application.EntryPoint;
+import org.eclipse.rap.rwt.application.EntryPointFactory;
+import org.eclipse.rap.rwt.client.WebClient;
+import org.eclipse.rap.rwt.client.service.JavaScriptExecutor;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.SelectionEvent;
+import org.eclipse.swt.widgets.Button;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.eclipse.swt.widgets.Label;
+import org.osgi.framework.BundleContext;
+
+public class AppUi implements CmsUiProvider, Branding {
+       private final CmsScriptApp app;
+
+       private CmsUiProvider ui;
+       private String createUi;
+       private Object impl;
+       private String script;
+       // private Branding branding = new Branding();
+
+       // Branding
+       private String themeId;
+       private String additionalHeaders;
+       private String bodyHtml;
+       private String pageTitle;
+       private String pageOverflow;
+       private String favicon;
+
+       public AppUi(CmsScriptApp app) {
+               this.app = app;
+       }
+
+       public AppUi(CmsScriptApp app, String scriptPath) {
+               this.app = app;
+               this.ui = new ScriptUi((BundleContext) app.getScriptEngine().get(CmsScriptRwtApplication.BC), scriptPath);
+       }
+
+       public AppUi(CmsScriptApp app, CmsUiProvider uiProvider) {
+               this.app = app;
+               this.ui = uiProvider;
+       }
+
+       public void apply(Repository repository, Application application, Branding appBranding, String path) {
+               Map<String, String> factoryProperties = new HashMap<>();
+               if (appBranding != null)
+                       appBranding.applyBranding(factoryProperties);
+               applyBranding(factoryProperties);
+               EntryPointFactory entryPointFactory = new EntryPointFactory() {
+                       @Override
+                       public EntryPoint create() {
+                               SimpleErgonomics ergonomics = new SimpleErgonomics(repository, "main", "/home/root/argeo:keyring",
+                                               AppUi.this, factoryProperties);
+                               return ergonomics;
+                       }
+               };
+               application.addEntryPoint("/" + path, entryPointFactory, factoryProperties);
+       }
+
+       public void setUi(CmsUiProvider uiProvider) {
+               this.ui = uiProvider;
+       }
+
+       public void applyBranding(Map<String, String> properties) {
+               if (themeId != null)
+                       properties.put(WebClient.THEME_ID, themeId);
+               if (additionalHeaders != null)
+                       properties.put(WebClient.HEAD_HTML, additionalHeaders);
+               if (bodyHtml != null)
+                       properties.put(WebClient.BODY_HTML, bodyHtml);
+               if (pageTitle != null)
+                       properties.put(WebClient.PAGE_TITLE, pageTitle);
+               if (pageOverflow != null)
+                       properties.put(WebClient.PAGE_OVERFLOW, pageOverflow);
+               if (favicon != null)
+                       properties.put(WebClient.FAVICON, favicon);
+       }
+
+       // public Branding getBranding() {
+       // return branding;
+       // }
+
+       @Override
+       public Control createUi(Composite parent, Node context) throws RepositoryException {
+               CmsPane cmsPane = new CmsPane(parent, SWT.NONE);
+               // QA
+               CmsUtils.style(cmsPane.getQaArea(), "qa");
+               Button reload = new Button(cmsPane.getQaArea(), SWT.FLAT);
+               CmsUtils.style(reload, "qa");
+               reload.setText("Reload");
+               reload.addSelectionListener(new Selected() {
+                       private static final long serialVersionUID = 1L;
+
+                       @Override
+                       public void widgetSelected(SelectionEvent e) {
+                               new Thread() {
+                                       @Override
+                                       public void run() {
+                                               app.reload();
+                                       }
+                               }.start();
+                               RWT.getClient().getService(JavaScriptExecutor.class).execute("setTimeout('location.reload()',1000)");
+                       }
+               });
+
+               // Support
+               CmsUtils.style(cmsPane.getSupportArea(), "support");
+               Label msg = new Label(cmsPane.getSupportArea(), SWT.NONE);
+               CmsUtils.style(msg, "support");
+               msg.setText("UNSUPPORTED DEVELOPMENT VERSION");
+
+               if (ui != null) {
+                       ui.createUi(cmsPane.getMainArea(), context);
+               }
+               if (createUi != null) {
+                       Invocable invocable = (Invocable) app.getScriptEngine();
+                       try {
+                               invocable.invokeFunction(createUi, cmsPane.getMainArea(), context);
+
+                       } catch (NoSuchMethodException e) {
+                               // TODO Auto-generated catch block
+                               e.printStackTrace();
+                       } catch (ScriptException e) {
+                               // TODO Auto-generated catch block
+                               e.printStackTrace();
+                       }
+               }
+               if (impl != null) {
+                       Invocable invocable = (Invocable) app.getScriptEngine();
+                       try {
+                               invocable.invokeMethod(impl, "createUi", cmsPane.getMainArea(), context);
+
+                       } catch (NoSuchMethodException e) {
+                               // TODO Auto-generated catch block
+                               e.printStackTrace();
+                       } catch (ScriptException e) {
+                               // TODO Auto-generated catch block
+                               e.printStackTrace();
+                       }
+               }
+
+               // Invocable invocable = (Invocable) app.getScriptEngine();
+               // try {
+               // invocable.invokeMethod(AppUi.this, "initUi", parent, context);
+               //
+               // } catch (NoSuchMethodException e) {
+               // // TODO Auto-generated catch block
+               // e.printStackTrace();
+               // } catch (ScriptException e) {
+               // // TODO Auto-generated catch block
+               // e.printStackTrace();
+               // }
+
+               return null;
+       }
+
+       public void setCreateUi(String createUi) {
+               this.createUi = createUi;
+       }
+
+       public void setImpl(Object impl) {
+               this.impl = impl;
+       }
+
+       public Object getImpl() {
+               return impl;
+       }
+
+       public String getScript() {
+               return script;
+       }
+
+       public void setScript(String script) {
+               this.script = script;
+       }
+
+       // Branding
+       public String getThemeId() {
+               return themeId;
+       }
+
+       public void setThemeId(String themeId) {
+               this.themeId = themeId;
+       }
+
+       public String getAdditionalHeaders() {
+               return additionalHeaders;
+       }
+
+       public void setAdditionalHeaders(String additionalHeaders) {
+               this.additionalHeaders = additionalHeaders;
+       }
+
+       public String getBodyHtml() {
+               return bodyHtml;
+       }
+
+       public void setBodyHtml(String bodyHtml) {
+               this.bodyHtml = bodyHtml;
+       }
+
+       public String getPageTitle() {
+               return pageTitle;
+       }
+
+       public void setPageTitle(String pageTitle) {
+               this.pageTitle = pageTitle;
+       }
+
+       public String getPageOverflow() {
+               return pageOverflow;
+       }
+
+       public void setPageOverflow(String pageOverflow) {
+               this.pageOverflow = pageOverflow;
+       }
+
+       public String getFavicon() {
+               return favicon;
+       }
+
+       public void setFavicon(String favicon) {
+               this.favicon = favicon;
+       }
+
+}
diff --git a/org.argeo.cms.ui/src/org/argeo/cms/script/Branding.java b/org.argeo.cms.ui/src/org/argeo/cms/script/Branding.java
new file mode 100644 (file)
index 0000000..2a99191
--- /dev/null
@@ -0,0 +1,22 @@
+package org.argeo.cms.script;
+
+import java.util.Map;
+
+import org.eclipse.rap.rwt.client.WebClient;
+
+public interface Branding {
+       public void applyBranding(Map<String, String> properties);
+
+       public String getThemeId();
+
+       public String getAdditionalHeaders();
+
+       public String getBodyHtml();
+
+       public String getPageTitle();
+
+       public String getPageOverflow();
+
+       public String getFavicon();
+
+}
diff --git a/org.argeo.cms.ui/src/org/argeo/cms/script/CmsScriptApp.java b/org.argeo.cms.ui/src/org/argeo/cms/script/CmsScriptApp.java
new file mode 100644 (file)
index 0000000..e6ccf52
--- /dev/null
@@ -0,0 +1,304 @@
+package org.argeo.cms.script;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Map;
+
+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.script.ScriptEngine;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.cms.CmsException;
+import org.argeo.cms.ui.CmsConstants;
+import org.argeo.cms.util.BundleResourceLoader;
+import org.argeo.cms.util.CmsUtils;
+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.eclipse.rap.rwt.service.ResourceLoader;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceRegistration;
+
+public class CmsScriptApp implements Branding {
+       public final static String CONTEXT_NAME = "contextName";
+
+       ServiceRegistration<ApplicationConfiguration> appConfigReg;
+
+       private ScriptEngine scriptEngine;
+
+       private final static Log log = LogFactory.getLog(CmsScriptApp.class);
+
+       private String webPath;
+       private String repo = "(cn=node)";
+
+       // private Branding branding = new Branding();
+       private Theme theme;
+
+       private List<String> resources = new ArrayList<>();
+
+       private Map<String, AppUi> ui = new HashMap<>();
+
+       // Branding
+       private String themeId;
+       private String additionalHeaders;
+       private String bodyHtml;
+       private String pageTitle;
+       private String pageOverflow;
+       private String favicon;
+
+       public CmsScriptApp(ScriptEngine scriptEngine) {
+               super();
+               this.scriptEngine = scriptEngine;
+       }
+
+       public void apply(BundleContext bundleContext, Repository repository, Application application) {
+               BundleResourceLoader bundleRL = new BundleResourceLoader(bundleContext.getBundle());
+
+               application.setOperationMode(OperationMode.SWT_COMPATIBILITY);
+               // application.setOperationMode(OperationMode.JEE_COMPATIBILITY);
+
+               application.setExceptionHandler(new CmsExceptionHandler());
+
+               // loading animated gif
+               application.addResource(CmsConstants.LOADING_IMAGE, createResourceLoader(CmsConstants.LOADING_IMAGE));
+               // empty image
+               application.addResource(CmsConstants.NO_IMAGE, createResourceLoader(CmsConstants.NO_IMAGE));
+
+               for (String resource : resources) {
+                       application.addResource(resource, bundleRL);
+                       if (log.isTraceEnabled())
+                               log.trace("Resource " + resource);
+               }
+
+               if (theme != null) {
+                       theme.apply(application);
+                       String themeHeaders = theme.getAdditionalHeaders();
+                       if (themeHeaders != null) {
+                               if (additionalHeaders == null)
+                                       additionalHeaders = themeHeaders;
+                               else
+                                       additionalHeaders = themeHeaders + "\n" + additionalHeaders;
+                       }
+                       themeId = theme.getThemeId();
+               }
+
+               for (String appUiName : ui.keySet()) {
+                       AppUi appUi = ui.get(appUiName);
+                       appUi.apply(repository, application, this, appUiName);
+               }
+
+       }
+
+       public void register(BundleContext bundleContext, ApplicationConfiguration appConfig) {
+               Hashtable<String, String> props = new Hashtable<>();
+               props.put(CONTEXT_NAME, webPath);
+               appConfigReg = bundleContext.registerService(ApplicationConfiguration.class, appConfig, props);
+       }
+
+       public void reload() {
+               BundleContext bundleContext = appConfigReg.getReference().getBundle().getBundleContext();
+               ApplicationConfiguration appConfig = bundleContext.getService(appConfigReg.getReference());
+               appConfigReg.unregister();
+               register(bundleContext, appConfig);
+
+               // BundleContext bundleContext = (BundleContext)
+               // getScriptEngine().get("bundleContext");
+               // try {
+               // Bundle bundle = bundleContext.getBundle();
+               // bundle.stop();
+               // bundle.start();
+               // } catch (BundleException e) {
+               // // TODO Auto-generated catch block
+               // e.printStackTrace();
+               // }
+       }
+
+       private static ResourceLoader createResourceLoader(final String resourceName) {
+               return new ResourceLoader() {
+                       public InputStream getResourceAsStream(String resourceName) throws IOException {
+                               return getClass().getClassLoader().getResourceAsStream(resourceName);
+                       }
+               };
+       }
+
+       public List<String> getResources() {
+               return resources;
+       }
+
+       public AppUi newUi(String name) {
+               if (ui.containsKey(name))
+                       throw new IllegalArgumentException("There is already an UI named " + name);
+               AppUi appUi = new AppUi(this);
+               // appUi.setApp(this);
+               ui.put(name, appUi);
+               return appUi;
+       }
+
+       public void addUi(String name, AppUi appUi) {
+               if (ui.containsKey(name))
+                       throw new IllegalArgumentException("There is already an UI named " + name);
+               // appUi.setApp(this);
+               ui.put(name, appUi);
+       }
+
+       public void applyBranding(Map<String, String> properties) {
+               if (themeId != null)
+                       properties.put(WebClient.THEME_ID, themeId);
+               if (additionalHeaders != null)
+                       properties.put(WebClient.HEAD_HTML, additionalHeaders);
+               if (bodyHtml != null)
+                       properties.put(WebClient.BODY_HTML, bodyHtml);
+               if (pageTitle != null)
+                       properties.put(WebClient.PAGE_TITLE, pageTitle);
+               if (pageOverflow != null)
+                       properties.put(WebClient.PAGE_OVERFLOW, pageOverflow);
+               if (favicon != null)
+                       properties.put(WebClient.FAVICON, favicon);
+       }
+
+       class CmsExceptionHandler implements ExceptionHandler {
+
+               @Override
+               public void handleException(Throwable throwable) {
+                       // TODO be smarter
+                       CmsUtils.getCmsView().exception(throwable);
+               }
+
+       }
+
+       // public Branding getBranding() {
+       // return branding;
+       // }
+
+       ScriptEngine getScriptEngine() {
+               return scriptEngine;
+       }
+
+       public static String toJson(Node node) {
+               try {
+                       StringBuilder sb = new StringBuilder();
+                       sb.append('{');
+                       PropertyIterator pit = node.getProperties();
+                       int count = 0;
+                       while (pit.hasNext()) {
+                               Property p = pit.nextProperty();
+                               int type = p.getType();
+                               if (type == PropertyType.REFERENCE || type == PropertyType.WEAKREFERENCE || type == PropertyType.PATH) {
+                                       Node ref = p.getNode();
+                                       if (count != 0)
+                                               sb.append(',');
+                                       // TODO limit depth?
+                                       sb.append(toJson(ref));
+                                       count++;
+                               } else if (!p.isMultiple()) {
+                                       if (count != 0)
+                                               sb.append(',');
+                                       sb.append('\"').append(p.getName()).append("\":\"").append(p.getString()).append('\"');
+                                       count++;
+                               }
+                       }
+                       sb.append('}');
+                       return sb.toString();
+               } catch (RepositoryException e) {
+                       throw new CmsException("Cannot convert " + node + " to JSON", e);
+               }
+       }
+
+       public void fromJson(Node node, String json) {
+               // TODO
+       }
+
+       public Theme getTheme() {
+               return theme;
+       }
+
+       public void setTheme(Theme theme) {
+               this.theme = theme;
+       }
+
+       public String getWebPath() {
+               return webPath;
+       }
+
+       public void setWebPath(String context) {
+               this.webPath = context;
+       }
+
+       public String getRepo() {
+               return repo;
+       }
+
+       public void setRepo(String repo) {
+               this.repo = repo;
+       }
+
+       public Map<String, AppUi> getUi() {
+               return ui;
+       }
+
+       public void setUi(Map<String, AppUi> ui) {
+               this.ui = ui;
+       }
+
+       // Branding
+       public String getThemeId() {
+               return themeId;
+       }
+
+       public void setThemeId(String themeId) {
+               this.themeId = themeId;
+       }
+
+       public String getAdditionalHeaders() {
+               return additionalHeaders;
+       }
+
+       public void setAdditionalHeaders(String additionalHeaders) {
+               this.additionalHeaders = additionalHeaders;
+       }
+
+       public String getBodyHtml() {
+               return bodyHtml;
+       }
+
+       public void setBodyHtml(String bodyHtml) {
+               this.bodyHtml = bodyHtml;
+       }
+
+       public String getPageTitle() {
+               return pageTitle;
+       }
+
+       public void setPageTitle(String pageTitle) {
+               this.pageTitle = pageTitle;
+       }
+
+       public String getPageOverflow() {
+               return pageOverflow;
+       }
+
+       public void setPageOverflow(String pageOverflow) {
+               this.pageOverflow = pageOverflow;
+       }
+
+       public String getFavicon() {
+               return favicon;
+       }
+
+       public void setFavicon(String favicon) {
+               this.favicon = favicon;
+       }
+
+}
diff --git a/org.argeo.cms.ui/src/org/argeo/cms/script/CmsScriptRwtApplication.java b/org.argeo.cms.ui/src/org/argeo/cms/script/CmsScriptRwtApplication.java
new file mode 100644 (file)
index 0000000..1bc2b0b
--- /dev/null
@@ -0,0 +1,104 @@
+package org.argeo.cms.script;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URL;
+
+import javax.jcr.Repository;
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+import javax.script.ScriptException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.cms.CmsException;
+import org.eclipse.rap.rwt.application.Application;
+import org.eclipse.rap.rwt.application.ApplicationConfiguration;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.BundleException;
+import org.osgi.framework.wiring.BundleWiring;
+
+public class CmsScriptRwtApplication implements ApplicationConfiguration {
+       public final static String APP = "APP";
+       public final static String BC = "BC";
+
+       private final Log log = LogFactory.getLog(CmsScriptRwtApplication.class);
+
+       BundleContext bundleContext;
+       Repository repository;
+
+       ScriptEngine engine;
+
+       public void init(BundleContext bundleContext) {
+               this.bundleContext = bundleContext;
+               // System.out.println("bundleContext=" + bundleContext);
+               // System.out.println("repository=" + repository);
+               ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
+               ClassLoader bundleCl = bundleContext.getBundle().adapt(BundleWiring.class).getClassLoader();
+               ClassLoader currentCcl = Thread.currentThread().getContextClassLoader();
+               try {
+                       Thread.currentThread().setContextClassLoader(bundleCl);
+                       engine = scriptEngineManager.getEngineByName("JavaScript");
+               } catch (Exception e) {
+                       e.printStackTrace();
+               } finally {
+                       Thread.currentThread().setContextClassLoader(currentCcl);
+               }
+
+               // Load script
+               URL appUrl = bundleContext.getBundle().getEntry("cms/app.js");
+               // System.out.println("Loading " + appUrl);
+               // System.out.println("Loading " + appUrl.getHost());
+               // System.out.println("Loading " + appUrl.getPath());
+
+               CmsScriptApp app = new CmsScriptApp(engine);
+               engine.put(APP, app);
+               engine.put(BC, bundleContext);
+               try (Reader reader = new InputStreamReader(appUrl.openStream())) {
+                       engine.eval(reader);
+               } catch (IOException | ScriptException e) {
+                       throw new CmsException("Cannot execute " + appUrl, e);
+               }
+
+               if (log.isDebugEnabled())
+                       log.debug("CMS script app initialized from " + appUrl);
+
+       }
+
+       public void destroy(BundleContext bundleContext) {
+               engine = null;
+       }
+
+       @Override
+       public void configure(Application application) {
+               load(application);
+       }
+
+       void load(Application application) {
+               CmsScriptApp app = getApp();
+               app.apply(bundleContext, repository, application);
+               if (log.isDebugEnabled())
+                       log.debug("CMS script app loaded to " + app.getWebPath());
+       }
+
+       CmsScriptApp getApp() {
+               if (engine == null)
+                       throw new IllegalStateException("CMS script app is not initialized");
+               return (CmsScriptApp) engine.get(APP);
+       }
+
+       void update() {
+
+               try {
+                       bundleContext.getBundle().update();
+               } catch (BundleException e) {
+                       e.printStackTrace();
+               }
+       }
+
+       public void setRepository(Repository repository) {
+               this.repository = repository;
+       }
+
+}
diff --git a/org.argeo.cms.ui/src/org/argeo/cms/script/ScriptAppActivator.java b/org.argeo.cms.ui/src/org/argeo/cms/script/ScriptAppActivator.java
new file mode 100644 (file)
index 0000000..5ff89b5
--- /dev/null
@@ -0,0 +1,46 @@
+package org.argeo.cms.script;
+
+import javax.jcr.Repository;
+
+import org.osgi.framework.BundleActivator;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.FrameworkUtil;
+import org.osgi.framework.ServiceReference;
+import org.osgi.util.tracker.ServiceTracker;
+
+public class ScriptAppActivator implements BundleActivator {
+//     ServiceRegistration<ApplicationConfiguration> appConfigReg;
+
+       @Override
+       public void start(BundleContext context) throws Exception {
+               CmsScriptRwtApplication appConfig = new CmsScriptRwtApplication();
+               appConfig.init(context);
+               CmsScriptApp app = appConfig.getApp();
+               ServiceTracker<Repository, Repository> repoSt = new ServiceTracker<Repository, Repository>(context,
+                               FrameworkUtil.createFilter("(&" + app.getRepo() + "(objectClass=javax.jcr.Repository))"), null) {
+
+                       @Override
+                       public Repository addingService(ServiceReference<Repository> reference) {
+                               Repository repository = super.addingService(reference);
+                               appConfig.setRepository(repository);
+                               CmsScriptApp app = appConfig.getApp();
+                               app.register(context, appConfig);
+//                             Hashtable<String, String> props = new Hashtable<>();
+//                             if (app.getWebPath() != null)
+//                                     props.put("contextName", app.getWebPath());
+//                             appConfigReg = context.registerService(ApplicationConfiguration.class, appConfig, props);
+                               return repository;
+                       }
+
+               };
+               repoSt.open();
+       }
+
+       @Override
+       public void stop(BundleContext context) throws Exception {
+//             if (appConfigReg != null)
+//                     appConfigReg.unregister();
+
+       }
+
+}
diff --git a/org.argeo.cms.ui/src/org/argeo/cms/script/ScriptUi.java b/org.argeo.cms.ui/src/org/argeo/cms/script/ScriptUi.java
new file mode 100644 (file)
index 0000000..c8b3bb3
--- /dev/null
@@ -0,0 +1,111 @@
+package org.argeo.cms.script;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.Reader;
+import java.net.URL;
+
+import javax.jcr.Node;
+import javax.jcr.RepositoryException;
+import javax.script.Invocable;
+import javax.script.ScriptEngine;
+import javax.script.ScriptEngineManager;
+import javax.script.ScriptException;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.cms.ui.CmsUiProvider;
+import org.eclipse.swt.widgets.Composite;
+import org.eclipse.swt.widgets.Control;
+import org.osgi.framework.BundleContext;
+import org.osgi.framework.wiring.BundleWiring;
+
+public class ScriptUi implements CmsUiProvider {
+       private final static Log log = LogFactory.getLog(ScriptUi.class);
+
+       private boolean development = true;
+       private ScriptEngine scriptEngine;
+
+       private URL appUrl;
+       // private BundleContext bundleContext;
+       // private String path;
+
+       // private Bindings bindings;
+       // private String script;
+
+       public ScriptUi(BundleContext bundleContext, String path) {
+               ScriptEngineManager scriptEngineManager = new ScriptEngineManager();
+               ClassLoader bundleCl = bundleContext.getBundle().adapt(BundleWiring.class).getClassLoader();
+               ClassLoader currentCcl = Thread.currentThread().getContextClassLoader();
+               try {
+                       Thread.currentThread().setContextClassLoader(bundleCl);
+                       scriptEngine = scriptEngineManager.getEngineByName("JavaScript");
+                       scriptEngine.put(CmsScriptRwtApplication.BC, bundleContext);
+               } catch (Exception e) {
+                       e.printStackTrace();
+               } finally {
+                       Thread.currentThread().setContextClassLoader(currentCcl);
+               }
+               this.appUrl = bundleContext.getBundle().getEntry(path);
+               load();
+       }
+
+       private void load() {
+               try (Reader reader = new InputStreamReader(appUrl.openStream())) {
+                       scriptEngine.eval(reader);
+               } catch (IOException | ScriptException e) {
+                       log.warn("Cannot execute " + appUrl, e);
+               }
+       }
+
+       // public ScriptUiProvider(ScriptEngine scriptEngine, String script) throws
+       // ScriptException {
+       // super();
+       // this.scriptEngine = scriptEngine;
+       // this.script = script;
+       // bindings = scriptEngine.createBindings();
+       // scriptEngine.eval(script, bindings);
+       // }
+
+       @Override
+       public Control createUi(Composite parent, Node context) throws RepositoryException {
+               long begin = System.currentTimeMillis();
+               // if (bindings == null) {
+               // bindings = scriptEngine.createBindings();
+               // try {
+               // scriptEngine.eval(script, bindings);
+               // } catch (ScriptException e) {
+               // log.warn("Cannot evaluate script", e);
+               // }
+               // }
+               // Bindings bindings = scriptEngine.createBindings();
+               // bindings.put("parent", parent);
+               // bindings.put("context", context);
+               // URL appUrl = bundleContext.getBundle().getEntry(path);
+               // try (Reader reader = new InputStreamReader(appUrl.openStream())) {
+               // scriptEngine.eval(reader,bindings);
+               // } catch (IOException | ScriptException e) {
+               // log.warn("Cannot execute " + appUrl, e);
+               // }
+
+               if (development)
+                       load();
+
+               Invocable invocable = (Invocable) scriptEngine;
+               try {
+                       invocable.invokeFunction("createUi", parent, context);
+               } catch (NoSuchMethodException e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               } catch (ScriptException e) {
+                       // TODO Auto-generated catch block
+                       e.printStackTrace();
+               }
+
+               long duration = System.currentTimeMillis() - begin;
+               if (log.isDebugEnabled())
+                       log.debug(appUrl + " UI in " + duration + " ms");
+               return null;
+       }
+
+}
diff --git a/org.argeo.cms.ui/src/org/argeo/cms/script/Theme.java b/org.argeo.cms.ui/src/org/argeo/cms/script/Theme.java
new file mode 100644 (file)
index 0000000..51fc65d
--- /dev/null
@@ -0,0 +1,148 @@
+package org.argeo.cms.script;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.argeo.cms.CmsException;
+import org.argeo.cms.util.BundleResourceLoader;
+import org.argeo.cms.util.ThemeUtils;
+import org.eclipse.rap.rwt.application.Application;
+import org.eclipse.rap.rwt.service.ResourceLoader;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+
+public class Theme {
+       private final static Log log = LogFactory.getLog(Theme.class);
+
+       private String themeId;
+       private Map<String, ResourceLoader> css = new HashMap<>();
+       private Map<String, ResourceLoader> resources = new HashMap<>();
+
+       private String headerCss;
+       private List<String> fonts = new ArrayList<>();
+
+       public Theme(BundleContext bundleContext, String symbolicName) {
+               this.themeId = symbolicName;
+               Bundle themeBundle = ThemeUtils.findThemeBundle(bundleContext, symbolicName);
+               addStyleSheets(themeBundle, new BundleResourceLoader(themeBundle));
+               BundleResourceLoader themeBRL = new BundleResourceLoader(themeBundle);
+               addResources(themeBRL, "*.png");
+               addResources(themeBRL, "*.gif");
+               addResources(themeBRL, "*.jpg");
+               addResources(themeBRL, "*.jpeg");
+               addResources(themeBRL, "*.svg");
+               addResources(themeBRL, "*.ico");
+
+               // fonts
+               URL fontsUrl = themeBundle.getEntry("fonts.txt");
+               if (fontsUrl != null) {
+                       loadFontsUrl(fontsUrl);
+               }
+
+               // common CSS header (plain CSS)
+               URL headerCssUrl = themeBundle.getEntry("header.css");
+               if (headerCssUrl != null) {
+                       try (BufferedReader buffer = new BufferedReader(new InputStreamReader(headerCssUrl.openStream(), UTF_8))) {
+                               headerCss = buffer.lines().collect(Collectors.joining("\n"));
+                       } catch (IOException e) {
+                               throw new CmsException("Cannot read " + headerCssUrl, e);
+                       }
+               }
+
+       }
+
+       public void apply(Application application) {
+               for (String name : resources.keySet()) {
+                       application.addResource(name, resources.get(name));
+               }
+               for (String name : css.keySet()) {
+                       application.addStyleSheet(themeId, name, css.get(name));
+               }
+       }
+
+       public String getAdditionalHeaders() {
+               StringBuilder sb = new StringBuilder();
+               if (headerCss != null) {
+                       sb.append("<style type='text/css'>\n");
+                       sb.append(headerCss);
+                       sb.append("\n</style>\n");
+               }
+               for (String link : fonts) {
+                       sb.append("<link rel='stylesheet' href='");
+                       sb.append(link);
+                       sb.append("'/>\n");
+               }
+               if (sb.length() == 0)
+                       return null;
+               else
+                       return sb.toString();
+       }
+
+       void addStyleSheets(Bundle themeBundle, ResourceLoader ssRL) {
+               Enumeration<URL> themeResources = themeBundle.findEntries("/rap", "*.css", 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("/")) {
+                               if (css.containsKey(resource))
+                                       log.warn("Overriding " + resource + " from " + themeBundle.getSymbolicName());
+                               css.put(resource, ssRL);
+                       }
+
+               }
+
+       }
+
+       void loadFontsUrl(URL url) {
+               try (BufferedReader in = new BufferedReader(new InputStreamReader(url.openStream(), UTF_8))) {
+                       String line = null;
+                       while ((line = in.readLine()) != null) {
+                               line = line.trim();
+                               if (!line.equals("") && !line.startsWith("#")) {
+                                       fonts.add(line);
+                               }
+                       }
+               } catch (IOException e) {
+                       throw new CmsException("Cannot load URL " + url, e);
+               }
+       }
+
+       void addResources(BundleResourceLoader themeBRL, String pattern) {
+               Bundle themeBundle = themeBRL.getBundle();
+               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("/")) {
+                               if (resources.containsKey(resource))
+                                       log.warn("Overriding " + resource + " from " + themeBundle.getSymbolicName());
+                               resources.put(resource, themeBRL);
+                       }
+
+               }
+
+       }
+
+       public String getThemeId() {
+               return themeId;
+       }
+
+}
diff --git a/org.argeo.cms.ui/src/org/argeo/cms/script/cms.js b/org.argeo.cms.ui/src/org/argeo/cms/script/cms.js
new file mode 100644 (file)
index 0000000..1aacd2c
--- /dev/null
@@ -0,0 +1,44 @@
+//var CmsUiProvider = Java.type('org.argeo.cms.ui.CmsUiProvider');
+var CmsScriptApp = Java.type('org.argeo.cms.script.CmsScriptApp');
+var AppUi = Java.type('org.argeo.cms.script.AppUi');
+var Theme = Java.type('org.argeo.cms.script.Theme');
+var ScriptUi = Java.type('org.argeo.cms.script.ScriptUi');
+var CmsUtils = Java.type('org.argeo.cms.util.CmsUtils');
+
+// SWT
+var SWT = Java.type('org.eclipse.swt.SWT');
+var Composite = Java.type('org.eclipse.swt.widgets.Composite');
+var Label = Java.type('org.eclipse.swt.widgets.Label');
+var Button = Java.type('org.eclipse.swt.widgets.Button');
+var Text = Java.type('org.eclipse.swt.widgets.Text');
+
+var FillLayout = Java.type('org.eclipse.swt.layout.FillLayout');
+var GridLayout = Java.type('org.eclipse.swt.layout.GridLayout');
+var GridData = Java.type('org.eclipse.swt.layout.GridData');
+
+function loadNode(node) {
+       var json = CmsScriptApp.toJson(node)
+       var fromJson = JSON.parse(json)
+       return fromJson
+}
+
+function newLabel(parent, style, text) {
+       var control = new Label(parent, SWT.NONE)
+       control.setText(text)
+       CmsUtils.style(control, style)
+       CmsUtils.markup(control)
+       return control
+}
+
+function newFormLabel(parent, style, text) {
+       return newLabel(parent, style, '<b>' + text + '</b>')
+}
+
+function newText(parent, style, msg) {
+       var control = new Text(parent, SWT.NONE)
+       control.setMessage(msg)
+       CmsUtils.style(control, style)
+       return control
+}
+
+// print(__FILE__, __LINE__, __DIR__)
index c8fb8a40cdb5c1db652b2c04b9bc0e52ea365d5d..21ee78ec8a8f8e5a5202e4919caf12cd5da74ed3 100644 (file)
@@ -9,7 +9,7 @@ import org.eclipse.rap.rwt.service.ResourceLoader;
 import org.osgi.framework.Bundle;
 
 /** {@link ResourceLoader} implementation wrapping an {@link Bundle}. */
-class BundleResourceLoader implements ResourceLoader {
+public class BundleResourceLoader implements ResourceLoader {
        private final Bundle bundle;
 
        public BundleResourceLoader(Bundle bundle) {
@@ -18,10 +18,14 @@ class BundleResourceLoader implements ResourceLoader {
 
        @Override
        public InputStream getResourceAsStream(String resourceName) throws IOException {
-               URL res = bundle.getResource(resourceName);
+               URL res = bundle.getEntry(resourceName);
                if (res == null)
                        throw new CmsException("Resource " + resourceName + " not found in bundle " + bundle.getSymbolicName());
                return res.openStream();
        }
 
+       public Bundle getBundle() {
+               return bundle;
+       }
+
 }
diff --git a/org.argeo.cms.ui/src/org/argeo/cms/util/CmsPane.java b/org.argeo.cms.ui/src/org/argeo/cms/util/CmsPane.java
new file mode 100644 (file)
index 0000000..f64768d
--- /dev/null
@@ -0,0 +1,48 @@
+package org.argeo.cms.util;
+
+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(CmsUtils.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;
+       }
+
+}
index b75c700078d7bff8a81840390a94f3a99c2c30da..5b0e1b779cf991ea41cb213cb7b120e893d1419b 100644 (file)
@@ -2,10 +2,8 @@ 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;
@@ -109,7 +107,7 @@ public class SimpleApp implements CmsConstants, ApplicationConfiguration {
                                // favicon
                                if (properties.containsKey(WebClient.FAVICON)) {
                                        String themeId = defaultBranding.get(WebClient.THEME_ID);
-                                       Bundle themeBundle = findThemeBundle(themeId);
+                                       Bundle themeBundle = ThemeUtils.findThemeBundle(bundleContext, themeId);
                                        String faviconRelPath = properties.get(WebClient.FAVICON);
                                        application.addResource(faviconRelPath,
                                                        new BundleResourceLoader(themeBundle != null ? themeBundle : bundleContext.getBundle()));
@@ -139,7 +137,7 @@ public class SimpleApp implements CmsConstants, ApplicationConfiguration {
                        // stylesheets and themes
                        Set<Bundle> themeBundles = new HashSet<>();
                        for (String themeId : styleSheets.keySet()) {
-                               Bundle themeBundle = findThemeBundle(themeId);
+                               Bundle themeBundle = ThemeUtils.findThemeBundle(bundleContext, themeId);
                                StyleSheetResourceLoader styleSheetRL = new StyleSheetResourceLoader(
                                                themeBundle != null ? themeBundle : bundleContext.getBundle());
                                if (themeBundle != null)
@@ -156,9 +154,9 @@ 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");
+                               ThemeUtils.addThemeResources(application, themeBundle, themeBRL, "*.png");
+                               ThemeUtils.addThemeResources(application, themeBundle, themeBRL, "*.gif");
+                               ThemeUtils.addThemeResources(application, themeBundle, themeBRL, "*.jpg");
                        }
                } catch (RuntimeException e) {
                        // Easier access to initialisation errors
@@ -167,41 +165,6 @@ 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 505d482f3befd2e9de8a9ee35c3ce210c0d42661..37464079155e3a4097325ae656f5b82d4c024997 100644 (file)
@@ -34,7 +34,7 @@ public class SimpleErgonomics extends AbstractCmsEntryPoint {
        private final CmsUiProvider uiProvider;
 
        private CmsUiProvider header;
-       private Integer headerHeight = 40;
+       private Integer headerHeight = 0;
 
        private CmsImageManager imageManager = new ImageManagerImpl();
        private UxContext uxContext = null;
@@ -83,6 +83,9 @@ public class SimpleErgonomics extends AbstractCmsEntryPoint {
        }
 
        protected void refreshHeader() {
+               if (header == null)
+                       return;
+
                for (Control child : headerArea.getChildren())
                        child.dispose();
                try {
index 1df98d484963d7fad233283aff31a4e4972fc5e9..face42b0fe780d359399ead8cde83313b51caa27 100644 (file)
@@ -14,7 +14,7 @@ import org.eclipse.rap.rwt.service.ResourceLoader;
 import org.osgi.framework.Bundle;
 
 /** {@link ResourceLoader} caching stylesheets. */
-class StyleSheetResourceLoader implements ResourceLoader {
+public class StyleSheetResourceLoader implements ResourceLoader {
        private Bundle themeBundle;
        private Map<String, StyleSheet> stylesheets = new LinkedHashMap<String, StyleSheet>();
 
@@ -43,10 +43,10 @@ class StyleSheetResourceLoader implements ResourceLoader {
                        // }
                        // }
 
-                       URL res = themeBundle.getResource(resourceName);
+                       URL res = themeBundle.getEntry(resourceName);
                        if (res == null)
                                throw new CmsException(
-                                               "Resource " + resourceName + " not found in bundle " + themeBundle.getSymbolicName());
+                                               "Entry " + resourceName + " not found in bundle " + themeBundle.getSymbolicName());
                        ByteArrayOutputStream out = new ByteArrayOutputStream();
                        IOUtils.copy(res.openStream(), out);
                        stylesheets.put(resourceName, new StyleSheet(out.toByteArray()));
diff --git a/org.argeo.cms.ui/src/org/argeo/cms/util/ThemeUtils.java b/org.argeo.cms.ui/src/org/argeo/cms/util/ThemeUtils.java
new file mode 100644 (file)
index 0000000..fdc1cb7
--- /dev/null
@@ -0,0 +1,50 @@
+package org.argeo.cms.util;
+
+import java.net.URL;
+import java.util.Enumeration;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+import org.eclipse.rap.rwt.application.Application;
+import org.osgi.framework.Bundle;
+import org.osgi.framework.BundleContext;
+
+public class ThemeUtils {
+       final static Log log = LogFactory.getLog(ThemeUtils.class);
+
+       public static Bundle findThemeBundle(BundleContext bundleContext, 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;
+       }
+
+       public static 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);
+                       }
+
+               }
+
+       }
+
+}