From 3baeb167c9a92d1f915904bb956632c0aff65b2b Mon Sep 17 00:00:00 2001 From: Mathieu Baudier Date: Tue, 28 Aug 2012 20:48:33 +0000 Subject: [PATCH] Restructure JCR repository wrappers RCP UI for remote node working git-svn-id: https://svn.argeo.org/commons/trunk@5543 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc --- demo/argeo_node_rcp.properties | 3 + demo/argeo_node_rcp_remote.properties | 4 +- .../META-INF/spring/security-jcr-services.xml | 1 + .../META-INF/spring/security-ldap-jcr.xml | 2 +- .../META-INF/spring/security-os.xml | 2 +- .../security/equinox/SpringLoginModule.java | 12 +- .../ui/dialogs/DefaultLoginDialog.java | 5 + .../runtime/org.argeo.security.core/pom.xml | 12 +- .../security/NodeAuthenticationToken.java | 10 +- .../jcr/RemoteJcrAuthenticationProvider.java | 71 ++- .../jcr/RemoteJcrRepositoryWrapper.java | 145 +++++ .../META-INF/spring/noderepo-osgi.xml | 1 + .../META-INF/spring/noderepo.xml | 8 - .../noderepo.properties | 7 - .../ui/explorer/views/GenericJcrBrowser.java | 6 +- .../argeo/jackrabbit/JackrabbitContainer.java | 452 +++++---------- .../argeo/jackrabbit/JackrabbitWrapper.java | 527 ++++++++---------- .../remote/SimpleSessionProvider.java | 27 +- .../java/org/argeo/jcr/ArgeoJcrUtils.java | 12 + .../org/argeo/jcr/JcrRepositoryWrapper.java | 152 +++++ .../main/resources/org/argeo/jcr/argeo.cnd | 3 +- 21 files changed, 777 insertions(+), 685 deletions(-) create mode 100644 security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/RemoteJcrRepositoryWrapper.java create mode 100644 server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrRepositoryWrapper.java diff --git a/demo/argeo_node_rcp.properties b/demo/argeo_node_rcp.properties index 2545106f7..c7979d926 100644 --- a/demo/argeo_node_rcp.properties +++ b/demo/argeo_node_rcp.properties @@ -4,6 +4,9 @@ org.argeo.node.repofactory.jackrabbit,\ org.argeo.node.repo.jackrabbit,\ org.argeo.security.dao.os,\ org.argeo.security.equinox,\ +org.argeo.dep.osgi.catalina.start,\ +org.springframework.osgi.web.extender,\ +org.argeo.jackrabbit.webapp,\ #org.argeo.security.ui.initialPerspective=org.argeo.osgi.ui.explorer.perspective #org.argeo.security.ui.initialPerspective==org.argeo.security.ui.userHomePerspective diff --git a/demo/argeo_node_rcp_remote.properties b/demo/argeo_node_rcp_remote.properties index bd2344c26..171de44e8 100644 --- a/demo/argeo_node_rcp_remote.properties +++ b/demo/argeo_node_rcp_remote.properties @@ -1,12 +1,10 @@ argeo.osgi.start=\ org.springframework.osgi.extender,\ org.argeo.node.repofactory.jackrabbit,\ -org.argeo.node.repo.jackrabbit,\ org.argeo.security.dao.jackrabbit,\ org.argeo.security.equinox,\ -org.argeo.security.ui.initialPerspective=org.argeo.osgi.ui.explorer.perspective - +#org.argeo.security.ui.initialPerspective=org.argeo.osgi.ui.explorer.perspective argeo.node.repo.uri=http://localhost:7070/org.argeo.jcr.webapp/remoting/node log4j.configuration=file:../../log4j.properties diff --git a/security/modules/org.argeo.security.dao.jackrabbit/META-INF/spring/security-jcr-services.xml b/security/modules/org.argeo.security.dao.jackrabbit/META-INF/spring/security-jcr-services.xml index 1a3d2eefe..ce2361ef4 100644 --- a/security/modules/org.argeo.security.dao.jackrabbit/META-INF/spring/security-jcr-services.xml +++ b/security/modules/org.argeo.security.dao.jackrabbit/META-INF/spring/security-jcr-services.xml @@ -24,6 +24,7 @@ + - diff --git a/security/modules/org.argeo.security.dao.os/META-INF/spring/security-os.xml b/security/modules/org.argeo.security.dao.os/META-INF/spring/security-os.xml index 970f38182..3e1556263 100644 --- a/security/modules/org.argeo.security.dao.os/META-INF/spring/security-os.xml +++ b/security/modules/org.argeo.security.dao.os/META-INF/spring/security-os.xml @@ -11,7 +11,7 @@ - diff --git a/security/plugins/org.argeo.security.equinox/src/main/java/org/argeo/security/equinox/SpringLoginModule.java b/security/plugins/org.argeo.security.equinox/src/main/java/org/argeo/security/equinox/SpringLoginModule.java index 553b0994d..adeec870e 100644 --- a/security/plugins/org.argeo.security.equinox/src/main/java/org/argeo/security/equinox/SpringLoginModule.java +++ b/security/plugins/org.argeo.security.equinox/src/main/java/org/argeo/security/equinox/SpringLoginModule.java @@ -111,18 +111,15 @@ public class SpringLoginModule extends SecurityContextLoginModule { NameCallback nameCallback = new NameCallback("User"); PasswordCallback passwordCallback = new PasswordCallback( "Password", false); - final String defaultNodeUrl = "http://localhost:7070/org.argeo.jcr.webapp/remoting/node"; - final String defaultSecurityWorkspace = "security"; + final String defaultNodeUrl = System.getProperty(NODE_REPO_URI, + "http://localhost:7070/org.argeo.jcr.webapp/remoting/node"); NameCallback urlCallback = new NameCallback("Site URL", defaultNodeUrl); - NameCallback securityWorkspaceCallback = new NameCallback( - "Security Workspace", defaultSecurityWorkspace); // handle callbacks if (remote) callbackHandler.handle(new Callback[] { nameCallback, - passwordCallback, urlCallback, - securityWorkspaceCallback }); + passwordCallback, urlCallback }); else callbackHandler.handle(new Callback[] { nameCallback, passwordCallback }); @@ -139,9 +136,8 @@ public class SpringLoginModule extends SecurityContextLoginModule { NodeAuthenticationToken credentials; if (remote) { String url = urlCallback.getName(); - String workspace = securityWorkspaceCallback.getName(); credentials = new NodeAuthenticationToken(username, password, - url, workspace); + url); } else { credentials = new NodeAuthenticationToken(username, password); } diff --git a/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/dialogs/DefaultLoginDialog.java b/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/dialogs/DefaultLoginDialog.java index 5cb59d8e0..c07e8a9df 100644 --- a/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/dialogs/DefaultLoginDialog.java +++ b/security/plugins/org.argeo.security.ui/src/main/java/org/argeo/security/ui/dialogs/DefaultLoginDialog.java @@ -111,6 +111,11 @@ public class DefaultLoginDialog extends AbstractLoginDialog { label.setText(callback.getPrompt()); final Text text = new Text(composite, SWT.SINGLE | SWT.LEAD | SWT.BORDER); + if (callback.getDefaultName() != null) { + // set default value, if provided + text.setText(callback.getDefaultName()); + callback.setName(callback.getDefaultName()); + } text.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true)); text.addModifyListener(new ModifyListener() { diff --git a/security/runtime/org.argeo.security.core/pom.xml b/security/runtime/org.argeo.security.core/pom.xml index dca34b352..abb559ad7 100644 --- a/security/runtime/org.argeo.security.core/pom.xml +++ b/security/runtime/org.argeo.security.core/pom.xml @@ -1,4 +1,6 @@ - + + 4.0.0 org.argeo.commons.security @@ -96,6 +98,14 @@ org.argeo.tp org.springframework.transaction + + + + org.argeo.tp + org.eclipse.osgi + provided + + org.argeo.tp diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/NodeAuthenticationToken.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/NodeAuthenticationToken.java index f834d234e..b34677d70 100644 --- a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/NodeAuthenticationToken.java +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/NodeAuthenticationToken.java @@ -23,21 +23,18 @@ public class NodeAuthenticationToken extends UsernamePasswordAuthenticationToken { private static final long serialVersionUID = 1955222132884795213L; private final String url; - private final String securityWorkspace; /** Non authenticated local constructor */ public NodeAuthenticationToken(Object principal, Object credentials) { super(principal, credentials); this.url = null; - this.securityWorkspace = null; } /** Non authenticated remote constructor */ public NodeAuthenticationToken(Object principal, Object credentials, - String url, String workspace) { + String url) { super(principal, credentials); this.url = url; - this.securityWorkspace = workspace; } /** Authenticated constructor */ @@ -45,17 +42,12 @@ public class NodeAuthenticationToken extends GrantedAuthority[] authorities) { super(sat.getPrincipal(), sat.getCredentials(), authorities); this.url = sat.getUrl(); - this.securityWorkspace = sat.getSecurityWorkspace(); } public String getUrl() { return url; } - public String getSecurityWorkspace() { - return securityWorkspace; - } - public Boolean isRemote() { return url != null; } diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/RemoteJcrAuthenticationProvider.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/RemoteJcrAuthenticationProvider.java index f61d37ab2..09e723930 100644 --- a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/RemoteJcrAuthenticationProvider.java +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/RemoteJcrAuthenticationProvider.java @@ -16,11 +16,9 @@ package org.argeo.security.jcr; import java.util.ArrayList; -import java.util.HashMap; import java.util.List; -import java.util.Map; +import java.util.Properties; -import javax.jcr.Credentials; import javax.jcr.Node; import javax.jcr.Repository; import javax.jcr.RepositoryException; @@ -34,6 +32,7 @@ import org.argeo.jcr.ArgeoJcrConstants; import org.argeo.jcr.ArgeoNames; import org.argeo.jcr.UserJcrUtils; import org.argeo.security.NodeAuthenticationToken; +import org.osgi.framework.BundleContext; import org.springframework.security.Authentication; import org.springframework.security.AuthenticationException; import org.springframework.security.BadCredentialsException; @@ -45,44 +44,63 @@ import org.springframework.security.providers.AuthenticationProvider; public class RemoteJcrAuthenticationProvider implements AuthenticationProvider, ArgeoNames { private RepositoryFactory repositoryFactory; + private BundleContext bundleContext; public Authentication authenticate(Authentication authentication) throws AuthenticationException { NodeAuthenticationToken siteAuth = (NodeAuthenticationToken) authentication; String url = siteAuth.getUrl(); - if (url == null) - return null; + if (url == null)// TODO? login on own node + throw new ArgeoException("No url set in " + siteAuth); Session session; - Node userProfile; + Node userProfile; try { SimpleCredentials sp = new SimpleCredentials(siteAuth.getName(), siteAuth.getCredentials().toString().toCharArray()); // get repository - Repository repository = getRepository(url, sp); - if (repository == null) - return null; + Repository repository = new RemoteJcrRepositoryWrapper( + repositoryFactory, url, sp); + if (bundleContext != null) { + Properties serviceProperties = new Properties(); + serviceProperties.setProperty( + ArgeoJcrConstants.JCR_REPOSITORY_ALIAS, + ArgeoJcrConstants.ALIAS_NODE); + serviceProperties.setProperty( + ArgeoJcrConstants.JCR_REPOSITORY_URI, url); + bundleContext.registerService(Repository.class.getName(), + repository, serviceProperties); + } + // Repository repository = ArgeoJcrUtils.getRepositoryByUri( + // repositoryFactory, url); + // if (repository == null) + // throw new ArgeoException("Cannot connect to " + url); - String workspace = siteAuth.getSecurityWorkspace(); - session = repository.login(sp, workspace); - Node userHome = UserJcrUtils.getUserHome(session); - if (userHome == null || !userHome.hasNode(ArgeoNames.ARGEO_PROFILE)) - throw new ArgeoException("No profile for user " - + siteAuth.getName() + " in security workspace " - + siteAuth.getSecurityWorkspace() + " of " - + siteAuth.getUrl()); - userProfile = userHome.getNode(ArgeoNames.ARGEO_PROFILE); + session = repository.login(sp, null); + + userProfile = UserJcrUtils.getUserProfile(session, sp.getUserID()); + JcrUserDetails.checkAccountStatus(userProfile); + + // Node userHome = UserJcrUtils.getUserHome(session); + // if (userHome == null || + // !userHome.hasNode(ArgeoNames.ARGEO_PROFILE)) + // throw new ArgeoException("No profile for user " + // + siteAuth.getName() + " in security workspace " + // + siteAuth.getSecurityWorkspace() + " of " + // + siteAuth.getUrl()); + // userProfile = userHome.getNode(ArgeoNames.ARGEO_PROFILE); } catch (RepositoryException e) { throw new BadCredentialsException( "Cannot authenticate " + siteAuth, e); } try { - JcrUserDetails.checkAccountStatus(userProfile); + Node userHome = UserJcrUtils.getUserHome(session); // retrieve remote roles List authoritiesList = new ArrayList(); - if (userProfile.hasProperty(ArgeoNames.ARGEO_REMOTE_ROLES)) { - Value[] roles = userProfile.getProperty( + if (userHome != null + && userHome.hasProperty(ArgeoNames.ARGEO_REMOTE_ROLES)) { + Value[] roles = userHome.getProperty( ArgeoNames.ARGEO_REMOTE_ROLES).getValues(); for (int i = 0; i < roles.length; i++) authoritiesList.add(new GrantedAuthorityImpl(roles[i] @@ -104,13 +122,6 @@ public class RemoteJcrAuthenticationProvider implements AuthenticationProvider, } } - protected Repository getRepository(String url, Credentials credentials) - throws RepositoryException { - Map parameters = new HashMap(); - parameters.put(ArgeoJcrConstants.JCR_REPOSITORY_URI, url); - return repositoryFactory.getRepository(parameters); - } - @SuppressWarnings("rawtypes") public boolean supports(Class authentication) { return NodeAuthenticationToken.class.isAssignableFrom(authentication); @@ -120,4 +131,8 @@ public class RemoteJcrAuthenticationProvider implements AuthenticationProvider, this.repositoryFactory = repositoryFactory; } + public void setBundleContext(BundleContext bundleContext) { + this.bundleContext = bundleContext; + } + } diff --git a/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/RemoteJcrRepositoryWrapper.java b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/RemoteJcrRepositoryWrapper.java new file mode 100644 index 000000000..b8272356e --- /dev/null +++ b/security/runtime/org.argeo.security.core/src/main/java/org/argeo/security/jcr/RemoteJcrRepositoryWrapper.java @@ -0,0 +1,145 @@ +/* + * Copyright (C) 2007-2012 Mathieu Baudier + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.security.jcr; + +import javax.jcr.Credentials; +import javax.jcr.LoginException; +import javax.jcr.NoSuchWorkspaceException; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.RepositoryFactory; +import javax.jcr.Session; +import javax.jcr.SimpleCredentials; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.ArgeoException; +import org.argeo.jcr.ArgeoJcrUtils; +import org.argeo.jcr.JcrRepositoryWrapper; +import org.argeo.security.NodeAuthenticationToken; +import org.argeo.security.SystemAuthentication; +import org.springframework.security.Authentication; +import org.springframework.security.context.SecurityContextHolder; +import org.springframework.security.providers.UsernamePasswordAuthenticationToken; + +/** + * Wrapper around a remote Jackrabbit repository which allows to simplify + * configuration and intercept some actions. It exposes itself as a + * {@link Repository}. + */ +public class RemoteJcrRepositoryWrapper extends JcrRepositoryWrapper { + private final static Log log = LogFactory + .getLog(RemoteJcrRepositoryWrapper.class); + + private String uri = null; + + private RepositoryFactory repositoryFactory; + + // remote + private Credentials remoteSystemCredentials = null; + + /** + * Empty constructor, {@link #init()} should be called after properties have + * been set + */ + public RemoteJcrRepositoryWrapper() { + } + + /** + * Embedded constructor, calling the {@link #init()} method. + * + * @param alias + * if not null the repository will be published under this alias + */ + public RemoteJcrRepositoryWrapper(RepositoryFactory repositoryFactory, + String uri, Credentials remoteSystemCredentials) { + this.repositoryFactory = repositoryFactory; + this.uri = uri; + this.remoteSystemCredentials = remoteSystemCredentials; + init(); + } + + public void init() { + Repository repository = createJackrabbitRepository(); + setRepository(repository); + } + + /** Actually creates the new repository. */ + protected Repository createJackrabbitRepository() { + long begin = System.currentTimeMillis(); + try { + if (uri == null || uri.trim().equals("")) + throw new ArgeoException("Remote URI not set"); + + Repository repository = ArgeoJcrUtils.getRepositoryByUri( + repositoryFactory, uri); + if (repository == null) + throw new ArgeoException("Remote JCR repository " + uri + + " not found"); + double duration = ((double) (System.currentTimeMillis() - begin)) / 1000; + log.info("Created remote JCR repository in " + duration + + " s from URI " + uri); + // we assume that the data model of the remote repository has + // been properly initialized + return repository; + } catch (Exception e) { + throw new ArgeoException("Cannot create remote JCR repository " + + uri, e); + } + } + + /** Shutdown the repository */ + public void destroy() throws Exception { + super.destroy(); + } + + /** Central login method */ + public Session login(Credentials credentials, String workspaceName) + throws LoginException, NoSuchWorkspaceException, + RepositoryException { + + // retrieve credentials for remote + if (credentials == null) { + Authentication authentication = SecurityContextHolder.getContext() + .getAuthentication(); + if (authentication != null) { + if (authentication instanceof UsernamePasswordAuthenticationToken) { + UsernamePasswordAuthenticationToken upat = (UsernamePasswordAuthenticationToken) authentication; + credentials = new SimpleCredentials(upat.getName(), upat + .getCredentials().toString().toCharArray()); + } else if ((authentication instanceof SystemAuthentication) + || (authentication instanceof NodeAuthenticationToken)) { + credentials = remoteSystemCredentials; + } + } + } + + return super.login(credentials, workspaceName); + } + + public void setUri(String uri) { + this.uri = uri; + } + + public void setRepositoryFactory(RepositoryFactory repositoryFactory) { + this.repositoryFactory = repositoryFactory; + } + + public void setRemoteSystemCredentials(Credentials remoteSystemCredentials) { + this.remoteSystemCredentials = remoteSystemCredentials; + } + +} diff --git a/server/modules/org.argeo.node.repo.jackrabbit/META-INF/spring/noderepo-osgi.xml b/server/modules/org.argeo.node.repo.jackrabbit/META-INF/spring/noderepo-osgi.xml index 1c16f257d..5b0715dd0 100644 --- a/server/modules/org.argeo.node.repo.jackrabbit/META-INF/spring/noderepo-osgi.xml +++ b/server/modules/org.argeo.node.repo.jackrabbit/META-INF/spring/noderepo-osgi.xml @@ -13,6 +13,7 @@ + \ No newline at end of file diff --git a/server/modules/org.argeo.node.repo.jackrabbit/META-INF/spring/noderepo.xml b/server/modules/org.argeo.node.repo.jackrabbit/META-INF/spring/noderepo.xml index 3d7b30c6c..f21293ded 100644 --- a/server/modules/org.argeo.node.repo.jackrabbit/META-INF/spring/noderepo.xml +++ b/server/modules/org.argeo.node.repo.jackrabbit/META-INF/spring/noderepo.xml @@ -13,16 +13,8 @@ - - - - - - - - \ No newline at end of file diff --git a/server/modules/org.argeo.node.repo.jackrabbit/noderepo.properties b/server/modules/org.argeo.node.repo.jackrabbit/noderepo.properties index f50029b5d..7f01f8a9d 100644 --- a/server/modules/org.argeo.node.repo.jackrabbit/noderepo.properties +++ b/server/modules/org.argeo.node.repo.jackrabbit/noderepo.properties @@ -11,12 +11,5 @@ argeo.node.repo.dburl=jdbc:h2:${osgi.instance.area}/node/h2/repository argeo.node.repo.dbuser=sa argeo.node.repo.dbpassword= -## Remote -# Remote repository URI (overrides other configurations if not empty) -argeo.node.repo.uri= -# may change in the near future: -argeo.node.repo.remoteSystemUser=root -argeo.node.repo.remoteSystemPassword=demo - # ADVANCED argeo.node.repo.maxPoolSize=10 \ No newline at end of file diff --git a/server/plugins/org.argeo.jcr.ui.explorer/src/main/java/org/argeo/jcr/ui/explorer/views/GenericJcrBrowser.java b/server/plugins/org.argeo.jcr.ui.explorer/src/main/java/org/argeo/jcr/ui/explorer/views/GenericJcrBrowser.java index 1c1ce6a23..9ae1b0b63 100644 --- a/server/plugins/org.argeo.jcr.ui.explorer/src/main/java/org/argeo/jcr/ui/explorer/views/GenericJcrBrowser.java +++ b/server/plugins/org.argeo.jcr.ui.explorer/src/main/java/org/argeo/jcr/ui/explorer/views/GenericJcrBrowser.java @@ -26,6 +26,8 @@ import javax.jcr.observation.Event; import javax.jcr.observation.EventListener; import javax.jcr.observation.ObservationManager; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.argeo.ArgeoException; import org.argeo.eclipse.ui.TreeParent; import org.argeo.eclipse.ui.jcr.AsyncUiEventListener; @@ -65,8 +67,7 @@ import org.eclipse.swt.widgets.Menu; */ public class GenericJcrBrowser extends AbstractJcrBrowser { - // private final static Log log = - // LogFactory.getLog(GenericJcrBrowser.class); + private final static Log log = LogFactory.getLog(GenericJcrBrowser.class); public final static String ID = JcrExplorerPlugin.ID + ".browserView"; private boolean sortChildNodes = false; @@ -182,6 +183,7 @@ public class GenericJcrBrowser extends AbstractJcrBrowser { resultsObserver = new TreeObserver(tmpNodeViewer.getTree().getDisplay()); if (jcrKeyring != null) try { + log.debug("userID=" + jcrKeyring.getSession().getUserID()); ObservationManager observationManager = jcrKeyring.getSession() .getWorkspace().getObservationManager(); observationManager.addEventListener(resultsObserver, diff --git a/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitContainer.java b/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitContainer.java index ee8b5e5d8..b501f6241 100644 --- a/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitContainer.java +++ b/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitContainer.java @@ -15,89 +15,53 @@ */ package org.argeo.jackrabbit; +import java.io.File; import java.io.IOException; import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.net.URL; -import java.util.ArrayList; -import java.util.HashMap; import java.util.HashSet; -import java.util.List; import java.util.Map; +import java.util.Properties; import java.util.Set; import java.util.TreeSet; +import java.util.UUID; -import javax.jcr.Credentials; -import javax.jcr.LoginException; -import javax.jcr.NoSuchWorkspaceException; import javax.jcr.Node; -import javax.jcr.NodeIterator; import javax.jcr.Repository; import javax.jcr.RepositoryException; import javax.jcr.Session; -import javax.jcr.SimpleCredentials; -import org.apache.commons.io.FilenameUtils; +import org.apache.commons.io.FileUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.jackrabbit.api.JackrabbitRepository; -import org.apache.jackrabbit.commons.NamespaceHelper; -import org.apache.jackrabbit.commons.cnd.CndImporter; import org.apache.jackrabbit.core.RepositoryImpl; +import org.apache.jackrabbit.core.config.RepositoryConfig; +import org.apache.jackrabbit.core.config.RepositoryConfigurationParser; import org.argeo.ArgeoException; -import org.argeo.jcr.ArgeoJcrConstants; import org.argeo.jcr.ArgeoNames; -import org.argeo.jcr.ArgeoTypes; import org.argeo.jcr.JcrUtils; -import org.argeo.security.SystemAuthentication; -import org.osgi.framework.Bundle; -import org.osgi.framework.BundleContext; -import org.osgi.framework.ServiceReference; -import org.osgi.service.packageadmin.ExportedPackage; -import org.osgi.service.packageadmin.PackageAdmin; -import org.springframework.context.ResourceLoaderAware; import org.springframework.core.io.Resource; -import org.springframework.core.io.ResourceLoader; -import org.springframework.security.Authentication; -import org.springframework.security.context.SecurityContextHolder; -import org.springframework.security.providers.UsernamePasswordAuthenticationToken; import org.springframework.util.SystemPropertyUtils; +import org.xml.sax.InputSource; /** * Wrapper around a Jackrabbit repository which allows to configure it in Spring * and expose it as a {@link Repository}. */ -public class JackrabbitContainer extends JackrabbitWrapper implements - ResourceLoaderAware { +public class JackrabbitContainer extends JackrabbitWrapper { private Log log = LogFactory.getLog(JackrabbitContainer.class); - // remote - private Credentials remoteSystemCredentials = null; - // local private Resource configuration; private Resource variables; - private ResourceLoader resourceLoader; - - // data model - /** Node type definitions in CND format */ - private List cndFiles = new ArrayList(); - /** - * Always import CNDs. Useful during development of new data models. In - * production, explicit migration processes should be used. - */ - private Boolean forceCndImport = false; + private RepositoryConfig repositoryConfig; + private File homeDirectory; + private Boolean inMemory = false; /** Migrations to execute (if not already done) */ private Set dataModelMigrations = new HashSet(); - /** Namespaces to register: key is prefix, value namespace */ - private Map namespaces = new HashMap(); - - private BundleContext bundleContext; - /** * Empty constructor, {@link #init()} should be called after properties have * been set @@ -105,204 +69,93 @@ public class JackrabbitContainer extends JackrabbitWrapper implements public JackrabbitContainer() { } - /** - * Convenience constructor for remote, {@link #init()} is called in the - * constructor. - */ - public JackrabbitContainer(String uri, Credentials remoteSystemCredentials) { - setUri(uri); - setRemoteSystemCredentials(remoteSystemCredentials); - init(); - } + public void init() { + long begin = System.currentTimeMillis(); - @Override - protected void postInitWrapped() { - prepareDataModel(); - } + if (getRepository() != null) + throw new ArgeoException( + "Cannot be used to wrap another repository"); + Repository repository = createJackrabbitRepository(); + super.setRepository(repository); - @Override - protected void postInitNew() { // migrate if needed migrate(); // apply new CND files after migration - if (cndFiles != null && cndFiles.size() > 0) - prepareDataModel(); - } - - /* - * DATA MODEL - */ + prepareDataModel(); - /** - * Import declared node type definitions and register namespaces. Tries to - * update the node definitions if they have changed. In case of failures an - * error will be logged but no exception will be thrown. - */ - protected void prepareDataModel() { - // importing node def on remote si currently not supported - if (isRemote()) - return; + double duration = ((double) (System.currentTimeMillis() - begin)) / 1000; + if (log.isDebugEnabled()) + log.debug("Initialized JCR repository wrapper in " + duration + + " s"); + } - Session session = null; + /** Actually creates the new repository. */ + protected Repository createJackrabbitRepository() { + long begin = System.currentTimeMillis(); + InputStream configurationIn = null; + Repository repository; try { - if (remoteSystemCredentials == null) - session = login(); - else - session = login(remoteSystemCredentials); - // register namespaces - if (namespaces.size() > 0) { - NamespaceHelper namespaceHelper = new NamespaceHelper(session); - namespaceHelper.registerNamespaces(namespaces); + // temporary + if (inMemory && getHomeDirectory().exists()) { + FileUtils.deleteDirectory(getHomeDirectory()); + log.warn("Deleted Jackrabbit home directory " + + getHomeDirectory()); } - // load CND files from classpath or as URL - for (String resUrl : cndFiles) { - boolean classpath; - // normalize URL - if (resUrl.startsWith("classpath:")) { - resUrl = resUrl.substring("classpath:".length()); - classpath = true; - } else if (resUrl.indexOf(':') < 0) { - if (!resUrl.startsWith("/")) { - resUrl = "/" + resUrl; - log.warn("Classpath should start with '/'"); - } - // resUrl = "classpath:" + resUrl; - classpath = true; - } else { - classpath = false; - } - - URL url; - Bundle dataModelBundle = null; - if (classpath) { - if (bundleContext != null) { - Bundle currentBundle = bundleContext.getBundle(); - url = currentBundle.getResource(resUrl); - if (url != null) {// found - dataModelBundle = findDataModelBundle(resUrl); - } - } else { - url = getClass().getClassLoader().getResource(resUrl); - // if (url == null) - // url = Thread.currentThread() - // .getContextClassLoader() - // .getResource(resUrl); - } - } else { - url = new URL(resUrl); - } - - // check existing data model nodes - new NamespaceHelper(session).registerNamespace( - ArgeoNames.ARGEO, ArgeoNames.ARGEO_NAMESPACE); - if (!session - .itemExists(ArgeoJcrConstants.DATA_MODELS_BASE_PATH)) - JcrUtils.mkdirs(session, - ArgeoJcrConstants.DATA_MODELS_BASE_PATH); - Node dataModels = session - .getNode(ArgeoJcrConstants.DATA_MODELS_BASE_PATH); - NodeIterator it = dataModels.getNodes(); - Node dataModel = null; - while (it.hasNext()) { - Node node = it.nextNode(); - if (node.getProperty(ArgeoNames.ARGEO_URI).getString() - .equals(resUrl)) { - dataModel = node; - break; - } - } - - // does nothing if data model already registered - if (dataModel != null && !forceCndImport) { - if (dataModelBundle != null) { - String version = dataModel.getProperty( - ArgeoNames.ARGEO_DATA_MODEL_VERSION) - .getString(); - String dataModelBundleVersion = dataModelBundle - .getVersion().toString(); - if (!version.equals(dataModelBundleVersion)) { - log.warn("Data model with version " - + dataModelBundleVersion - + " available, current version is " - + version); - } - } - // do not implicitly update - return; - } - InputStream in = null; - Reader reader = null; - try { - if (url != null) { - in = url.openStream(); - } else if (resourceLoader != null) { - Resource res = resourceLoader.getResource(resUrl); - in = res.getInputStream(); - url = res.getURL(); - } else { - throw new ArgeoException("No " + resUrl - + " in the classpath," - + " make sure the containing" - + " package is visible."); - } - - reader = new InputStreamReader(in); - // actually imports the CND - CndImporter.registerNodeTypes(reader, session, true); - - // FIXME: what if argeo.cnd would not be the first called on - // a new repo? argeo:dataModel would not be found - String fileName = FilenameUtils.getName(url.getPath()); - if (dataModel == null) { - dataModel = dataModels.addNode(fileName); - dataModel.addMixin(ArgeoTypes.ARGEO_DATA_MODEL); - dataModel.setProperty(ArgeoNames.ARGEO_URI, resUrl); - } else { - session.getWorkspace().getVersionManager() - .checkout(dataModel.getPath()); - } - if (dataModelBundle != null) - dataModel.setProperty( - ArgeoNames.ARGEO_DATA_MODEL_VERSION, - dataModelBundle.getVersion().toString()); - else - dataModel.setProperty( - ArgeoNames.ARGEO_DATA_MODEL_VERSION, "0.0.0"); - JcrUtils.updateLastModified(dataModel); - session.save(); - session.getWorkspace().getVersionManager() - .checkin(dataModel.getPath()); - } finally { - IOUtils.closeQuietly(in); - IOUtils.closeQuietly(reader); - } - - if (log.isDebugEnabled()) - log.debug("Data model " - + resUrl - + (dataModelBundle != null ? ", version " - + dataModelBundle.getVersion() - + ", bundle " - + dataModelBundle.getSymbolicName() : "")); - } + // process configuration file + Properties vars = getConfigurationProperties(); + configurationIn = readConfiguration(); + vars.put(RepositoryConfigurationParser.REPOSITORY_HOME_VARIABLE, + getHomeDirectory().getCanonicalPath()); + repositoryConfig = RepositoryConfig.create(new InputSource( + configurationIn), vars); + + // + // Actual repository creation + // + repository = RepositoryImpl.create(repositoryConfig); + + double duration = ((double) (System.currentTimeMillis() - begin)) / 1000; + if (log.isTraceEnabled()) + log.trace("Created Jackrabbit repository in " + duration + + " s, home: " + getHomeDirectory()); + + return repository; } catch (Exception e) { - JcrUtils.discardQuietly(session); - throw new ArgeoException("Cannot import node type definitions " - + cndFiles, e); + throw new ArgeoException("Cannot create Jackrabbit repository " + + getHomeDirectory(), e); } finally { - JcrUtils.logoutQuietly(session); + IOUtils.closeQuietly(configurationIn); } + } + + /** Lazy init. */ + protected File getHomeDirectory() { + try { + if (homeDirectory == null) { + if (inMemory) { + homeDirectory = new File( + System.getProperty("java.io.tmpdir") + + File.separator + + System.getProperty("user.name") + + File.separator + "jackrabbit-" + + UUID.randomUUID()); + homeDirectory.mkdirs(); + // will it work if directory is not empty?? + homeDirectory.deleteOnExit(); + } + } + return homeDirectory.getCanonicalFile(); + } catch (IOException e) { + throw new ArgeoException("Cannot get canonical file for " + + homeDirectory, e); + } } /** Executes migrations, if needed. */ protected void migrate() { - // Remote migration not supported - if (isRemote()) - return; - // No migration to perform if (dataModelMigrations.size() == 0) return; @@ -366,38 +219,32 @@ public class JackrabbitContainer extends JackrabbitWrapper implements } - /* - * REPOSITORY INTERCEPTOR - */ - /** Central login method */ - public Session login(Credentials credentials, String workspaceName) - throws LoginException, NoSuchWorkspaceException, - RepositoryException { - - // retrieve credentials for remote - if (credentials == null && isRemote()) { - Authentication authentication = SecurityContextHolder.getContext() - .getAuthentication(); - if (authentication != null) { - if (authentication instanceof UsernamePasswordAuthenticationToken) { - UsernamePasswordAuthenticationToken upat = (UsernamePasswordAuthenticationToken) authentication; - credentials = new SimpleCredentials(upat.getName(), upat - .getCredentials().toString().toCharArray()); - } else if ((authentication instanceof SystemAuthentication) - && remoteSystemCredentials != null) { - credentials = remoteSystemCredentials; + /** Shutdown the repository */ + public void destroy() throws Exception { + Repository repository = getRepository(); + if (repository != null && repository instanceof RepositoryImpl) { + long begin = System.currentTimeMillis(); + ((RepositoryImpl) repository).shutdown(); + if (inMemory) + if (getHomeDirectory().exists()) { + FileUtils.deleteDirectory(getHomeDirectory()); + if (log.isDebugEnabled()) + log.debug("Deleted Jackrabbit home directory " + + getHomeDirectory()); } - } + double duration = ((double) (System.currentTimeMillis() - begin)) / 1000; + log.info("Destroyed Jackrabbit repository in " + duration + + " s, home: " + getHomeDirectory()); } - - return super.login(credentials, workspaceName); + repository = null; } /* * UTILITIES */ - - @Override + /** + * Reads the configuration which will initialize a {@link RepositoryConfig}. + */ protected InputStream readConfiguration() { try { return configuration != null ? configuration.getInputStream() @@ -408,7 +255,12 @@ public class JackrabbitContainer extends JackrabbitWrapper implements } } - @Override + /** + * Reads the variables which will initialize a {@link Properties}. Returns + * null by default, to be overridden. + * + * @return a new stream or null if no variables available + */ protected InputStream readVariables() { try { return variables != null ? variables.getInputStream() : null; @@ -418,69 +270,63 @@ public class JackrabbitContainer extends JackrabbitWrapper implements } } - @Override + /** + * Resolves ${} placeholders in the provided string. Based on system + * properties if no map is provided. + */ protected String resolvePlaceholders(String string, Map variables) { return SystemPropertyUtils.resolvePlaceholders(string); } - /** Find which OSGi bundle provided the data model resource */ - protected Bundle findDataModelBundle(String resUrl) { - if (resUrl.startsWith("/")) - resUrl = resUrl.substring(1); - String pkg = resUrl.substring(0, resUrl.lastIndexOf('/')).replace('/', - '.'); - ServiceReference paSr = bundleContext - .getServiceReference(PackageAdmin.class.getName()); - PackageAdmin packageAdmin = (PackageAdmin) bundleContext - .getService(paSr); - - // find exported package - ExportedPackage exportedPackage = null; - ExportedPackage[] exportedPackages = packageAdmin - .getExportedPackages(pkg); - if (exportedPackages == null) - throw new ArgeoException("No exported package found for " + pkg); - for (ExportedPackage ep : exportedPackages) { - for (Bundle b : ep.getImportingBundles()) { - if (b.getBundleId() == bundleContext.getBundle().getBundleId()) { - exportedPackage = ep; - break; - } + /** Generates the properties to use in the configuration. */ + protected Properties getConfigurationProperties() { + InputStream propsIn = null; + Properties vars; + try { + vars = new Properties(); + propsIn = readVariables(); + if (propsIn != null) { + vars.load(propsIn); + } + // resolve system properties + for (Object key : vars.keySet()) { + // TODO: implement a smarter mechanism to resolve nested ${} + String newValue = resolvePlaceholders( + vars.getProperty(key.toString()), null); + vars.put(key, newValue); + } + // override with system properties + vars.putAll(System.getProperties()); + + if (log.isTraceEnabled()) { + log.trace("Jackrabbit config variables:"); + for (Object key : new TreeSet(vars.keySet())) + log.trace(key + "=" + vars.getProperty(key.toString())); } - } - Bundle exportingBundle = null; - if (exportedPackage != null) { - exportingBundle = exportedPackage.getExportingBundle(); - } else { - throw new ArgeoException("No OSGi exporting package found for " - + resUrl); + } catch (IOException e) { + throw new ArgeoException("Cannot read configuration properties", e); + } finally { + IOUtils.closeQuietly(propsIn); } - return exportingBundle; + return vars; } /* * FIELDS ACCESS */ - public void setConfiguration(Resource configuration) { - this.configuration = configuration; - } - - public void setNamespaces(Map namespaces) { - this.namespaces = namespaces; - } - public void setCndFiles(List cndFiles) { - this.cndFiles = cndFiles; + public void setHomeDirectory(File homeDirectory) { + this.homeDirectory = homeDirectory; } - public void setVariables(Resource variables) { - this.variables = variables; + public void setInMemory(Boolean inMemory) { + this.inMemory = inMemory; } - public void setRemoteSystemCredentials(Credentials remoteSystemCredentials) { - this.remoteSystemCredentials = remoteSystemCredentials; + public void setRepository(Repository repository) { + throw new ArgeoException("Cannot be used to wrap another repository"); } public void setDataModelMigrations( @@ -488,16 +334,12 @@ public class JackrabbitContainer extends JackrabbitWrapper implements this.dataModelMigrations = dataModelMigrations; } - public void setBundleContext(BundleContext bundleContext) { - this.bundleContext = bundleContext; - } - - public void setForceCndImport(Boolean forceCndUpdate) { - this.forceCndImport = forceCndUpdate; + public void setVariables(Resource variables) { + this.variables = variables; } - public void setResourceLoader(ResourceLoader resourceLoader) { - this.resourceLoader = resourceLoader; + public void setConfiguration(Resource configuration) { + this.configuration = configuration; } } diff --git a/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitWrapper.java b/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitWrapper.java index 9e25f03bc..4635ef2c2 100644 --- a/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitWrapper.java +++ b/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/JackrabbitWrapper.java @@ -15,53 +15,65 @@ */ package org.argeo.jackrabbit; -import java.io.File; -import java.io.IOException; import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.net.URL; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; -import java.util.Properties; -import java.util.TreeSet; -import java.util.UUID; -import javax.jcr.Credentials; -import javax.jcr.LoginException; -import javax.jcr.NoSuchWorkspaceException; +import javax.jcr.Node; +import javax.jcr.NodeIterator; import javax.jcr.Repository; -import javax.jcr.RepositoryException; import javax.jcr.Session; -import javax.jcr.Value; -import org.apache.commons.io.FileUtils; +import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import org.apache.jackrabbit.core.RepositoryImpl; -import org.apache.jackrabbit.core.config.RepositoryConfig; -import org.apache.jackrabbit.core.config.RepositoryConfigurationParser; -import org.apache.jackrabbit.jcr2dav.Jcr2davRepositoryFactory; +import org.apache.jackrabbit.commons.NamespaceHelper; +import org.apache.jackrabbit.commons.cnd.CndImporter; import org.argeo.ArgeoException; -import org.xml.sax.InputSource; +import org.argeo.jcr.ArgeoJcrConstants; +import org.argeo.jcr.ArgeoNames; +import org.argeo.jcr.ArgeoTypes; +import org.argeo.jcr.JcrRepositoryWrapper; +import org.argeo.jcr.JcrUtils; +import org.osgi.framework.Bundle; +import org.osgi.framework.BundleContext; +import org.osgi.framework.ServiceReference; +import org.osgi.service.packageadmin.ExportedPackage; +import org.osgi.service.packageadmin.PackageAdmin; +import org.springframework.context.ResourceLoaderAware; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; /** * Wrapper around a Jackrabbit repository which allows to simplify configuration * and intercept some actions. It exposes itself as a {@link Repository}. */ -public abstract class JackrabbitWrapper implements Repository { +public class JackrabbitWrapper extends JcrRepositoryWrapper implements + ResourceLoaderAware { private Log log = LogFactory.getLog(JackrabbitWrapper.class); - // remote - private String uri = null; - // local - private RepositoryConfig repositoryConfig; - private File homeDirectory; - private Boolean inMemory = false; + private ResourceLoader resourceLoader; + + // data model + /** Node type definitions in CND format */ + private List cndFiles = new ArrayList(); + /** + * Always import CNDs. Useful during development of new data models. In + * production, explicit migration processes should be used. + */ + private Boolean forceCndImport = false; - // wrapped repository - private Repository repository; + /** Namespaces to register: key is prefix, value namespace */ + private Map namespaces = new HashMap(); - private Boolean autocreateWorkspaces = false; + private BundleContext bundleContext; /** * Empty constructor, {@link #init()} should be called after properties have @@ -70,319 +82,240 @@ public abstract class JackrabbitWrapper implements Repository { public JackrabbitWrapper() { } - /** - * Reads the configuration which will initialize a {@link RepositoryConfig}. - */ - protected abstract InputStream readConfiguration(); - - /** - * Reads the variables which will initialize a {@link Properties}. Returns - * null by default, to be overridden. - * - * @return a new stream or null if no variables available - */ - protected InputStream readVariables() { - return null; - } - - /** - * Resolves ${} placeholders in the provided string. Based on system - * properties if no map is provided. - */ - protected abstract String resolvePlaceholders(String string, - Map variables); - - /** Initializes */ + @Override public void init() { - long begin = System.currentTimeMillis(); - - if (repository != null) { - // we are just wrapping another repository - postInitWrapped(); - } else { - createJackrabbitRepository(); - postInitNew(); - } - - double duration = ((double) (System.currentTimeMillis() - begin)) / 1000; - if (log.isTraceEnabled()) - log.trace("Initialized Jackrabbit wrapper in " + duration + " s"); + prepareDataModel(); } - /** - * Called after initialization of an already existing {@link Repository} - * which is being wrapped (e.g. in order to impact its data model). To be - * overridden, does nothing by default. + /* + * DATA MODEL */ - protected void postInitWrapped() { - - } /** - * Called after initialization of a new {@link Repository} either local or - * remote. To be overridden, does nothing by default. + * Import declared node type definitions and register namespaces. Tries to + * update the node definitions if they have changed. In case of failures an + * error will be logged but no exception will be thrown. */ - protected void postInitNew() { + protected void prepareDataModel() { + if ((cndFiles == null || cndFiles.size() == 0) + && (namespaces == null || namespaces.size() == 0)) + return; + + Session session = null; + try { + session = login(); + // register namespaces + if (namespaces.size() > 0) { + NamespaceHelper namespaceHelper = new NamespaceHelper(session); + namespaceHelper.registerNamespaces(namespaces); + } - } + // load CND files from classpath or as URL + for (String resUrl : cndFiles) { + boolean classpath; + // normalize URL + if (resUrl.startsWith("classpath:")) { + resUrl = resUrl.substring("classpath:".length()); + classpath = true; + } else if (resUrl.indexOf(':') < 0) { + if (!resUrl.startsWith("/")) { + resUrl = "/" + resUrl; + log.warn("Classpath should start with '/'"); + } + // resUrl = "classpath:" + resUrl; + classpath = true; + } else { + classpath = false; + } - /** Actually creates the new repository. */ - protected void createJackrabbitRepository() { - long begin = System.currentTimeMillis(); - InputStream configurationIn = null; - try { - if (uri != null && !uri.trim().equals("")) {// remote - Map params = new HashMap(); - params.put( - org.apache.jackrabbit.commons.JcrUtils.REPOSITORY_URI, - uri); - repository = new Jcr2davRepositoryFactory() - .getRepository(params); - if (repository == null) - throw new ArgeoException("Remote Davex repository " + uri - + " not found"); - double duration = ((double) (System.currentTimeMillis() - begin)) / 1000; - log.info("Created Jackrabbit repository in " + duration - + " s from URI " + uri); - // we assume that the data model of the remote repository has - // been properly initialized - } else {// local - // force uri to null in order to optimize isRemote() - uri = null; + URL url; + Bundle dataModelBundle = null; + if (classpath) { + if (bundleContext != null) { + Bundle currentBundle = bundleContext.getBundle(); + url = currentBundle.getResource(resUrl); + if (url != null) {// found + dataModelBundle = findDataModelBundle(resUrl); + } + } else { + url = getClass().getClassLoader().getResource(resUrl); + // if (url == null) + // url = Thread.currentThread() + // .getContextClassLoader() + // .getResource(resUrl); + } + } else { + url = new URL(resUrl); + } - // temporary - if (inMemory && getHomeDirectory().exists()) { - FileUtils.deleteDirectory(getHomeDirectory()); - log.warn("Deleted Jackrabbit home directory " - + getHomeDirectory()); + // check existing data model nodes + new NamespaceHelper(session).registerNamespace( + ArgeoNames.ARGEO, ArgeoNames.ARGEO_NAMESPACE); + if (!session + .itemExists(ArgeoJcrConstants.DATA_MODELS_BASE_PATH)) + JcrUtils.mkdirs(session, + ArgeoJcrConstants.DATA_MODELS_BASE_PATH); + Node dataModels = session + .getNode(ArgeoJcrConstants.DATA_MODELS_BASE_PATH); + NodeIterator it = dataModels.getNodes(); + Node dataModel = null; + while (it.hasNext()) { + Node node = it.nextNode(); + if (node.getProperty(ArgeoNames.ARGEO_URI).getString() + .equals(resUrl)) { + dataModel = node; + break; + } } - // process configuration file - Properties vars = getConfigurationProperties(); - configurationIn = readConfiguration(); - vars.put( - RepositoryConfigurationParser.REPOSITORY_HOME_VARIABLE, - getHomeDirectory().getCanonicalPath()); - repositoryConfig = RepositoryConfig.create(new InputSource( - configurationIn), vars); + // does nothing if data model already registered + if (dataModel != null && !forceCndImport) { + if (dataModelBundle != null) { + String version = dataModel.getProperty( + ArgeoNames.ARGEO_DATA_MODEL_VERSION) + .getString(); + String dataModelBundleVersion = dataModelBundle + .getVersion().toString(); + if (!version.equals(dataModelBundleVersion)) { + log.warn("Data model with version " + + dataModelBundleVersion + + " available, current version is " + + version); + } + } + // do not implicitly update + return; + } - // - // Actual repository creation - // - repository = RepositoryImpl.create(repositoryConfig); + InputStream in = null; + Reader reader = null; + try { + if (url != null) { + in = url.openStream(); + } else if (resourceLoader != null) { + Resource res = resourceLoader.getResource(resUrl); + in = res.getInputStream(); + url = res.getURL(); + } else { + throw new ArgeoException("No " + resUrl + + " in the classpath," + + " make sure the containing" + + " package is visible."); + } + + reader = new InputStreamReader(in); + // actually imports the CND + CndImporter.registerNodeTypes(reader, session, true); + + // FIXME: what if argeo.cnd would not be the first called on + // a new repo? argeo:dataModel would not be found + String fileName = FilenameUtils.getName(url.getPath()); + if (dataModel == null) { + dataModel = dataModels.addNode(fileName); + dataModel.addMixin(ArgeoTypes.ARGEO_DATA_MODEL); + dataModel.setProperty(ArgeoNames.ARGEO_URI, resUrl); + } else { + session.getWorkspace().getVersionManager() + .checkout(dataModel.getPath()); + } + if (dataModelBundle != null) + dataModel.setProperty( + ArgeoNames.ARGEO_DATA_MODEL_VERSION, + dataModelBundle.getVersion().toString()); + else + dataModel.setProperty( + ArgeoNames.ARGEO_DATA_MODEL_VERSION, "0.0.0"); + JcrUtils.updateLastModified(dataModel); + session.save(); + session.getWorkspace().getVersionManager() + .checkin(dataModel.getPath()); + } finally { + IOUtils.closeQuietly(in); + IOUtils.closeQuietly(reader); + } - double duration = ((double) (System.currentTimeMillis() - begin)) / 1000; - if (log.isTraceEnabled()) - log.trace("Created Jackrabbit repository in " + duration - + " s, home: " + getHomeDirectory()); + if (log.isDebugEnabled()) + log.debug("Data model " + + resUrl + + (dataModelBundle != null ? ", version " + + dataModelBundle.getVersion() + + ", bundle " + + dataModelBundle.getSymbolicName() : "")); } } catch (Exception e) { - throw new ArgeoException("Cannot create Jackrabbit repository " - + getHomeDirectory(), e); + JcrUtils.discardQuietly(session); + throw new ArgeoException("Cannot import node type definitions " + + cndFiles, e); } finally { - IOUtils.closeQuietly(configurationIn); - } - } - - /** Lazy init. */ - protected File getHomeDirectory() { - try { - if (homeDirectory == null) { - if (inMemory) { - homeDirectory = new File( - System.getProperty("java.io.tmpdir") - + File.separator - + System.getProperty("user.name") - + File.separator + "jackrabbit-" - + UUID.randomUUID()); - homeDirectory.mkdirs(); - // will it work if directory is not empty?? - homeDirectory.deleteOnExit(); - } - } - - return homeDirectory.getCanonicalFile(); - } catch (IOException e) { - throw new ArgeoException("Cannot get canonical file for " - + homeDirectory, e); + JcrUtils.logoutQuietly(session); } - } - /** Shutdown the repository */ - public void destroy() throws Exception { - if (repository != null && repository instanceof RepositoryImpl) { - long begin = System.currentTimeMillis(); - ((RepositoryImpl) repository).shutdown(); - if (inMemory) - if (getHomeDirectory().exists()) { - FileUtils.deleteDirectory(getHomeDirectory()); - if (log.isDebugEnabled()) - log.debug("Deleted Jackrabbit home directory " - + getHomeDirectory()); - } - double duration = ((double) (System.currentTimeMillis() - begin)) / 1000; - log.info("Destroyed Jackrabbit repository in " + duration - + " s, home: " + getHomeDirectory()); - } } - /** - * @deprecated explicitly declare {@link #destroy()} as destroy-method - * instead. + /* + * REPOSITORY INTERCEPTOR */ - public void dispose() throws Exception { - log.error("## Declare destroy-method=\"destroy\". in the Jackrabbit container bean"); - destroy(); - } /* * UTILITIES */ - - /** Generates the properties to use in the configuration. */ - protected Properties getConfigurationProperties() { - InputStream propsIn = null; - Properties vars; - try { - vars = new Properties(); - propsIn = readVariables(); - if (propsIn != null) { - vars.load(propsIn); - } - // resolve system properties - for (Object key : vars.keySet()) { - // TODO: implement a smarter mechanism to resolve nested ${} - String newValue = resolvePlaceholders( - vars.getProperty(key.toString()), null); - vars.put(key, newValue); - } - // override with system properties - vars.putAll(System.getProperties()); - - if (log.isTraceEnabled()) { - log.trace("Jackrabbit config variables:"); - for (Object key : new TreeSet(vars.keySet())) - log.trace(key + "=" + vars.getProperty(key.toString())); + /** Find which OSGi bundle provided the data model resource */ + protected Bundle findDataModelBundle(String resUrl) { + if (resUrl.startsWith("/")) + resUrl = resUrl.substring(1); + String pkg = resUrl.substring(0, resUrl.lastIndexOf('/')).replace('/', + '.'); + ServiceReference paSr = bundleContext + .getServiceReference(PackageAdmin.class.getName()); + PackageAdmin packageAdmin = (PackageAdmin) bundleContext + .getService(paSr); + + // find exported package + ExportedPackage exportedPackage = null; + ExportedPackage[] exportedPackages = packageAdmin + .getExportedPackages(pkg); + if (exportedPackages == null) + throw new ArgeoException("No exported package found for " + pkg); + for (ExportedPackage ep : exportedPackages) { + for (Bundle b : ep.getImportingBundles()) { + if (b.getBundleId() == bundleContext.getBundle().getBundleId()) { + exportedPackage = ep; + break; + } } - - } catch (IOException e) { - throw new ArgeoException("Cannot read configuration properties", e); - } finally { - IOUtils.closeQuietly(propsIn); - } - return vars; - } - - /* - * DELEGATED JCR REPOSITORY METHODS - */ - - public String getDescriptor(String key) { - return getRepository().getDescriptor(key); - } - - public String[] getDescriptorKeys() { - return getRepository().getDescriptorKeys(); - } - - /** Central login method */ - public Session login(Credentials credentials, String workspaceName) - throws LoginException, NoSuchWorkspaceException, - RepositoryException { - Session session; - try { - session = getRepository().login(credentials, workspaceName); - } catch (NoSuchWorkspaceException e) { - if (autocreateWorkspaces && workspaceName != null) - session = createWorkspaceAndLogsIn(credentials, workspaceName); - else - throw e; } - processNewSession(session); - return session; - } - public Session login() throws LoginException, RepositoryException { - return login(null, null); - } - - public Session login(Credentials credentials) throws LoginException, - RepositoryException { - return login(credentials, null); - } - - public Session login(String workspaceName) throws LoginException, - NoSuchWorkspaceException, RepositoryException { - return login(null, workspaceName); - } - - /** Called after a session has been created, does nothing by default. */ - protected void processNewSession(Session session) { - } - - public Boolean isRemote() { - return uri != null; - } - - /** Wraps access to the repository, making sure it is available. */ - protected Repository getRepository() { - if (repository == null) { - throw new ArgeoException("No repository initialized." - + " Was the init() method called?" - + " The destroy() method should also" - + " be called on shutdown."); + Bundle exportingBundle = null; + if (exportedPackage != null) { + exportingBundle = exportedPackage.getExportingBundle(); + } else { + throw new ArgeoException("No OSGi exporting package found for " + + resUrl); } - return repository; - } - - /** - * Logs in to the default workspace, creates the required workspace, logs - * out, logs in to the required workspace. - */ - protected Session createWorkspaceAndLogsIn(Credentials credentials, - String workspaceName) throws RepositoryException { - if (workspaceName == null) - throw new ArgeoException("No workspace specified."); - Session session = getRepository().login(credentials); - session.getWorkspace().createWorkspace(workspaceName); - session.logout(); - return getRepository().login(credentials, workspaceName); - } - - public boolean isStandardDescriptor(String key) { - return getRepository().isStandardDescriptor(key); - } - - public boolean isSingleValueDescriptor(String key) { - return getRepository().isSingleValueDescriptor(key); - } - - public Value getDescriptorValue(String key) { - return getRepository().getDescriptorValue(key); - } - - public Value[] getDescriptorValues(String key) { - return getRepository().getDescriptorValues(key); + return exportingBundle; } /* * FIELDS ACCESS */ + public void setNamespaces(Map namespaces) { + this.namespaces = namespaces; + } - public void setHomeDirectory(File homeDirectory) { - this.homeDirectory = homeDirectory; + public void setCndFiles(List cndFiles) { + this.cndFiles = cndFiles; } - public void setInMemory(Boolean inMemory) { - this.inMemory = inMemory; + public void setBundleContext(BundleContext bundleContext) { + this.bundleContext = bundleContext; } - public void setUri(String uri) { - this.uri = uri; + public void setForceCndImport(Boolean forceCndUpdate) { + this.forceCndImport = forceCndUpdate; } - public void setRepository(Repository repository) { - this.repository = repository; + public void setResourceLoader(ResourceLoader resourceLoader) { + this.resourceLoader = resourceLoader; } } diff --git a/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/remote/SimpleSessionProvider.java b/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/remote/SimpleSessionProvider.java index acec82180..11edfe947 100644 --- a/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/remote/SimpleSessionProvider.java +++ b/server/runtime/org.argeo.server.jackrabbit/src/main/java/org/argeo/jackrabbit/remote/SimpleSessionProvider.java @@ -58,7 +58,7 @@ public class SimpleSessionProvider implements SessionProvider, Serializable { private Boolean openSessionInView = true; - private String securityWorkspace = "security"; + private String defaultWorkspace = "default"; public Session getSession(HttpServletRequest request, Repository rep, String workspace) throws LoginException, ServletException, @@ -67,7 +67,7 @@ public class SimpleSessionProvider implements SessionProvider, Serializable { if (openSessionInView) { JackrabbitSession session = (JackrabbitSession) rep .login(workspace); - if (session.getWorkspace().getName().equals(securityWorkspace)) + if (session.getWorkspace().getName().equals(defaultWorkspace)) writeRemoteRoles(session); return session; } else { @@ -81,7 +81,7 @@ public class SimpleSessionProvider implements SessionProvider, Serializable { JackrabbitSession session = (JackrabbitSession) rep.login( null, workspace); if (session.getWorkspace().getName() - .equals(securityWorkspace)) + .equals(defaultWorkspace)) writeRemoteRoles(session); if (log.isTraceEnabled()) log.trace("User " + session.getUserID() @@ -105,6 +105,8 @@ public class SimpleSessionProvider implements SessionProvider, Serializable { protected void writeRemoteRoles(JackrabbitSession session) throws RepositoryException { + // FIXME better deal w/ non node repo + // retrieve roles String userId = session.getUserID(); UserManager userManager = session.getUserManager(); @@ -119,12 +121,11 @@ public class SimpleSessionProvider implements SessionProvider, Serializable { userGroupIds.add(it.next().getID()); // write roles if needed - Node userProfile = UserJcrUtils.getUserHome(session).getNode( - ArgeoNames.ARGEO_PROFILE); + Node userHome = UserJcrUtils.getUserHome(session); boolean writeRoles = false; - if (userProfile.hasProperty(ArgeoNames.ARGEO_REMOTE_ROLES)) { - Value[] roles = userProfile.getProperty( - ArgeoNames.ARGEO_REMOTE_ROLES).getValues(); + if (userHome.hasProperty(ArgeoNames.ARGEO_REMOTE_ROLES)) { + Value[] roles = userHome.getProperty(ArgeoNames.ARGEO_REMOTE_ROLES) + .getValues(); if (roles.length != userGroupIds.size()) writeRoles = true; else @@ -136,14 +137,14 @@ public class SimpleSessionProvider implements SessionProvider, Serializable { if (writeRoles) { session.getWorkspace().getVersionManager() - .checkout(userProfile.getPath()); + .checkout(userHome.getPath()); String[] roleIds = userGroupIds.toArray(new String[userGroupIds .size()]); - userProfile.setProperty(ArgeoNames.ARGEO_REMOTE_ROLES, roleIds); - JcrUtils.updateLastModified(userProfile); + userHome.setProperty(ArgeoNames.ARGEO_REMOTE_ROLES, roleIds); + JcrUtils.updateLastModified(userHome); session.save(); session.getWorkspace().getVersionManager() - .checkin(userProfile.getPath()); + .checkin(userHome.getPath()); } } @@ -185,7 +186,7 @@ public class SimpleSessionProvider implements SessionProvider, Serializable { } public void setSecurityWorkspace(String securityWorkspace) { - this.securityWorkspace = securityWorkspace; + this.defaultWorkspace = securityWorkspace; } } diff --git a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ArgeoJcrUtils.java b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ArgeoJcrUtils.java index ed4ba421f..90bf0f0f4 100644 --- a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ArgeoJcrUtils.java +++ b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/ArgeoJcrUtils.java @@ -36,9 +36,21 @@ public class ArgeoJcrUtils implements ArgeoJcrConstants { */ public static Repository getRepositoryByUri( RepositoryFactory repositoryFactory, String uri) { + return getRepositoryByUri(repositoryFactory, uri, null); + } + + /** + * Wraps the call to the repository factory based on parameter + * {@link ArgeoJcrConstants#JCR_REPOSITORY_URI} in order to simplify it and + * protect against future API changes. + */ + public static Repository getRepositoryByUri( + RepositoryFactory repositoryFactory, String uri, String alias) { try { Map parameters = new HashMap(); parameters.put(JCR_REPOSITORY_URI, uri); + if (alias != null) + parameters.put(JCR_REPOSITORY_ALIAS, alias); return repositoryFactory.getRepository(parameters); } catch (RepositoryException e) { throw new ArgeoException( diff --git a/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrRepositoryWrapper.java b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrRepositoryWrapper.java new file mode 100644 index 000000000..fce52cea8 --- /dev/null +++ b/server/runtime/org.argeo.server.jcr/src/main/java/org/argeo/jcr/JcrRepositoryWrapper.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2007-2012 Mathieu Baudier + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.argeo.jcr; + +import javax.jcr.Credentials; +import javax.jcr.LoginException; +import javax.jcr.NoSuchWorkspaceException; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.Value; + +import org.argeo.ArgeoException; + +/** + * Wrapper around a JCR repository which allows to simplify configuration and + * intercept some actions. It exposes itself as a {@link Repository}. + */ +public abstract class JcrRepositoryWrapper implements Repository { + // private final static Log log = LogFactory + // .getLog(JcrRepositoryWrapper.class); + + // wrapped repository + private Repository repository; + + private Boolean autocreateWorkspaces = false; + + /** + * Empty constructor, {@link #init()} should be called after properties have + * been set + */ + public JcrRepositoryWrapper() { + } + + /** Initializes */ + public void init() { + } + + /** Shutdown the repository */ + public void destroy() throws Exception { + } + + /* + * DELEGATED JCR REPOSITORY METHODS + */ + + public String getDescriptor(String key) { + return getRepository().getDescriptor(key); + } + + public String[] getDescriptorKeys() { + return getRepository().getDescriptorKeys(); + } + + /** Central login method */ + public Session login(Credentials credentials, String workspaceName) + throws LoginException, NoSuchWorkspaceException, + RepositoryException { + Session session; + try { + session = getRepository().login(credentials, workspaceName); + } catch (NoSuchWorkspaceException e) { + if (autocreateWorkspaces && workspaceName != null) + session = createWorkspaceAndLogsIn(credentials, workspaceName); + else + throw e; + } + processNewSession(session); + return session; + } + + public Session login() throws LoginException, RepositoryException { + return login(null, null); + } + + public Session login(Credentials credentials) throws LoginException, + RepositoryException { + return login(credentials, null); + } + + public Session login(String workspaceName) throws LoginException, + NoSuchWorkspaceException, RepositoryException { + return login(null, workspaceName); + } + + /** Called after a session has been created, does nothing by default. */ + protected void processNewSession(Session session) { + } + + /** Wraps access to the repository, making sure it is available. */ + protected synchronized Repository getRepository() { +// if (repository == null) { +// throw new ArgeoException("No repository initialized." +// + " Was the init() method called?" +// + " The destroy() method should also" +// + " be called on shutdown."); +// } + return repository; + } + + /** + * Logs in to the default workspace, creates the required workspace, logs + * out, logs in to the required workspace. + */ + protected Session createWorkspaceAndLogsIn(Credentials credentials, + String workspaceName) throws RepositoryException { + if (workspaceName == null) + throw new ArgeoException("No workspace specified."); + Session session = getRepository().login(credentials); + session.getWorkspace().createWorkspace(workspaceName); + session.logout(); + return getRepository().login(credentials, workspaceName); + } + + public boolean isStandardDescriptor(String key) { + return getRepository().isStandardDescriptor(key); + } + + public boolean isSingleValueDescriptor(String key) { + return getRepository().isSingleValueDescriptor(key); + } + + public Value getDescriptorValue(String key) { + return getRepository().getDescriptorValue(key); + } + + public Value[] getDescriptorValues(String key) { + return getRepository().getDescriptorValues(key); + } + + public synchronized void setRepository(Repository repository) { + this.repository = repository; + } + + public void setAutocreateWorkspaces(Boolean autocreateWorkspaces) { + this.autocreateWorkspaces = autocreateWorkspaces; + } + +} diff --git a/server/runtime/org.argeo.server.jcr/src/main/resources/org/argeo/jcr/argeo.cnd b/server/runtime/org.argeo.server.jcr/src/main/resources/org/argeo/jcr/argeo.cnd index e46aec737..1ae7a1e77 100644 --- a/server/runtime/org.argeo.server.jcr/src/main/resources/org/argeo/jcr/argeo.cnd +++ b/server/runtime/org.argeo.server.jcr/src/main/resources/org/argeo/jcr/argeo.cnd @@ -20,7 +20,7 @@ mixin [argeo:userHome] > mix:created, mix:lastModified mixin - argeo:userID (STRING) m -+ argeo:profile (argeo:userProfile) +- argeo:remoteRoles (STRING) * + argeo:keyring (argeo:pbeSpec) + argeo:preferences (argeo:preferenceNode) @@ -31,7 +31,6 @@ mixin - argeo:accountNonExpired (BOOLEAN) - argeo:accountNonLocked (BOOLEAN) - argeo:credentialsNonExpired (BOOLEAN) -- argeo:remoteRoles (STRING) * [argeo:preferenceNode] > mix:lastModified, mix:versionable mixin -- 2.30.2