From a667d43d6a3ccef0e701320e9a61e50d8608615e Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Sat, 15 Sep 2018 21:43:40 +0200 Subject: [PATCH] CMS Script --- .../src/org/argeo/cms/script/AppUi.java | 244 ++++++++++++++ .../src/org/argeo/cms/script/Branding.java | 22 ++ .../org/argeo/cms/script/CmsScriptApp.java | 304 ++++++++++++++++++ .../cms/script/CmsScriptRwtApplication.java | 104 ++++++ .../argeo/cms/script/ScriptAppActivator.java | 46 +++ .../src/org/argeo/cms/script/ScriptUi.java | 111 +++++++ .../src/org/argeo/cms/script/Theme.java | 148 +++++++++ .../src/org/argeo/cms/script/cms.js | 44 +++ .../argeo/cms/util/BundleResourceLoader.java | 8 +- .../src/org/argeo/cms/util/CmsPane.java | 48 +++ .../src/org/argeo/cms/util/SimpleApp.java | 47 +-- .../org/argeo/cms/util/SimpleErgonomics.java | 5 +- .../cms/util/StyleSheetResourceLoader.java | 6 +- .../src/org/argeo/cms/util/ThemeUtils.java | 50 +++ 14 files changed, 1139 insertions(+), 48 deletions(-) create mode 100644 org.argeo.cms.ui/src/org/argeo/cms/script/AppUi.java create mode 100644 org.argeo.cms.ui/src/org/argeo/cms/script/Branding.java create mode 100644 org.argeo.cms.ui/src/org/argeo/cms/script/CmsScriptApp.java create mode 100644 org.argeo.cms.ui/src/org/argeo/cms/script/CmsScriptRwtApplication.java create mode 100644 org.argeo.cms.ui/src/org/argeo/cms/script/ScriptAppActivator.java create mode 100644 org.argeo.cms.ui/src/org/argeo/cms/script/ScriptUi.java create mode 100644 org.argeo.cms.ui/src/org/argeo/cms/script/Theme.java create mode 100644 org.argeo.cms.ui/src/org/argeo/cms/script/cms.js create mode 100644 org.argeo.cms.ui/src/org/argeo/cms/util/CmsPane.java create mode 100644 org.argeo.cms.ui/src/org/argeo/cms/util/ThemeUtils.java 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 index 000000000..cea57df89 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/script/AppUi.java @@ -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 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 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 index 000000000..2a99191d2 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/script/Branding.java @@ -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 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 index 000000000..e6ccf52a7 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/script/CmsScriptApp.java @@ -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 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 resources = new ArrayList<>(); + + private Map 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 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 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 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 getUi() { + return ui; + } + + public void setUi(Map 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 index 000000000..1bc2b0b5a --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/script/CmsScriptRwtApplication.java @@ -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 index 000000000..5ff89b5fe --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/script/ScriptAppActivator.java @@ -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 appConfigReg; + + @Override + public void start(BundleContext context) throws Exception { + CmsScriptRwtApplication appConfig = new CmsScriptRwtApplication(); + appConfig.init(context); + CmsScriptApp app = appConfig.getApp(); + ServiceTracker repoSt = new ServiceTracker(context, + FrameworkUtil.createFilter("(&" + app.getRepo() + "(objectClass=javax.jcr.Repository))"), null) { + + @Override + public Repository addingService(ServiceReference reference) { + Repository repository = super.addingService(reference); + appConfig.setRepository(repository); + CmsScriptApp app = appConfig.getApp(); + app.register(context, appConfig); +// Hashtable 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 index 000000000..c8b3bb352 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/script/ScriptUi.java @@ -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 index 000000000..51fc65dda --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/script/Theme.java @@ -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 css = new HashMap<>(); + private Map resources = new HashMap<>(); + + private String headerCss; + private List 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("\n"); + } + for (String link : fonts) { + sb.append("\n"); + } + if (sb.length() == 0) + return null; + else + return sb.toString(); + } + + void addStyleSheets(Bundle themeBundle, ResourceLoader ssRL) { + Enumeration 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 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 index 000000000..1aacd2c4a --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/script/cms.js @@ -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, '' + text + '') +} + +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__) diff --git a/org.argeo.cms.ui/src/org/argeo/cms/util/BundleResourceLoader.java b/org.argeo.cms.ui/src/org/argeo/cms/util/BundleResourceLoader.java index c8fb8a40c..21ee78ec8 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/util/BundleResourceLoader.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/util/BundleResourceLoader.java @@ -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 index 000000000..f64768df7 --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/util/CmsPane.java @@ -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; + } + +} diff --git a/org.argeo.cms.ui/src/org/argeo/cms/util/SimpleApp.java b/org.argeo.cms.ui/src/org/argeo/cms/util/SimpleApp.java index b75c70007..5b0e1b779 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/util/SimpleApp.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/util/SimpleApp.java @@ -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 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 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 { diff --git a/org.argeo.cms.ui/src/org/argeo/cms/util/SimpleErgonomics.java b/org.argeo.cms.ui/src/org/argeo/cms/util/SimpleErgonomics.java index 505d482f3..374640791 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/util/SimpleErgonomics.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/util/SimpleErgonomics.java @@ -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 { diff --git a/org.argeo.cms.ui/src/org/argeo/cms/util/StyleSheetResourceLoader.java b/org.argeo.cms.ui/src/org/argeo/cms/util/StyleSheetResourceLoader.java index 1df98d484..face42b0f 100644 --- a/org.argeo.cms.ui/src/org/argeo/cms/util/StyleSheetResourceLoader.java +++ b/org.argeo.cms.ui/src/org/argeo/cms/util/StyleSheetResourceLoader.java @@ -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 stylesheets = new LinkedHashMap(); @@ -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 index 000000000..fdc1cb78a --- /dev/null +++ b/org.argeo.cms.ui/src/org/argeo/cms/util/ThemeUtils.java @@ -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 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); + } + + } + + } + +} -- 2.30.2