package org.argeo.cms.ui;
+import java.io.IOException;
import java.security.PrivilegedAction;
import java.util.HashMap;
import java.util.Map;
import javax.jcr.Session;
import javax.jcr.nodetype.NodeType;
import javax.security.auth.Subject;
+import javax.security.auth.callback.Callback;
+import javax.security.auth.callback.CallbackHandler;
+import javax.security.auth.callback.NameCallback;
+import javax.security.auth.callback.PasswordCallback;
+import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
import javax.servlet.http.HttpServletRequest;
private Node node;
private String nodePath;// useful when changing auth
private String state;
- private String page;
private Throwable exception;
// Client services
* The node to return when no node was found (for authenticated users and
* anonymous)
*/
- protected Node getDefaultNode(Session session) throws RepositoryException {
+ private Node getDefaultNode(Session session) throws RepositoryException {
if (!session.hasPermission(defaultPath, "read")) {
String userId = session.getUserID();
if (userId.equals(NodeConstants.ROLE_ANONYMOUS))
protected synchronized String setState(String newState) {
String previousState = this.state;
- Node node = null;
- page = null;
+ String newNodePath = null;
+ String prefix = null;
this.state = newState;
if (newState.equals("~"))
this.state = "";
try {
int firstSlash = state.indexOf('/');
if (firstSlash == 0) {
- node = session.getNode(state);
- page = "";
+ newNodePath = state;
+ prefix = "";
} else if (firstSlash > 0) {
- String prefix = state.substring(0, firstSlash);
- String path = state.substring(firstSlash);
- if (session.nodeExists(path))
- node = session.getNode(path);
- else
- throw new CmsException("Data " + path + " does not exist");
- page = prefix;
+ prefix = state.substring(0, firstSlash);
+ newNodePath = state.substring(firstSlash);
} else {
- node = getDefaultNode(session);
- page = state;
+ newNodePath = defaultPath;
+ prefix = state;
+
+ }
+
+ // auth
+ int colonIndex = prefix.indexOf(':');
+ if (colonIndex > 0) {
+ String user = prefix.substring(0, colonIndex);
+ // if (isAnonymous()) {
+ String token = prefix.substring(colonIndex + 1);
+ LoginContext lc = new LoginContext(NodeConstants.LOGIN_CONTEXT_USER, new CallbackHandler() {
+
+ @Override
+ public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
+ for (Callback callback : callbacks) {
+ if (callback instanceof NameCallback)
+ ((NameCallback) callback).setName(user);
+ else if (callback instanceof PasswordCallback)
+ ((PasswordCallback) callback).setPassword(token.toCharArray());
+ }
+
+ }
+ });
+ lc.login();
+ authChange(lc);// sets the node as well
+ // } else {
+ // // TODO check consistency
+ // }
+ } else {
+ Node newNode = null;
+ if (session.nodeExists(newNodePath))
+ newNode = session.getNode(newNodePath);
+ else
+ throw new CmsException("Data " + newNodePath + " does not exist");
+ setNode(newNode);
}
- setNode(node);
- String title = publishMetaData(node);
+ String title = publishMetaData(getNode());
if (log.isTraceEnabled())
- log.trace("node=" + node + ", state=" + state + " (page=" + page + ")");
+ log.trace("node=" + newNodePath + ", state=" + state + " (prefix=" + prefix + ")");
return title;
} catch (Exception e) {
public interface UxContext {
boolean isPortrait();
+
boolean isLandscape();
+
boolean isSquare();
-
+
boolean isSmall();
+
+ /**
+ * Is a production environment (must be false by default, and be explicitly
+ * set during the CMS deployment). When false, it can activate additional UI
+ * capabilities in order to facilitate QA.
+ */
+ boolean isMasterData();
}
import org.argeo.cms.CmsException;
import org.eclipse.rap.rwt.service.ResourceLoader;
import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
/** {@link ResourceLoader} implementation wrapping an {@link Bundle}. */
-public class BundleResourceLoader implements ResourceLoader {
- private final BundleContext bundleContext;
+class BundleResourceLoader implements ResourceLoader {
+ private final Bundle bundle;
- public BundleResourceLoader(BundleContext bundleContext) {
- this.bundleContext = bundleContext;
+ public BundleResourceLoader(Bundle bundle) {
+ this.bundle = bundle;
}
@Override
- public InputStream getResourceAsStream(String resourceName)
- throws IOException {
- // TODO deal with other bundles
- Bundle bundle = bundleContext.getBundle();
- // String location =
- // bundle.getLocation().substring("initial@reference:".length());
- // if (location.startsWith("file:")) {
- // Path path = null;
- // try {
- // path = Paths.get(new URI(location));
- // } catch (URISyntaxException e) {
- // e.printStackTrace();
- // }
- // if (path != null) {
- // Path resourcePath = path.resolve(resourceName);
- // if (Files.exists(resourcePath))
- // return Files.newInputStream(resourcePath);
- // }
- // }
+ public InputStream getResourceAsStream(String resourceName) throws IOException {
URL res = bundle.getResource(resourceName);
if (res == null)
- throw new CmsException("Resource " + resourceName
- + " not found in bundle " + bundle.getSymbolicName());
+ throw new CmsException("Resource " + resourceName + " not found in bundle " + bundle.getSymbolicName());
return res.openStream();
}
import java.io.IOException;
import java.io.InputStream;
+import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
+import java.util.Enumeration;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Hashtable;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import javax.jcr.Repository;
import javax.jcr.RepositoryException;
import org.eclipse.swt.layout.FillLayout;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
+import org.osgi.framework.Bundle;
import org.osgi.framework.BundleContext;
import org.osgi.framework.ServiceRegistration;
public void configure(Application application) {
try {
- StyleSheetResourceLoader styleSheetRL = new StyleSheetResourceLoader(bundleContext);
- BundleResourceLoader bundleRL = new BundleResourceLoader(bundleContext);
+ BundleResourceLoader bundleRL = new BundleResourceLoader(bundleContext.getBundle());
application.setOperationMode(OperationMode.SWT_COMPATIBILITY);
// application.setOperationMode(OperationMode.JEE_COMPATIBILITY);
Map<String, String> defaultBranding = null;
if (branding.containsKey("*"))
defaultBranding = branding.get("*");
+ String defaultTheme = defaultBranding.get(WebClient.THEME_ID);
// entry points
for (String page : pages.keySet()) {
}
// favicon
if (properties.containsKey(WebClient.FAVICON)) {
+ String themeId = defaultBranding.get(WebClient.THEME_ID);
+ Bundle themeBundle = findThemeBundle(themeId);
String faviconRelPath = properties.get(WebClient.FAVICON);
- application.addResource(faviconRelPath, new BundleResourceLoader(bundleContext));
+ application.addResource(faviconRelPath,
+ new BundleResourceLoader(themeBundle != null ? themeBundle : bundleContext.getBundle()));
if (log.isTraceEnabled())
log.trace("Favicon " + faviconRelPath);
log.info("Page /" + page);
}
- // stylesheets
+ // stylesheets and themes
+ Set<Bundle> themeBundles = new HashSet<>();
for (String themeId : styleSheets.keySet()) {
+ Bundle themeBundle = findThemeBundle(themeId);
+ StyleSheetResourceLoader styleSheetRL = new StyleSheetResourceLoader(
+ themeBundle != null ? themeBundle : bundleContext.getBundle());
+ if (themeBundle != null)
+ themeBundles.add(themeBundle);
List<String> cssLst = styleSheets.get(themeId);
if (log.isDebugEnabled())
log.debug("Theme " + themeId);
}
}
+ for (Bundle themeBundle : themeBundles) {
+ BundleResourceLoader themeBRL = new BundleResourceLoader(themeBundle);
+ addThemeResources(application, themeBundle, themeBRL, "*.png");
+ addThemeResources(application, themeBundle, themeBRL, "*.gif");
+ addThemeResources(application, themeBundle, themeBRL, "*.jpg");
+ }
} catch (RuntimeException e) {
// Easier access to initialisation errors
log.error("Unexpected exception when configuring RWT application.", e);
}
}
+ 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 {
parent.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
parent.setLayout(CmsUtils.noSpaceGridLayout());
- // createAdminArea(parent);
+ uxContext = new SimpleUxContext();
+ if (!getUxContext().isMasterData())
+ createAdminArea(parent);
headerArea = new Composite(parent, SWT.NONE);
headerArea.setLayout(new FillLayout());
GridData headerData = new GridData(SWT.FILL, SWT.FILL, false, false);
bodyArea.setData(RWT.CUSTOM_VARIANT, CmsStyles.CMS_BODY);
bodyArea.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
bodyArea.setLayout(CmsUtils.noSpaceGridLayout());
- uxContext = new SimpleUxContext();
uiInitialized = true;
refresh();
}
return size.x <= small.x || size.y <= small.y;
}
+ @Override
+ public boolean isMasterData() {
+ return false;
+ }
+
}
import org.argeo.cms.CmsException;
import org.eclipse.rap.rwt.service.ResourceLoader;
import org.osgi.framework.Bundle;
-import org.osgi.framework.BundleContext;
/** {@link ResourceLoader} caching stylesheets. */
-public class StyleSheetResourceLoader implements ResourceLoader {
- private final BundleContext bundleContext;
-
+class StyleSheetResourceLoader implements ResourceLoader {
+ private Bundle themeBundle;
private Map<String, StyleSheet> stylesheets = new LinkedHashMap<String, StyleSheet>();
- public StyleSheetResourceLoader(BundleContext bundleContext) {
- this.bundleContext = bundleContext;
+ public StyleSheetResourceLoader(Bundle themeBundle) {
+ this.themeBundle = themeBundle;
}
@Override
- public InputStream getResourceAsStream(String resourceName)
- throws IOException {
+ public InputStream getResourceAsStream(String resourceName) throws IOException {
if (!stylesheets.containsKey(resourceName)) {
// TODO deal with other bundles
- Bundle bundle = bundleContext.getBundle();
+ // Bundle bundle = bundleContext.getBundle();
// String location =
// bundle.getLocation().substring("initial@reference:".length());
// if (location.startsWith("file:")) {
// return Files.newInputStream(resourcePath);
// }
// }
- URL res = bundle.getResource(resourceName);
+
+ URL res = themeBundle.getResource(resourceName);
if (res == null)
- throw new CmsException("Resource " + resourceName
- + " not found in bundle " + bundle.getSymbolicName());
+ throw new CmsException(
+ "Resource " + resourceName + " not found in bundle " + themeBundle.getSymbolicName());
ByteArrayOutputStream out = new ByteArrayOutputStream();
IOUtils.copy(res.openStream(), out);
stylesheets.put(resourceName, new StyleSheet(out.toByteArray()));
package org.argeo.cms.internal.backup;
import java.text.DateFormat;
+import java.time.Period;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
import java.util.Date;
import java.util.SortedMap;
import java.util.TreeMap;
import org.apache.commons.vfs2.FileSystemOptions;
import org.apache.commons.vfs2.Selectors;
import org.argeo.cms.CmsException;
-import org.joda.time.DateTime;
-import org.joda.time.Period;
/** Simple backup purge which keeps backups only for a given number of days */
public class SimpleBackupPurge implements BackupPurge {
private Integer daysKept = 30;
@Override
- public void purge(FileSystemManager fileSystemManager, String base,
- String name, DateFormat dateFormat, FileSystemOptions opts) {
+ public void purge(FileSystemManager fileSystemManager, String base, String name, DateFormat dateFormat,
+ FileSystemOptions opts) {
try {
- DateTime nowDt = new DateTime();
- FileObject baseFo = fileSystemManager.resolveFile(
- base + '/' + name, opts);
+ ZonedDateTime nowDt = ZonedDateTime.now();
+ FileObject baseFo = fileSystemManager.resolveFile(base + '/' + name, opts);
- SortedMap<DateTime, FileObject> toDelete = new TreeMap<DateTime, FileObject>();
+ SortedMap<ZonedDateTime, FileObject> toDelete = new TreeMap<ZonedDateTime, FileObject>();
int backupCount = 0;
// make sure base dir exists
String backupName = backupFo.getName().getBaseName();
Date backupDate = dateFormat.parse(backupName);
backupCount++;
-
- DateTime backupDt = new DateTime(backupDate.getTime());
- Period sinceThen = new Period(backupDt, nowDt);
+ ZonedDateTime backupDt = ZonedDateTime.ofInstant(backupDate.toInstant(), ZoneId.systemDefault());
+ Period sinceThen = Period.between(backupDt.toLocalDate(), nowDt.toLocalDate());
+ // new Period(backupDt, nowDt);
int days = sinceThen.getDays();
// int days = sinceThen.getMinutes();
if (days > daysKept) {
if (toDelete.size() != 0 && toDelete.size() == backupCount) {
// all backups would be deleted
// but we want to keep at least one
- DateTime lastBackupDt = toDelete.firstKey();
+ ZonedDateTime lastBackupDt = toDelete.firstKey();
FileObject keptFo = toDelete.remove(lastBackupDt);
- log.warn("Backup " + keptFo
- + " kept although it is older than " + daysKept
- + " days.");
+ log.warn("Backup " + keptFo + " kept although it is older than " + daysKept + " days.");
}
// delete old backups
import org.argeo.eclipse.ui.EclipseUiException;
import org.eclipse.swt.widgets.Display;
-/** {@link EventListener} which simplifies running actions within the UI thread. */
+/**
+ * {@link EventListener} which simplifies running actions within the UI thread.
+ */
public abstract class AsyncUiEventListener implements EventListener {
-// private final static Log logSuper = LogFactory
-// .getLog(AsyncUiEventListener.class);
+ // private final static Log logSuper = LogFactory
+ // .getLog(AsyncUiEventListener.class);
private final Log logThis = LogFactory.getLog(getClass());
private final Display display;
}
/** Called asynchronously in the UI thread. */
- protected abstract void onEventInUiThread(List<Event> events)
- throws RepositoryException;
+ protected abstract void onEventInUiThread(List<Event> events) throws RepositoryException;
/**
* Whether these events should be processed in the UI or skipped with no UI
* job created.
*/
- protected Boolean willProcessInUiThread(List<Event> events)
- throws RepositoryException {
+ protected Boolean willProcessInUiThread(List<Event> events) throws RepositoryException {
return true;
}
throw new EclipseUiException("Cannot test skip events " + events, e);
}
-// Job job = new Job("JCR Events") {
-// protected IStatus run(IProgressMonitor monitor) {
-// if (display.isDisposed()) {
-// logSuper.warn("Display is disposed cannot update UI");
-// return Status.CANCEL_STATUS;
-// }
+ // Job job = new Job("JCR Events") {
+ // protected IStatus run(IProgressMonitor monitor) {
+ // if (display.isDisposed()) {
+ // logSuper.warn("Display is disposed cannot update UI");
+ // return Status.CANCEL_STATUS;
+ // }
- display.asyncExec(new Runnable() {
- public void run() {
- try {
- onEventInUiThread(events);
- } catch (RepositoryException e) {
- throw new EclipseUiException("Cannot process events "
- + events, e);
- }
+ if (!display.isDisposed())
+ display.asyncExec(new Runnable() {
+ public void run() {
+ try {
+ onEventInUiThread(events);
+ } catch (RepositoryException e) {
+ throw new EclipseUiException("Cannot process events " + events, e);
}
- });
+ }
+ });
-// return Status.OK_STATUS;
-// }
-// };
-// job.schedule();
+ // return Status.OK_STATUS;
+ // }
+ // };
+ // job.schedule();
}
}