From: Mathieu Baudier Date: Tue, 25 Nov 2014 12:48:25 +0000 (+0000) Subject: New project conventions X-Git-Tag: argeo-commons-2.1.30~525 X-Git-Url: http://git.argeo.org/?a=commitdiff_plain;h=cf3a914f6fbf31b43be5cb86e54d05e8543be6a9;p=lgpl%2Fargeo-commons.git New project conventions git-svn-id: https://svn.argeo.org/commons/trunk@7523 4cfe0d0a-d680-48aa-b62c-e0a02a3f76cc --- diff --git a/org.argeo.server.jcr/.classpath b/org.argeo.server.jcr/.classpath index cbc801ced..71ce1c503 100644 --- a/org.argeo.server.jcr/.classpath +++ b/org.argeo.server.jcr/.classpath @@ -1,10 +1,8 @@ - - - - - + + + + + diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/ArgeoJcrConstants.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/ArgeoJcrConstants.java deleted file mode 100644 index b9b513a30..000000000 --- a/org.argeo.server.jcr/newsrc/org/argeo/jcr/ArgeoJcrConstants.java +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * 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; - -/** Argeo model specific constants */ -public interface ArgeoJcrConstants { - public final static String ARGEO_BASE_PATH = "/argeo:system"; - public final static String DATA_MODELS_BASE_PATH = ARGEO_BASE_PATH - + "/argeo:dataModels"; - public final static String PEOPLE_BASE_PATH = ARGEO_BASE_PATH - + "/argeo:people"; - - // parameters (typically for call to a RepositoryFactory) - public final static String JCR_REPOSITORY_ALIAS = "argeo.jcr.repository.alias"; - public final static String JCR_REPOSITORY_URI = "argeo.jcr.repository.uri"; - - // standard aliases - public final static String ALIAS_NODE = "node"; - -} diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/ArgeoJcrUtils.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/ArgeoJcrUtils.java deleted file mode 100644 index dccb06cf4..000000000 --- a/org.argeo.server.jcr/newsrc/org/argeo/jcr/ArgeoJcrUtils.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * 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 java.util.HashMap; -import java.util.Map; - -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.RepositoryFactory; - -import org.argeo.ArgeoException; - -/** Utilities related to Argeo model in JCR */ -public class ArgeoJcrUtils implements ArgeoJcrConstants { - /** - * Wraps the call to the repository factory based on parameter - * {@link ArgeoJcrConstants#JCR_REPOSITORY_ALIAS} in order to simplify it - * and protect against future API changes. - */ - public static Repository getRepositoryByAlias( - RepositoryFactory repositoryFactory, String alias) { - try { - Map parameters = new HashMap(); - parameters.put(JCR_REPOSITORY_ALIAS, alias); - return repositoryFactory.getRepository(parameters); - } catch (RepositoryException e) { - throw new ArgeoException( - "Unexpected exception when trying to retrieve repository with alias " - + alias, e); - } - } - - /** - * 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) { - 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( - "Unexpected exception when trying to retrieve repository with uri " - + uri, e); - } - } - - private ArgeoJcrUtils() { - } - -} diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/ArgeoNames.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/ArgeoNames.java deleted file mode 100644 index 6e3eca999..000000000 --- a/org.argeo.server.jcr/newsrc/org/argeo/jcr/ArgeoNames.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * 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; - -/** JCR names in the http://www.argeo.org/argeo namespace */ -public interface ArgeoNames { - public final static String ARGEO_NAMESPACE = "http://www.argeo.org/ns/argeo"; - public final static String ARGEO = "argeo"; - - public final static String ARGEO_URI = "argeo:uri"; - public final static String ARGEO_USER_ID = "argeo:userID"; - public final static String ARGEO_PREFERENCES = "argeo:preferences"; - public final static String ARGEO_DATA_MODEL_VERSION = "argeo:dataModelVersion"; - - public final static String ARGEO_REMOTE = "argeo:remote"; - public final static String ARGEO_PASSWORD = "argeo:password"; - public final static String ARGEO_REMOTE_ROLES = "argeo:remoteRoles"; - - // user profile - public final static String ARGEO_PROFILE = "argeo:profile"; - - // spring security - public final static String ARGEO_ENABLED = "argeo:enabled"; - public final static String ARGEO_ACCOUNT_NON_EXPIRED = "argeo:accountNonExpired"; - public final static String ARGEO_ACCOUNT_NON_LOCKED = "argeo:accountNonLocked"; - public final static String ARGEO_CREDENTIALS_NON_EXPIRED = "argeo:credentialsNonExpired"; - - // personal details - public final static String ARGEO_FIRST_NAME = "argeo:firstName"; - public final static String ARGEO_LAST_NAME = "argeo:lastName"; - public final static String ARGEO_PRIMARY_EMAIL = "argeo:primaryEmail"; - public final static String ARGEO_PRIMARY_ORGANIZATION = "argeo:primaryOrganization"; - - // tabular - public final static String ARGEO_IS_KEY = "argeo:isKey"; - - // crypto - public final static String ARGEO_IV = "argeo:iv"; - public final static String ARGEO_SECRET_KEY_FACTORY = "argeo:secretKeyFactory"; - public final static String ARGEO_SALT = "argeo:salt"; - public final static String ARGEO_ITERATION_COUNT = "argeo:iterationCount"; - public final static String ARGEO_KEY_LENGTH = "argeo:keyLength"; - public final static String ARGEO_SECRET_KEY_ENCRYPTION = "argeo:secretKeyEncryption"; - public final static String ARGEO_CIPHER = "argeo:cipher"; - public final static String ARGEO_KEYRING = "argeo:keyring"; -} diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/ArgeoTypes.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/ArgeoTypes.java deleted file mode 100644 index a11ead53f..000000000 --- a/org.argeo.server.jcr/newsrc/org/argeo/jcr/ArgeoTypes.java +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * 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; - -/** JCR types in the http://www.argeo.org/argeo namespace */ -public interface ArgeoTypes { - public final static String ARGEO_LINK = "argeo:link"; - public final static String ARGEO_USER_HOME = "argeo:userHome"; - public final static String ARGEO_USER_PROFILE = "argeo:userProfile"; - public final static String ARGEO_REMOTE_REPOSITORY = "argeo:remoteRepository"; - public final static String ARGEO_PREFERENCE_NODE = "argeo:preferenceNode"; - - // data model - public final static String ARGEO_DATA_MODEL = "argeo:dataModel"; - - // tabular - public final static String ARGEO_TABLE = "argeo:table"; - public final static String ARGEO_COLUMN = "argeo:column"; - public final static String ARGEO_CSV = "argeo:csv"; - - // crypto - public final static String ARGEO_ENCRYPTED = "argeo:encrypted"; - public final static String ARGEO_PBE_SPEC = "argeo:pbeSpec"; - -} diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/CollectionNodeIterator.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/CollectionNodeIterator.java deleted file mode 100644 index a65907a7a..000000000 --- a/org.argeo.server.jcr/newsrc/org/argeo/jcr/CollectionNodeIterator.java +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * 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 java.util.Collection; -import java.util.Iterator; -import java.util.NoSuchElementException; - -import javax.jcr.Node; -import javax.jcr.NodeIterator; - -/** Wraps a collection of nodes in order to read it as a {@link NodeIterator} */ -public class CollectionNodeIterator implements NodeIterator { - private final Long collectionSize; - private final Iterator iterator; - private Integer position = 0; - - public CollectionNodeIterator(Collection nodes) { - super(); - this.collectionSize = (long) nodes.size(); - this.iterator = nodes.iterator(); - } - - public void skip(long skipNum) { - if (skipNum < 0) - throw new IllegalArgumentException( - "Skip count has to be positive: " + skipNum); - - for (long i = 0; i < skipNum; i++) { - if (!hasNext()) - throw new NoSuchElementException("Last element past (position=" - + getPosition() + ")"); - nextNode(); - } - } - - public long getSize() { - return collectionSize; - } - - public long getPosition() { - return position; - } - - public boolean hasNext() { - return iterator.hasNext(); - } - - public Object next() { - return nextNode(); - } - - public void remove() { - iterator.remove(); - } - - public Node nextNode() { - Node node = iterator.next(); - position++; - return node; - } - -} diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/DefaultJcrListener.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/DefaultJcrListener.java deleted file mode 100644 index 253b3055d..000000000 --- a/org.argeo.server.jcr/newsrc/org/argeo/jcr/DefaultJcrListener.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * 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.RepositoryException; -import javax.jcr.Session; -import javax.jcr.observation.Event; -import javax.jcr.observation.EventIterator; -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; - -/** To be overridden */ -public class DefaultJcrListener implements EventListener { - private final static Log log = LogFactory.getLog(DefaultJcrListener.class); - private Session session; - private String path = "/"; - private Boolean deep = true; - - public void start() { - try { - addEventListener(session().getWorkspace().getObservationManager()); - if (log.isDebugEnabled()) - log.debug("Registered JCR event listener on " + path); - } catch (Exception e) { - throw new ArgeoException("Cannot register event listener", e); - } - } - - public void stop() { - try { - session().getWorkspace().getObservationManager() - .removeEventListener(this); - if (log.isDebugEnabled()) - log.debug("Unregistered JCR event listener on " + path); - } catch (Exception e) { - throw new ArgeoException("Cannot unregister event listener", e); - } - } - - /** Default is listen to all events */ - protected Integer getEvents() { - return Event.NODE_ADDED | Event.NODE_REMOVED | Event.PROPERTY_ADDED - | Event.PROPERTY_CHANGED | Event.PROPERTY_REMOVED; - } - - /** To be overidden */ - public void onEvent(EventIterator events) { - while (events.hasNext()) { - Event event = events.nextEvent(); - log.debug(event); - } - } - - /** To be overidden */ - protected void addEventListener(ObservationManager observationManager) - throws RepositoryException { - observationManager.addEventListener(this, getEvents(), path, deep, - null, null, false); - } - - private Session session() { - return session; - } - - public void setPath(String path) { - this.path = path; - } - - public void setDeep(Boolean deep) { - this.deep = deep; - } - - public void setSession(Session session) { - this.session = session; - } - -} diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/DefaultRepositoryFactory.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/DefaultRepositoryFactory.java deleted file mode 100644 index a7b30e7be..000000000 --- a/org.argeo.server.jcr/newsrc/org/argeo/jcr/DefaultRepositoryFactory.java +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * 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 java.net.URI; -import java.net.URISyntaxException; -import java.util.Map; -import java.util.Properties; - -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.RepositoryFactory; - -import org.argeo.ArgeoException; - -/** - * Simple implementation of {@link RepositoryFactory}, supporting OSGi aliases. - */ -public class DefaultRepositoryFactory extends DefaultRepositoryRegister - implements RepositoryFactory, ArgeoJcrConstants { - @SuppressWarnings("rawtypes") - public Repository getRepository(Map parameters) throws RepositoryException { - if (parameters.containsKey(JCR_REPOSITORY_ALIAS)) { - String alias = parameters.get(JCR_REPOSITORY_ALIAS).toString(); - return getRepositoryByAlias(alias); - } else if (parameters.containsKey(JCR_REPOSITORY_URI)) { - String uri = parameters.get(JCR_REPOSITORY_URI).toString(); - return getRepositoryByAlias(getAliasFromURI(uri)); - } - return null; - } - - protected String getAliasFromURI(String uri) { - try { - URI uriObj = new URI(uri); - String alias = uriObj.getPath(); - if (alias.charAt(0) == '/') - alias = alias.substring(1); - if (alias.charAt(alias.length() - 1) == '/') - alias = alias.substring(0, alias.length() - 1); - return alias; - } catch (URISyntaxException e) { - throw new ArgeoException("Cannot interpret URI " + uri, e); - } - } - - /** - * Retrieve a repository by alias - * - * @return the repository registered with alias or null if none - */ - protected Repository getRepositoryByAlias(String alias) { - if (getRepositories().containsKey(alias)) - return getRepositories().get(alias); - else - return null; - } - - protected void publish(String alias, Repository repository, - Properties properties) { - register(repository, properties); - } - -} diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/DefaultRepositoryRegister.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/DefaultRepositoryRegister.java deleted file mode 100644 index f13c84e3b..000000000 --- a/org.argeo.server.jcr/newsrc/org/argeo/jcr/DefaultRepositoryRegister.java +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * 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 java.util.Collections; -import java.util.Map; -import java.util.Observable; -import java.util.TreeMap; - -import javax.jcr.Repository; -import javax.jcr.RepositoryException; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -public class DefaultRepositoryRegister extends Observable implements - RepositoryRegister, ArgeoJcrConstants { - private final static Log log = LogFactory - .getLog(DefaultRepositoryRegister.class); - - /** Read only map which will be directly exposed. */ - private Map repositories = Collections - .unmodifiableMap(new TreeMap()); - - @SuppressWarnings("rawtypes") - public synchronized Repository getRepository(Map parameters) - throws RepositoryException { - if (!parameters.containsKey(JCR_REPOSITORY_ALIAS)) - throw new RepositoryException("Parameter " + JCR_REPOSITORY_ALIAS - + " has to be defined."); - String alias = parameters.get(JCR_REPOSITORY_ALIAS).toString(); - if (!repositories.containsKey(alias)) - throw new RepositoryException( - "No repository registered with alias " + alias); - - return repositories.get(alias); - } - - /** Access to the read-only map */ - public synchronized Map getRepositories() { - return repositories; - } - - /** Registers a service, typically called when OSGi services are bound. */ - @SuppressWarnings("rawtypes") - public synchronized void register(Repository repository, Map properties) { - // TODO: also check bean name? - String alias; - if (properties == null || !properties.containsKey(JCR_REPOSITORY_ALIAS)) { - log.warn("Cannot register a repository if no " - + JCR_REPOSITORY_ALIAS + " property is speecified."); - return; - } - alias = properties.get(JCR_REPOSITORY_ALIAS).toString(); - Map map = new TreeMap( - repositories); - map.put(alias, repository); - repositories = Collections.unmodifiableMap(map); - setChanged(); - notifyObservers(alias); - } - - /** Unregisters a service, typically called when OSGi services are unbound. */ - @SuppressWarnings("rawtypes") - public synchronized void unregister(Repository repository, Map properties) { - // TODO: also check bean name? - if (properties == null || !properties.containsKey(JCR_REPOSITORY_ALIAS)) { - log.warn("Cannot unregister a repository without property " - + JCR_REPOSITORY_ALIAS); - return; - } - - String alias = properties.get(JCR_REPOSITORY_ALIAS).toString(); - Map map = new TreeMap( - repositories); - map.put(alias, repository); - repositories = Collections.unmodifiableMap(map); - setChanged(); - notifyObservers(alias); - } -} diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/JcrCallback.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/JcrCallback.java deleted file mode 100644 index 0c4706f84..000000000 --- a/org.argeo.server.jcr/newsrc/org/argeo/jcr/JcrCallback.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * 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.Session; - -/** An arbitrary execution on a JCR session, optionally returning a result. */ -public interface JcrCallback { - public Object execute(Session session); -} diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/JcrRepositoryWrapper.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/JcrRepositoryWrapper.java deleted file mode 100644 index f993c2f58..000000000 --- a/org.argeo.server.jcr/newsrc/org/argeo/jcr/JcrRepositoryWrapper.java +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * 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/org.argeo.server.jcr/newsrc/org/argeo/jcr/JcrResourceAdapter.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/JcrResourceAdapter.java deleted file mode 100644 index 0b1a98cf0..000000000 --- a/org.argeo.server.jcr/newsrc/org/argeo/jcr/JcrResourceAdapter.java +++ /dev/null @@ -1,245 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * 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 java.io.InputStream; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.List; - -import javax.jcr.Binary; -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.nodetype.NodeType; -import javax.jcr.version.Version; -import javax.jcr.version.VersionHistory; -import javax.jcr.version.VersionIterator; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.argeo.ArgeoException; - -/** - * Bridge Spring resources and JCR folder / files semantics (nt:folder / - * nt:file), supporting versioning as well. - */ -public class JcrResourceAdapter { - private final static Log log = LogFactory.getLog(JcrResourceAdapter.class); - - private Session session; - - private Boolean versioning = true; - private String defaultEncoding = "UTF-8"; - - // private String restoreBase = "/.restore"; - - public JcrResourceAdapter() { - } - - public JcrResourceAdapter(Session session) { - this.session = session; - } - - public void mkdirs(String path) { - JcrUtils.mkdirs(session(), path, NodeType.NT_FOLDER, - NodeType.NT_FOLDER, versioning); - } - - public void create(String path, InputStream in, String mimeType) { - try { - if (session().itemExists(path)) { - throw new ArgeoException("Node " + path + " already exists."); - } - - int index = path.lastIndexOf('/'); - String parentPath = path.substring(0, index); - if (parentPath.equals("")) - parentPath = "/"; - String fileName = path.substring(index + 1); - if (!session().itemExists(parentPath)) - throw new ArgeoException("Parent folder of node " + path - + " does not exist: " + parentPath); - - Node folderNode = (Node) session().getItem(parentPath); - Node fileNode = folderNode.addNode(fileName, "nt:file"); - - Node contentNode = fileNode.addNode(Property.JCR_CONTENT, - "nt:resource"); - if (mimeType != null) - contentNode.setProperty(Property.JCR_MIMETYPE, mimeType); - contentNode.setProperty(Property.JCR_ENCODING, defaultEncoding); - Binary binary = session().getValueFactory().createBinary(in); - contentNode.setProperty(Property.JCR_DATA, binary); - JcrUtils.closeQuietly(binary); - Calendar lastModified = Calendar.getInstance(); - // lastModified.setTimeInMillis(file.lastModified()); - contentNode.setProperty(Property.JCR_LAST_MODIFIED, lastModified); - // resNode.addMixin("mix:referenceable"); - - if (versioning) - fileNode.addMixin("mix:versionable"); - - session().save(); - - if (versioning) - session().getWorkspace().getVersionManager() - .checkin(fileNode.getPath()); - - if (log.isDebugEnabled()) - log.debug("Created " + path); - } catch (Exception e) { - throw new ArgeoException("Cannot create node for " + path, e); - } - - } - - public void update(String path, InputStream in) { - try { - - if (!session().itemExists(path)) { - String type = null; - // FIXME: using javax.activation leads to conflict between Java - // 1.5 and 1.6 (since javax.activation was included in Java 1.6) - // String type = new MimetypesFileTypeMap() - // .getContentType(FilenameUtils.getName(path)); - create(path, in, type); - return; - } - - Node fileNode = (Node) session().getItem(path); - Node contentNode = fileNode.getNode(Property.JCR_CONTENT); - if (versioning) - session().getWorkspace().getVersionManager() - .checkout(fileNode.getPath()); - Binary binary = session().getValueFactory().createBinary(in); - contentNode.setProperty(Property.JCR_DATA, binary); - JcrUtils.closeQuietly(binary); - Calendar lastModified = Calendar.getInstance(); - // lastModified.setTimeInMillis(file.lastModified()); - contentNode.setProperty(Property.JCR_LAST_MODIFIED, lastModified); - - session().save(); - if (versioning) - session().getWorkspace().getVersionManager() - .checkin(fileNode.getPath()); - - if (log.isDebugEnabled()) - log.debug("Updated " + path); - } catch (Exception e) { - throw new ArgeoException("Cannot update node " + path, e); - } - } - - public List listVersions(String path) { - if (!versioning) - throw new ArgeoException("Versioning is not activated"); - - try { - List versions = new ArrayList(); - Node fileNode = (Node) session().getItem(path); - VersionHistory history = session().getWorkspace() - .getVersionManager().getVersionHistory(fileNode.getPath()); - for (VersionIterator it = history.getAllVersions(); it.hasNext();) { - Version version = (Version) it.next(); - versions.add(version.getCreated()); - if (log.isTraceEnabled()) { - log.debug(version); - // debug(version); - } - } - return versions; - } catch (Exception e) { - throw new ArgeoException("Cannot list version of node " + path, e); - } - } - - public InputStream retrieve(String path) { - try { - Node node = (Node) session().getItem( - path + "/" + Property.JCR_CONTENT); - Property property = node.getProperty(Property.JCR_DATA); - return property.getBinary().getStream(); - } catch (Exception e) { - throw new ArgeoException("Cannot retrieve " + path, e); - } - } - - public synchronized InputStream retrieve(String path, Integer revision) { - if (!versioning) - throw new ArgeoException("Versioning is not activated"); - - try { - Node fileNode = (Node) session().getItem(path); - VersionHistory history = session().getWorkspace() - .getVersionManager().getVersionHistory(fileNode.getPath()); - int count = 0; - Version version = null; - for (VersionIterator it = history.getAllVersions(); it.hasNext();) { - version = (Version) it.next(); - if (count == revision + 1) { - InputStream in = fromVersion(version); - if (log.isDebugEnabled()) - log.debug("Retrieved " + path + " at revision " - + revision); - return in; - } - count++; - } - } catch (Exception e) { - throw new ArgeoException("Cannot retrieve version " + revision - + " of " + path, e); - } - - throw new ArgeoException("Version " + revision - + " does not exist for node " + path); - } - - protected InputStream fromVersion(Version version) - throws RepositoryException { - Node frozenNode = version.getNode("jcr:frozenNode"); - InputStream in = frozenNode.getNode(Property.JCR_CONTENT) - .getProperty(Property.JCR_DATA).getBinary().getStream(); - return in; - } - - protected Session session() { - return session; - } - - public void setVersioning(Boolean versioning) { - this.versioning = versioning; - } - - public void setDefaultEncoding(String defaultEncoding) { - this.defaultEncoding = defaultEncoding; - } - - protected String fill(Integer number) { - int size = 4; - String str = number.toString(); - for (int i = str.length(); i < size; i++) { - str = "0" + str; - } - return str; - } - - public void setSession(Session session) { - this.session = session; - } - -} diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/JcrUrlStreamHandler.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/JcrUrlStreamHandler.java deleted file mode 100644 index a77763937..000000000 --- a/org.argeo.server.jcr/newsrc/org/argeo/jcr/JcrUrlStreamHandler.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * 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 java.io.IOException; -import java.io.InputStream; -import java.net.URL; -import java.net.URLConnection; -import java.net.URLStreamHandler; - -import javax.jcr.Item; -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.PropertyType; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.nodetype.NodeType; - -/** URL stream handler able to deal with nt:file node and properties. NOT FINISHED */ -public class JcrUrlStreamHandler extends URLStreamHandler { - private final Session session; - - public JcrUrlStreamHandler(Session session) { - this.session = session; - } - - @Override - protected URLConnection openConnection(final URL u) throws IOException { - // TODO Auto-generated method stub - return new URLConnection(u) { - - @Override - public void connect() throws IOException { - String itemPath = u.getPath(); - try { - if (!session.itemExists(itemPath)) - throw new IOException("No item under " + itemPath); - - Item item = session.getItem(u.getPath()); - if (item.isNode()) { - // this should be a nt:file node - Node node = (Node) item; - if (!node.getPrimaryNodeType().isNodeType( - NodeType.NT_FILE)) - throw new IOException("Node " + node + " is not a " - + NodeType.NT_FILE); - - } else { - Property property = (Property) item; - if(property.getType()==PropertyType.BINARY){ - //Binary binary = property.getBinary(); - - } - } - } catch (RepositoryException e) { - IOException ioe = new IOException( - "Unexpected JCR exception"); - ioe.initCause(e); - throw ioe; - } - } - - @Override - public InputStream getInputStream() throws IOException { - // TODO Auto-generated method stub - return super.getInputStream(); - } - - }; - } - -} diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/JcrUtils.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/JcrUtils.java deleted file mode 100644 index 2176e757c..000000000 --- a/org.argeo.server.jcr/newsrc/org/argeo/jcr/JcrUtils.java +++ /dev/null @@ -1,1590 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * 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 java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.MalformedURLException; -import java.net.URL; -import java.security.Principal; -import java.text.DateFormat; -import java.text.ParseException; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Collections; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; - -import javax.jcr.Binary; -import javax.jcr.NamespaceRegistry; -import javax.jcr.NoSuchWorkspaceException; -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.Property; -import javax.jcr.PropertyIterator; -import javax.jcr.PropertyType; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.Value; -import javax.jcr.Workspace; -import javax.jcr.nodetype.NodeType; -import javax.jcr.observation.EventListener; -import javax.jcr.query.Query; -import javax.jcr.query.QueryResult; -import javax.jcr.security.AccessControlEntry; -import javax.jcr.security.AccessControlList; -import javax.jcr.security.AccessControlManager; -import javax.jcr.security.AccessControlPolicy; -import javax.jcr.security.AccessControlPolicyIterator; -import javax.jcr.security.Privilege; - -import org.apache.commons.io.IOUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.argeo.ArgeoException; -import org.argeo.ArgeoMonitor; -import org.argeo.util.security.DigestUtils; -import org.argeo.util.security.SimplePrincipal; - -/** Utility methods to simplify common JCR operations. */ -public class JcrUtils implements ArgeoJcrConstants { - - final private static Log log = LogFactory.getLog(JcrUtils.class); - - /** - * Not complete yet. See - * http://www.day.com/specs/jcr/2.0/3_Repository_Model.html#3.2.2%20Local - * %20Names - */ - public final static char[] INVALID_NAME_CHARACTERS = { '/', ':', '[', ']', - '|', '*', /* - * invalid XML chars : - */ - '<', '>', '&' }; - - /** Prevents instantiation */ - private JcrUtils() { - } - - /** - * Queries one single node. - * - * @return one single node or null if none was found - * @throws ArgeoException - * if more than one node was found - */ - public static Node querySingleNode(Query query) { - NodeIterator nodeIterator; - try { - QueryResult queryResult = query.execute(); - nodeIterator = queryResult.getNodes(); - } catch (RepositoryException e) { - throw new ArgeoException("Cannot execute query " + query, e); - } - Node node; - if (nodeIterator.hasNext()) - node = nodeIterator.nextNode(); - else - return null; - - if (nodeIterator.hasNext()) - throw new ArgeoException("Query returned more than one node."); - return node; - } - - /** Retrieves the node name from the provided path */ - public static String nodeNameFromPath(String path) { - if (path.equals("/")) - return ""; - if (path.charAt(0) != '/') - throw new ArgeoException("Path " + path + " must start with a '/'"); - String pathT = path; - if (pathT.charAt(pathT.length() - 1) == '/') - pathT = pathT.substring(0, pathT.length() - 2); - - int index = pathT.lastIndexOf('/'); - return pathT.substring(index + 1); - } - - /** Retrieves the parent path of the provided path */ - public static String parentPath(String path) { - if (path.equals("/")) - throw new ArgeoException("Root path '/' has no parent path"); - if (path.charAt(0) != '/') - throw new ArgeoException("Path " + path + " must start with a '/'"); - String pathT = path; - if (pathT.charAt(pathT.length() - 1) == '/') - pathT = pathT.substring(0, pathT.length() - 2); - - int index = pathT.lastIndexOf('/'); - return pathT.substring(0, index); - } - - /** The provided data as a path ('/' at the end, not the beginning) */ - public static String dateAsPath(Calendar cal) { - return dateAsPath(cal, false); - } - - /** - * Creates a deep path based on a URL: - * http://subdomain.example.com/to/content?args => - * com/example/subdomain/to/content - */ - public static String urlAsPath(String url) { - try { - URL u = new URL(url); - StringBuffer path = new StringBuffer(url.length()); - // invert host - path.append(hostAsPath(u.getHost())); - // we don't put port since it may not always be there and may change - path.append(u.getPath()); - return path.toString(); - } catch (MalformedURLException e) { - throw new ArgeoException("Cannot generate URL path for " + url, e); - } - } - - /** Set the {@link NodeType#NT_ADDRESS} properties based on this URL. */ - public static void urlToAddressProperties(Node node, String url) { - try { - URL u = new URL(url); - node.setProperty(Property.JCR_PROTOCOL, u.getProtocol()); - node.setProperty(Property.JCR_HOST, u.getHost()); - node.setProperty(Property.JCR_PORT, Integer.toString(u.getPort())); - node.setProperty(Property.JCR_PATH, normalizePath(u.getPath())); - } catch (Exception e) { - throw new ArgeoException("Cannot set URL " + url - + " as nt:address properties", e); - } - } - - /** Build URL based on the {@link NodeType#NT_ADDRESS} properties. */ - public static String urlFromAddressProperties(Node node) { - try { - URL u = new URL( - node.getProperty(Property.JCR_PROTOCOL).getString(), node - .getProperty(Property.JCR_HOST).getString(), - (int) node.getProperty(Property.JCR_PORT).getLong(), node - .getProperty(Property.JCR_PATH).getString()); - return u.toString(); - } catch (Exception e) { - throw new ArgeoException( - "Cannot get URL from nt:address properties of " + node, e); - } - } - - /* - * PATH UTILITIES - */ - - /** Make sure that: starts with '/', do not end with '/', do not have '//' */ - public static String normalizePath(String path) { - List tokens = tokenize(path); - StringBuffer buf = new StringBuffer(path.length()); - for (String token : tokens) { - buf.append('/'); - buf.append(token); - } - return buf.toString(); - } - - /** - * Creates a path from a FQDN, inverting the order of the component: - * www.argeo.org => org.argeo.www - */ - public static String hostAsPath(String host) { - StringBuffer path = new StringBuffer(host.length()); - String[] hostTokens = host.split("\\."); - for (int i = hostTokens.length - 1; i >= 0; i--) { - path.append(hostTokens[i]); - if (i != 0) - path.append('/'); - } - return path.toString(); - } - - /** - * Creates a path from a UUID (e.g. 6ebda899-217d-4bf1-abe4-2839085c8f3c => - * 6ebda899-217d/4bf1/abe4/2839085c8f3c/). '/' at the end, not the beginning - */ - public static String uuidAsPath(String uuid) { - StringBuffer path = new StringBuffer(uuid.length()); - String[] tokens = uuid.split("-"); - for (int i = 0; i < tokens.length; i++) { - path.append(tokens[i]); - if (i != 0) - path.append('/'); - } - return path.toString(); - } - - /** - * The provided data as a path ('/' at the end, not the beginning) - * - * @param cal - * the date - * @param addHour - * whether to add hour as well - */ - public static String dateAsPath(Calendar cal, Boolean addHour) { - StringBuffer buf = new StringBuffer(14); - buf.append('Y'); - buf.append(cal.get(Calendar.YEAR)); - buf.append('/'); - - int month = cal.get(Calendar.MONTH) + 1; - buf.append('M'); - if (month < 10) - buf.append(0); - buf.append(month); - buf.append('/'); - - int day = cal.get(Calendar.DAY_OF_MONTH); - buf.append('D'); - if (day < 10) - buf.append(0); - buf.append(day); - buf.append('/'); - - if (addHour) { - int hour = cal.get(Calendar.HOUR_OF_DAY); - buf.append('H'); - if (hour < 10) - buf.append(0); - buf.append(hour); - buf.append('/'); - } - return buf.toString(); - - } - - /** Converts in one call a string into a gregorian calendar. */ - public static Calendar parseCalendar(DateFormat dateFormat, String value) { - try { - Date date = dateFormat.parse(value); - Calendar calendar = new GregorianCalendar(); - calendar.setTime(date); - return calendar; - } catch (ParseException e) { - throw new ArgeoException("Cannot parse " + value - + " with date format " + dateFormat, e); - } - - } - - /** The last element of a path. */ - public static String lastPathElement(String path) { - if (path.charAt(path.length() - 1) == '/') - throw new ArgeoException("Path " + path + " cannot end with '/'"); - int index = path.lastIndexOf('/'); - if (index < 0) - return path; - return path.substring(index + 1); - } - - /** - * Call {@link Node#getName()} without exceptions (useful in super - * constructors). - */ - public static String getNameQuietly(Node node) { - try { - return node.getName(); - } catch (RepositoryException e) { - throw new ArgeoException("Cannot get name from " + node, e); - } - } - - /** - * Call {@link Node#getProperty(String)} without exceptions (useful in super - * constructors). - */ - public static String getStringPropertyQuietly(Node node, String propertyName) { - try { - return node.getProperty(propertyName).getString(); - } catch (RepositoryException e) { - throw new ArgeoException("Cannot get name from " + node, e); - } - } - - /** - * Routine that get the child with this name, adding id it does not already - * exist - */ - public static Node getOrAdd(Node parent, String childName, - String childPrimaryNodeType) throws RepositoryException { - return parent.hasNode(childName) ? parent.getNode(childName) : parent - .addNode(childName, childPrimaryNodeType); - } - - /** - * Routine that get the child with this name, adding id it does not already - * exist - */ - public static Node getOrAdd(Node parent, String childName) - throws RepositoryException { - return parent.hasNode(childName) ? parent.getNode(childName) : parent - .addNode(childName); - } - - /** Convert a {@link NodeIterator} to a list of {@link Node} */ - public static List nodeIteratorToList(NodeIterator nodeIterator) { - List nodes = new ArrayList(); - while (nodeIterator.hasNext()) { - nodes.add(nodeIterator.nextNode()); - } - return nodes; - } - - /* - * PROPERTIES - */ - - /** - * Concisely get the string value of a property or null if this node doesn't - * have this property - */ - public static String get(Node node, String propertyName) { - try { - if (!node.hasProperty(propertyName)) - return null; - return node.getProperty(propertyName).getString(); - } catch (RepositoryException e) { - throw new ArgeoException("Cannot get property " + propertyName - + " of " + node, e); - } - } - - /** Concisely get the boolean value of a property */ - public static Boolean check(Node node, String propertyName) { - try { - return node.getProperty(propertyName).getBoolean(); - } catch (RepositoryException e) { - throw new ArgeoException("Cannot get property " + propertyName - + " of " + node, e); - } - } - - /** Concisely get the bytes array value of a property */ - public static byte[] getBytes(Node node, String propertyName) { - try { - return getBinaryAsBytes(node.getProperty(propertyName)); - } catch (RepositoryException e) { - throw new ArgeoException("Cannot get property " + propertyName - + " of " + node, e); - } - } - - /** Creates the nodes making path, if they don't exist. */ - public static Node mkdirs(Session session, String path) { - return mkdirs(session, path, null, null, false); - } - - /** - * use {@link #mkdirs(Session, String, String, String, Boolean)} instead. - * - * @deprecated - */ - @Deprecated - public static Node mkdirs(Session session, String path, String type, - Boolean versioning) { - return mkdirs(session, path, type, type, false); - } - - /** - * @param type - * the type of the leaf node - */ - public static Node mkdirs(Session session, String path, String type) { - return mkdirs(session, path, type, null, false); - } - - /** - * Create sub nodes relative to a parent node - * - * @param nodeType - * the type of the leaf node - */ - public static Node mkdirs(Node parentNode, String relativePath, - String nodeType) { - return mkdirs(parentNode, relativePath, nodeType, null); - } - - /** - * Create sub nodes relative to a parent node - * - * @param nodeType - * the type of the leaf node - */ - public static Node mkdirs(Node parentNode, String relativePath, - String nodeType, String intermediaryNodeType) { - List tokens = tokenize(relativePath); - Node currParent = parentNode; - try { - for (int i = 0; i < tokens.size(); i++) { - String name = tokens.get(i); - if (currParent.hasNode(name)) { - currParent = currParent.getNode(name); - } else { - if (i != (tokens.size() - 1)) {// intermediary - currParent = currParent.addNode(name, - intermediaryNodeType); - } else {// leaf - currParent = currParent.addNode(name, nodeType); - } - } - } - return currParent; - } catch (RepositoryException e) { - throw new ArgeoException("Cannot mkdirs relative path " - + relativePath + " from " + parentNode, e); - } - } - - /** - * Synchronized and save is performed, to avoid race conditions in - * initializers leading to duplicate nodes. - */ - public synchronized static Node mkdirsSafe(Session session, String path, - String type) { - try { - if (session.hasPendingChanges()) - throw new ArgeoException( - "Session has pending changes, save them first."); - Node node = mkdirs(session, path, type); - session.save(); - return node; - } catch (RepositoryException e) { - discardQuietly(session); - throw new ArgeoException("Cannot safely make directories", e); - } - } - - public synchronized static Node mkdirsSafe(Session session, String path) { - return mkdirsSafe(session, path, null); - } - - /** - * Creates the nodes making path, if they don't exist. This is up to the - * caller to save the session. Use with caution since it can create - * duplicate nodes if used concurrently. - */ - public static Node mkdirs(Session session, String path, String type, - String intermediaryNodeType, Boolean versioning) { - try { - if (path.equals('/')) - return session.getRootNode(); - - if (session.itemExists(path)) { - Node node = session.getNode(path); - // check type - if (type != null && !node.isNodeType(type) - && !node.getPath().equals("/")) - throw new ArgeoException("Node " + node - + " exists but is of type " - + node.getPrimaryNodeType().getName() - + " not of type " + type); - // TODO: check versioning - return node; - } - - StringBuffer current = new StringBuffer("/"); - Node currentNode = session.getRootNode(); - Iterator it = tokenize(path).iterator(); - while (it.hasNext()) { - String part = it.next(); - current.append(part).append('/'); - if (!session.itemExists(current.toString())) { - if (!it.hasNext() && type != null) - currentNode = currentNode.addNode(part, type); - else if (it.hasNext() && intermediaryNodeType != null) - currentNode = currentNode.addNode(part, - intermediaryNodeType); - else - currentNode = currentNode.addNode(part); - if (versioning) - currentNode.addMixin(NodeType.MIX_VERSIONABLE); - if (log.isTraceEnabled()) - log.debug("Added folder " + part + " as " + current); - } else { - currentNode = (Node) session.getItem(current.toString()); - } - } - return currentNode; - } catch (RepositoryException e) { - discardQuietly(session); - throw new ArgeoException("Cannot mkdirs " + path, e); - } finally { - } - } - - /** Convert a path to the list of its tokens */ - public static List tokenize(String path) { - List tokens = new ArrayList(); - boolean optimized = false; - if (!optimized) { - String[] rawTokens = path.split("/"); - for (String token : rawTokens) { - if (!token.equals("")) - tokens.add(token); - } - } else { - StringBuffer curr = new StringBuffer(); - char[] arr = path.toCharArray(); - chars: for (int i = 0; i < arr.length; i++) { - char c = arr[i]; - if (c == '/') { - if (i == 0 || (i == arr.length - 1)) - continue chars; - if (curr.length() > 0) { - tokens.add(curr.toString()); - curr = new StringBuffer(); - } - } else - curr.append(c); - } - if (curr.length() > 0) { - tokens.add(curr.toString()); - curr = new StringBuffer(); - } - } - return Collections.unmodifiableList(tokens); - } - - /** - * Safe and repository implementation independent registration of a - * namespace. - */ - public static void registerNamespaceSafely(Session session, String prefix, - String uri) { - try { - registerNamespaceSafely(session.getWorkspace() - .getNamespaceRegistry(), prefix, uri); - } catch (RepositoryException e) { - throw new ArgeoException("Cannot find namespace registry", e); - } - } - - /** - * Safe and repository implementation independent registration of a - * namespace. - */ - public static void registerNamespaceSafely(NamespaceRegistry nr, - String prefix, String uri) { - try { - String[] prefixes = nr.getPrefixes(); - for (String pref : prefixes) - if (pref.equals(prefix)) { - String registeredUri = nr.getURI(pref); - if (!registeredUri.equals(uri)) - throw new ArgeoException("Prefix " + pref - + " already registered for URI " - + registeredUri - + " which is different from provided URI " - + uri); - else - return;// skip - } - nr.registerNamespace(prefix, uri); - } catch (RepositoryException e) { - throw new ArgeoException("Cannot register namespace " + uri - + " under prefix " + prefix, e); - } - } - - /** Recursively outputs the contents of the given node. */ - public static void debug(Node node) { - debug(node, log); - } - - /** Recursively outputs the contents of the given node. */ - public static void debug(Node node, Log log) { - try { - // First output the node path - log.debug(node.getPath()); - // Skip the virtual (and large!) jcr:system subtree - if (node.getName().equals("jcr:system")) { - return; - } - - // Then the children nodes (recursive) - NodeIterator it = node.getNodes(); - while (it.hasNext()) { - Node childNode = it.nextNode(); - debug(childNode, log); - } - - // Then output the properties - PropertyIterator properties = node.getProperties(); - // log.debug("Property are : "); - - properties: while (properties.hasNext()) { - Property property = properties.nextProperty(); - if (property.getType() == PropertyType.BINARY) - continue properties;// skip - if (property.getDefinition().isMultiple()) { - // A multi-valued property, print all values - Value[] values = property.getValues(); - for (int i = 0; i < values.length; i++) { - log.debug(property.getPath() + "=" - + values[i].getString()); - } - } else { - // A single-valued property - log.debug(property.getPath() + "=" + property.getString()); - } - } - } catch (Exception e) { - log.error("Could not debug " + node, e); - } - - } - - /** Logs the effective access control policies */ - public static void logEffectiveAccessPolicies(Node node) { - try { - logEffectiveAccessPolicies(node.getSession(), node.getPath()); - } catch (RepositoryException e) { - log.error("Cannot log effective access policies of " + node, e); - } - } - - /** Logs the effective access control policies */ - public static void logEffectiveAccessPolicies(Session session, String path) { - if (!log.isDebugEnabled()) - return; - - try { - AccessControlPolicy[] effectivePolicies = session - .getAccessControlManager().getEffectivePolicies(path); - if (effectivePolicies.length > 0) { - for (AccessControlPolicy policy : effectivePolicies) { - if (policy instanceof AccessControlList) { - AccessControlList acl = (AccessControlList) policy; - log.debug("Access control list for " + path + "\n" - + accessControlListSummary(acl)); - } - } - } else { - log.debug("No effective access control policy for " + path); - } - } catch (RepositoryException e) { - log.error("Cannot log effective access policies of " + path, e); - } - } - - /** Returns a human-readable summary of this access control list. */ - public static String accessControlListSummary(AccessControlList acl) { - StringBuffer buf = new StringBuffer(""); - try { - for (AccessControlEntry ace : acl.getAccessControlEntries()) { - buf.append('\t').append(ace.getPrincipal().getName()) - .append('\n'); - for (Privilege priv : ace.getPrivileges()) - buf.append("\t\t").append(priv.getName()).append('\n'); - } - return buf.toString(); - } catch (RepositoryException e) { - throw new ArgeoException("Cannot write summary of " + acl, e); - } - } - - /** - * Copies recursively the content of a node to another one. Do NOT copy the - * property values of {@link NodeType#MIX_CREATED} and - * {@link NodeType#MIX_LAST_MODIFIED}, but update the - * {@link Property#JCR_LAST_MODIFIED} and - * {@link Property#JCR_LAST_MODIFIED_BY} properties if the target node has - * the {@link NodeType#MIX_LAST_MODIFIED} mixin. - */ - public static void copy(Node fromNode, Node toNode) { - try { - if (toNode.getDefinition().isProtected()) - return; - - // process properties - PropertyIterator pit = fromNode.getProperties(); - properties: while (pit.hasNext()) { - Property fromProperty = pit.nextProperty(); - String propertyName = fromProperty.getName(); - if (toNode.hasProperty(propertyName) - && toNode.getProperty(propertyName).getDefinition() - .isProtected()) - continue properties; - - if (fromProperty.getDefinition().isProtected()) - continue properties; - - if (propertyName.equals("jcr:created") - || propertyName.equals("jcr:createdBy") - || propertyName.equals("jcr:lastModified") - || propertyName.equals("jcr:lastModifiedBy")) - continue properties; - - if (fromProperty.isMultiple()) { - toNode.setProperty(propertyName, fromProperty.getValues()); - } else { - toNode.setProperty(propertyName, fromProperty.getValue()); - } - } - - // update jcr:lastModified and jcr:lastModifiedBy in toNode in case - // they existed, before adding the mixins - updateLastModified(toNode); - - // add mixins - for (NodeType mixinType : fromNode.getMixinNodeTypes()) { - toNode.addMixin(mixinType.getName()); - } - - // process children nodes - NodeIterator nit = fromNode.getNodes(); - while (nit.hasNext()) { - Node fromChild = nit.nextNode(); - Integer index = fromChild.getIndex(); - String nodeRelPath = fromChild.getName() + "[" + index + "]"; - Node toChild; - if (toNode.hasNode(nodeRelPath)) - toChild = toNode.getNode(nodeRelPath); - else - toChild = toNode.addNode(fromChild.getName(), fromChild - .getPrimaryNodeType().getName()); - copy(fromChild, toChild); - } - } catch (RepositoryException e) { - throw new ArgeoException("Cannot copy " + fromNode + " to " - + toNode, e); - } - } - - /** - * Check whether all first-level properties (except jcr:* properties) are - * equal. Skip jcr:* properties - */ - public static Boolean allPropertiesEquals(Node reference, Node observed, - Boolean onlyCommonProperties) { - try { - PropertyIterator pit = reference.getProperties(); - props: while (pit.hasNext()) { - Property propReference = pit.nextProperty(); - String propName = propReference.getName(); - if (propName.startsWith("jcr:")) - continue props; - - if (!observed.hasProperty(propName)) - if (onlyCommonProperties) - continue props; - else - return false; - // TODO: deal with multiple property values? - if (!observed.getProperty(propName).getValue() - .equals(propReference.getValue())) - return false; - } - return true; - } catch (RepositoryException e) { - throw new ArgeoException("Cannot check all properties equals of " - + reference + " and " + observed, e); - } - } - - public static Map diffProperties(Node reference, - Node observed) { - Map diffs = new TreeMap(); - diffPropertiesLevel(diffs, null, reference, observed); - return diffs; - } - - /** - * Compare the properties of two nodes. Recursivity to child nodes is not - * yet supported. Skip jcr:* properties. - */ - static void diffPropertiesLevel(Map diffs, - String baseRelPath, Node reference, Node observed) { - try { - // check removed and modified - PropertyIterator pit = reference.getProperties(); - props: while (pit.hasNext()) { - Property p = pit.nextProperty(); - String name = p.getName(); - if (name.startsWith("jcr:")) - continue props; - - if (!observed.hasProperty(name)) { - String relPath = propertyRelPath(baseRelPath, name); - PropertyDiff pDiff = new PropertyDiff(PropertyDiff.REMOVED, - relPath, p.getValue(), null); - diffs.put(relPath, pDiff); - } else { - if (p.isMultiple()) { - // FIXME implement multiple - } else { - Value referenceValue = p.getValue(); - Value newValue = observed.getProperty(name).getValue(); - if (!referenceValue.equals(newValue)) { - String relPath = propertyRelPath(baseRelPath, name); - PropertyDiff pDiff = new PropertyDiff( - PropertyDiff.MODIFIED, relPath, - referenceValue, newValue); - diffs.put(relPath, pDiff); - } - } - } - } - // check added - pit = observed.getProperties(); - props: while (pit.hasNext()) { - Property p = pit.nextProperty(); - String name = p.getName(); - if (name.startsWith("jcr:")) - continue props; - if (!reference.hasProperty(name)) { - if (p.isMultiple()) { - // FIXME implement multiple - } else { - String relPath = propertyRelPath(baseRelPath, name); - PropertyDiff pDiff = new PropertyDiff( - PropertyDiff.ADDED, relPath, null, p.getValue()); - diffs.put(relPath, pDiff); - } - } - } - } catch (RepositoryException e) { - throw new ArgeoException("Cannot diff " + reference + " and " - + observed, e); - } - } - - /** - * Compare only a restricted list of properties of two nodes. No - * recursivity. - * - */ - public static Map diffProperties(Node reference, - Node observed, List properties) { - Map diffs = new TreeMap(); - try { - Iterator pit = properties.iterator(); - - props: while (pit.hasNext()) { - String name = pit.next(); - if (!reference.hasProperty(name)) { - if (!observed.hasProperty(name)) - continue props; - Value val = observed.getProperty(name).getValue(); - try { - // empty String but not null - if ("".equals(val.getString())) - continue props; - } catch (Exception e) { - // not parseable as String, silent - } - PropertyDiff pDiff = new PropertyDiff(PropertyDiff.ADDED, - name, null, val); - diffs.put(name, pDiff); - } else if (!observed.hasProperty(name)) { - PropertyDiff pDiff = new PropertyDiff(PropertyDiff.REMOVED, - name, reference.getProperty(name).getValue(), null); - diffs.put(name, pDiff); - } else { - Value referenceValue = reference.getProperty(name) - .getValue(); - Value newValue = observed.getProperty(name).getValue(); - if (!referenceValue.equals(newValue)) { - PropertyDiff pDiff = new PropertyDiff( - PropertyDiff.MODIFIED, name, referenceValue, - newValue); - diffs.put(name, pDiff); - } - } - } - } catch (RepositoryException e) { - throw new ArgeoException("Cannot diff " + reference + " and " - + observed, e); - } - return diffs; - } - - /** Builds a property relPath to be used in the diff. */ - private static String propertyRelPath(String baseRelPath, - String propertyName) { - if (baseRelPath == null) - return propertyName; - else - return baseRelPath + '/' + propertyName; - } - - /** - * Normalizes a name so that it can be stored in contexts not supporting - * names with ':' (typically databases). Replaces ':' by '_'. - */ - public static String normalize(String name) { - return name.replace(':', '_'); - } - - /** - * Replaces characters which are invalid in a JCR name by '_'. Currently not - * exhaustive. - * - * @see JcrUtils#INVALID_NAME_CHARACTERS - */ - public static String replaceInvalidChars(String name) { - return replaceInvalidChars(name, '_'); - } - - /** - * Replaces characters which are invalid in a JCR name. Currently not - * exhaustive. - * - * @see JcrUtils#INVALID_NAME_CHARACTERS - */ - public static String replaceInvalidChars(String name, char replacement) { - boolean modified = false; - char[] arr = name.toCharArray(); - for (int i = 0; i < arr.length; i++) { - char c = arr[i]; - invalid: for (char invalid : INVALID_NAME_CHARACTERS) { - if (c == invalid) { - arr[i] = replacement; - modified = true; - break invalid; - } - } - } - if (modified) - return new String(arr); - else - // do not create new object if unnecessary - return name; - } - - /** - * Removes forbidden characters from a path, replacing them with '_' - * - * @deprecated use {@link #replaceInvalidChars(String)} instead - */ - public static String removeForbiddenCharacters(String str) { - return str.replace('[', '_').replace(']', '_').replace('/', '_') - .replace('*', '_'); - - } - - /** Cleanly disposes a {@link Binary} even if it is null. */ - public static void closeQuietly(Binary binary) { - if (binary == null) - return; - binary.dispose(); - } - - /** Retrieve a {@link Binary} as a byte array */ - public static byte[] getBinaryAsBytes(Property property) { - ByteArrayOutputStream out = new ByteArrayOutputStream(); - InputStream in = null; - Binary binary = null; - try { - binary = property.getBinary(); - in = binary.getStream(); - IOUtils.copy(in, out); - return out.toByteArray(); - } catch (Exception e) { - throw new ArgeoException("Cannot read binary " + property - + " as bytes", e); - } finally { - IOUtils.closeQuietly(out); - IOUtils.closeQuietly(in); - closeQuietly(binary); - } - } - - /** Writes a {@link Binary} from a byte array */ - public static void setBinaryAsBytes(Node node, String property, byte[] bytes) { - InputStream in = null; - Binary binary = null; - try { - in = new ByteArrayInputStream(bytes); - binary = node.getSession().getValueFactory().createBinary(in); - node.setProperty(property, binary); - } catch (Exception e) { - throw new ArgeoException("Cannot read binary " + property - + " as bytes", e); - } finally { - IOUtils.closeQuietly(in); - closeQuietly(binary); - } - } - - /** - * Creates depth from a string (typically a username) by adding levels based - * on its first characters: "aBcD",2 => a/aB - */ - public static String firstCharsToPath(String str, Integer nbrOfChars) { - if (str.length() < nbrOfChars) - throw new ArgeoException("String " + str - + " length must be greater or equal than " + nbrOfChars); - StringBuffer path = new StringBuffer(""); - StringBuffer curr = new StringBuffer(""); - for (int i = 0; i < nbrOfChars; i++) { - curr.append(str.charAt(i)); - path.append(curr); - if (i < nbrOfChars - 1) - path.append('/'); - } - return path.toString(); - } - - /** - * Discards the current changes in the session attached to this node. To be - * used typically in a catch block. - * - * @see #discardQuietly(Session) - */ - public static void discardUnderlyingSessionQuietly(Node node) { - try { - discardQuietly(node.getSession()); - } catch (RepositoryException e) { - log.warn("Cannot quietly discard session of node " + node + ": " - + e.getMessage()); - } - } - - /** - * Discards the current changes in a session by calling - * {@link Session#refresh(boolean)} with false, only logging - * potential errors when doing so. To be used typically in a catch block. - */ - public static void discardQuietly(Session session) { - try { - if (session != null) - session.refresh(false); - } catch (RepositoryException e) { - log.warn("Cannot quietly discard session " + session + ": " - + e.getMessage()); - } - } - - /** - * Login to a workspace with implicit credentials, creates the workspace - * with these credentials if it does not already exist. - */ - public static Session loginOrCreateWorkspace(Repository repository, - String workspaceName) throws RepositoryException { - Session workspaceSession = null; - Session defaultSession = null; - try { - try { - workspaceSession = repository.login(workspaceName); - } catch (NoSuchWorkspaceException e) { - // try to create workspace - defaultSession = repository.login(); - defaultSession.getWorkspace().createWorkspace(workspaceName); - workspaceSession = repository.login(workspaceName); - } - return workspaceSession; - } finally { - logoutQuietly(defaultSession); - } - } - - /** Logs out the session, not throwing any exception, even if it is null. */ - public static void logoutQuietly(Session session) { - try { - if (session != null) - if (session.isLive()) - session.logout(); - } catch (Exception e) { - // silent - } - } - - /** - * Convenient method to add a listener. uuids passed as null, deep=true, - * local=true, only one node type - */ - public static void addListener(Session session, EventListener listener, - int eventTypes, String basePath, String nodeType) { - try { - session.getWorkspace() - .getObservationManager() - .addEventListener( - listener, - eventTypes, - basePath, - true, - null, - nodeType == null ? null : new String[] { nodeType }, - true); - } catch (RepositoryException e) { - throw new ArgeoException("Cannot add JCR listener " + listener - + " to session " + session, e); - } - } - - /** Removes a listener without throwing exception */ - public static void removeListenerQuietly(Session session, - EventListener listener) { - if (session == null || !session.isLive()) - return; - try { - session.getWorkspace().getObservationManager() - .removeEventListener(listener); - } catch (RepositoryException e) { - // silent - } - } - - /** - * Quietly unregisters an {@link EventListener} from the udnerlying - * workspace of this node. - */ - public static void unregisterQuietly(Node node, EventListener eventListener) { - try { - unregisterQuietly(node.getSession().getWorkspace(), eventListener); - } catch (RepositoryException e) { - // silent - if (log.isTraceEnabled()) - log.trace("Could not unregister event listener " - + eventListener); - } - } - - /** Quietly unregisters an {@link EventListener} from this workspace */ - public static void unregisterQuietly(Workspace workspace, - EventListener eventListener) { - if (eventListener == null) - return; - try { - workspace.getObservationManager() - .removeEventListener(eventListener); - } catch (RepositoryException e) { - // silent - if (log.isTraceEnabled()) - log.trace("Could not unregister event listener " - + eventListener); - } - } - - /** - * If this node is has the {@link NodeType#MIX_LAST_MODIFIED} mixin, it - * updates the {@link Property#JCR_LAST_MODIFIED} property with the current - * time and the {@link Property#JCR_LAST_MODIFIED_BY} property with the - * underlying session user id. In Jackrabbit 2.x, these properties - * are not automatically updated, hence the need for manual update. The - * session is not saved. - */ - public static void updateLastModified(Node node) { - try { - if (!node.isNodeType(NodeType.MIX_LAST_MODIFIED)) - node.addMixin(NodeType.MIX_LAST_MODIFIED); - node.setProperty(Property.JCR_LAST_MODIFIED, - new GregorianCalendar()); - node.setProperty(Property.JCR_LAST_MODIFIED_BY, node.getSession() - .getUserID()); - } catch (RepositoryException e) { - throw new ArgeoException("Cannot update last modified on " + node, - e); - } - } - - /** - * Update lastModified recursively until this parent. - * - * @param node - * the node - * @param untilPath - * the base path, null is equivalent to "/" - */ - public static void updateLastModifiedAndParents(Node node, String untilPath) { - try { - if (untilPath != null && !node.getPath().startsWith(untilPath)) - throw new ArgeoException(node + " is not under " + untilPath); - updateLastModified(node); - if (untilPath == null) { - if (!node.getPath().equals("/")) - updateLastModifiedAndParents(node.getParent(), untilPath); - } else { - if (!node.getPath().equals(untilPath)) - updateLastModifiedAndParents(node.getParent(), untilPath); - } - } catch (RepositoryException e) { - throw new ArgeoException("Cannot update lastModified from " + node - + " until " + untilPath, e); - } - } - - /** - * Returns a String representing the short version (see Node type - * Notation attributes grammar) of the main business attributes of this - * property definition - * - * @param prop - */ - public static String getPropertyDefinitionAsString(Property prop) { - StringBuffer sbuf = new StringBuffer(); - try { - if (prop.getDefinition().isAutoCreated()) - sbuf.append("a"); - if (prop.getDefinition().isMandatory()) - sbuf.append("m"); - if (prop.getDefinition().isProtected()) - sbuf.append("p"); - if (prop.getDefinition().isMultiple()) - sbuf.append("*"); - } catch (RepositoryException re) { - throw new ArgeoException( - "unexpected error while getting property definition as String", - re); - } - return sbuf.toString(); - } - - /** - * Estimate the sub tree size from current node. Computation is based on the - * Jcr {@link Property.getLength()} method. Note : it is not the exact size - * used on the disk by the current part of the JCR Tree. - */ - - public static long getNodeApproxSize(Node node) { - long curNodeSize = 0; - try { - PropertyIterator pi = node.getProperties(); - while (pi.hasNext()) { - Property prop = pi.nextProperty(); - if (prop.isMultiple()) { - int nb = prop.getLengths().length; - for (int i = 0; i < nb; i++) { - curNodeSize += (prop.getLengths()[i] > 0 ? prop - .getLengths()[i] : 0); - } - } else - curNodeSize += (prop.getLength() > 0 ? prop.getLength() : 0); - } - - NodeIterator ni = node.getNodes(); - while (ni.hasNext()) - curNodeSize += getNodeApproxSize(ni.nextNode()); - return curNodeSize; - } catch (RepositoryException re) { - throw new ArgeoException( - "Unexpected error while recursively determining node size.", - re); - } - } - - /* - * SECURITY - */ - - /** - * Convenience method for adding a single privilege to a principal (user or - * role), typically jcr:all - */ - public synchronized static void addPrivilege(Session session, String path, - String principal, String privilege) throws RepositoryException { - List privileges = new ArrayList(); - privileges.add(session.getAccessControlManager().privilegeFromName( - privilege)); - addPrivileges(session, path, new SimplePrincipal(principal), privileges); - } - - /** - * Add privileges on a path to a {@link Principal}. The path must already - * exist. Session is saved. Synchronized to prevent concurrent modifications - * of the same node. - */ - public synchronized static Boolean addPrivileges(Session session, - String path, Principal principal, List privs) - throws RepositoryException { - // make sure the session is in line with the persisted state - session.refresh(false); - AccessControlManager acm = session.getAccessControlManager(); - AccessControlList acl = getAccessControlList(acm, path); - - accessControlEntries: for (AccessControlEntry ace : acl - .getAccessControlEntries()) { - Principal currentPrincipal = ace.getPrincipal(); - if (currentPrincipal.getName().equals(principal.getName())) { - Privilege[] currentPrivileges = ace.getPrivileges(); - if (currentPrivileges.length != privs.size()) - break accessControlEntries; - for (int i = 0; i < currentPrivileges.length; i++) { - Privilege currP = currentPrivileges[i]; - Privilege p = privs.get(i); - if (!currP.getName().equals(p.getName())) { - break accessControlEntries; - } - } - return false; - } - } - - Privilege[] privileges = privs.toArray(new Privilege[privs.size()]); - acl.addAccessControlEntry(principal, privileges); - acm.setPolicy(path, acl); - if (log.isDebugEnabled()) { - StringBuffer privBuf = new StringBuffer(); - for (Privilege priv : privs) - privBuf.append(priv.getName()); - log.debug("Added privileges " + privBuf + " to " - + principal.getName() + " on " + path + " in '" - + session.getWorkspace().getName() + "'"); - } - session.refresh(true); - session.save(); - return true; - } - - /** Gets access control list for this path, throws exception if not found */ - public synchronized static AccessControlList getAccessControlList( - AccessControlManager acm, String path) throws RepositoryException { - // search for an access control list - AccessControlList acl = null; - AccessControlPolicyIterator policyIterator = acm - .getApplicablePolicies(path); - if (policyIterator.hasNext()) { - while (policyIterator.hasNext()) { - AccessControlPolicy acp = policyIterator - .nextAccessControlPolicy(); - if (acp instanceof AccessControlList) - acl = ((AccessControlList) acp); - } - } else { - AccessControlPolicy[] existingPolicies = acm.getPolicies(path); - for (AccessControlPolicy acp : existingPolicies) { - if (acp instanceof AccessControlList) - acl = ((AccessControlList) acp); - } - } - if (acl != null) - return acl; - else - throw new ArgeoException("ACL not found at " + path); - } - - /** Clear authorizations for a user at this path */ - public synchronized static void clearAccessControList(Session session, - String path, String username) throws RepositoryException { - AccessControlManager acm = session.getAccessControlManager(); - AccessControlList acl = getAccessControlList(acm, path); - for (AccessControlEntry ace : acl.getAccessControlEntries()) { - if (ace.getPrincipal().getName().equals(username)) { - acl.removeAccessControlEntry(ace); - } - } - } - - /* - * FILES UTILITIES - */ - /** - * Creates the nodes making the path as {@link NodeType#NT_FOLDER} - */ - public static Node mkfolders(Session session, String path) { - return mkdirs(session, path, NodeType.NT_FOLDER, NodeType.NT_FOLDER, - false); - } - - /** - * Copy only nt:folder and nt:file, without their additional types and - * properties. - * - * @param recursive - * if true copies folders as well, otherwise only first level - * files - * @return how many files were copied - */ - @SuppressWarnings("resource") - public static Long copyFiles(Node fromNode, Node toNode, Boolean recursive, - ArgeoMonitor monitor) { - long count = 0l; - - Binary binary = null; - InputStream in = null; - try { - NodeIterator fromChildren = fromNode.getNodes(); - while (fromChildren.hasNext()) { - if (monitor != null && monitor.isCanceled()) - throw new ArgeoException( - "Copy cancelled before it was completed"); - - Node fromChild = fromChildren.nextNode(); - String fileName = fromChild.getName(); - if (fromChild.isNodeType(NodeType.NT_FILE)) { - if (monitor != null) - monitor.subTask("Copy " + fileName); - binary = fromChild.getNode(Node.JCR_CONTENT) - .getProperty(Property.JCR_DATA).getBinary(); - in = binary.getStream(); - copyStreamAsFile(toNode, fileName, in); - IOUtils.closeQuietly(in); - closeQuietly(binary); - - // save session - toNode.getSession().save(); - count++; - - if (log.isDebugEnabled()) - log.debug("Copied file " + fromChild.getPath()); - if (monitor != null) - monitor.worked(1); - } else if (fromChild.isNodeType(NodeType.NT_FOLDER) - && recursive) { - Node toChildFolder; - if (toNode.hasNode(fileName)) { - toChildFolder = toNode.getNode(fileName); - if (!toChildFolder.isNodeType(NodeType.NT_FOLDER)) - throw new ArgeoException(toChildFolder - + " is not of type nt:folder"); - } else { - toChildFolder = toNode.addNode(fileName, - NodeType.NT_FOLDER); - - // save session - toNode.getSession().save(); - } - count = count - + copyFiles(fromChild, toChildFolder, recursive, - monitor); - } - } - return count; - } catch (RepositoryException e) { - throw new ArgeoException("Cannot copy files between " + fromNode - + " and " + toNode); - } finally { - // in case there was an exception - IOUtils.closeQuietly(in); - closeQuietly(binary); - } - } - - /** - * Iteratively count all file nodes in subtree, inefficient but can be - * useful when query are poorly supported, such as in remoting. - */ - public static Long countFiles(Node node) { - Long localCount = 0l; - try { - for (NodeIterator nit = node.getNodes(); nit.hasNext();) { - Node child = nit.nextNode(); - if (child.isNodeType(NodeType.NT_FOLDER)) - localCount = localCount + countFiles(child); - else if (child.isNodeType(NodeType.NT_FILE)) - localCount = localCount + 1; - } - } catch (RepositoryException e) { - throw new ArgeoException("Cannot count all children of " + node); - } - return localCount; - } - - /** - * Copy a file as an nt:file, assuming an nt:folder hierarchy. The session - * is NOT saved. - * - * @return the created file node - */ - public static Node copyFile(Node folderNode, File file) { - InputStream in = null; - try { - in = new FileInputStream(file); - return copyStreamAsFile(folderNode, file.getName(), in); - } catch (IOException e) { - throw new ArgeoException("Cannot copy file " + file + " under " - + folderNode, e); - } finally { - IOUtils.closeQuietly(in); - } - } - - /** Copy bytes as an nt:file */ - public static Node copyBytesAsFile(Node folderNode, String fileName, - byte[] bytes) { - InputStream in = null; - try { - in = new ByteArrayInputStream(bytes); - return copyStreamAsFile(folderNode, fileName, in); - } catch (Exception e) { - throw new ArgeoException("Cannot copy file " + fileName + " under " - + folderNode, e); - } finally { - IOUtils.closeQuietly(in); - } - } - - /** - * Copy a stream as an nt:file, assuming an nt:folder hierarchy. The session - * is NOT saved. - * - * @return the created file node - */ - public static Node copyStreamAsFile(Node folderNode, String fileName, - InputStream in) { - Binary binary = null; - try { - Node fileNode; - Node contentNode; - if (folderNode.hasNode(fileName)) { - fileNode = folderNode.getNode(fileName); - if (!fileNode.isNodeType(NodeType.NT_FILE)) - throw new ArgeoException(fileNode - + " is not of type nt:file"); - // we assume that the content node is already there - contentNode = fileNode.getNode(Node.JCR_CONTENT); - } else { - fileNode = folderNode.addNode(fileName, NodeType.NT_FILE); - contentNode = fileNode.addNode(Node.JCR_CONTENT, - NodeType.NT_RESOURCE); - } - binary = contentNode.getSession().getValueFactory() - .createBinary(in); - contentNode.setProperty(Property.JCR_DATA, binary); - return fileNode; - } catch (Exception e) { - throw new ArgeoException("Cannot create file node " + fileName - + " under " + folderNode, e); - } finally { - closeQuietly(binary); - } - } - - /** Computes the checksum of an nt:file */ - public static String checksumFile(Node fileNode, String algorithm) { - Binary data = null; - InputStream in = null; - try { - data = fileNode.getNode(Node.JCR_CONTENT) - .getProperty(Property.JCR_DATA).getBinary(); - in = data.getStream(); - return DigestUtils.digest(algorithm, in); - } catch (RepositoryException e) { - throw new ArgeoException("Cannot checksum file " + fileNode, e); - } finally { - IOUtils.closeQuietly(in); - closeQuietly(data); - } - } - -} diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/MaintainedRepository.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/MaintainedRepository.java deleted file mode 100644 index 702d47a5c..000000000 --- a/org.argeo.server.jcr/newsrc/org/argeo/jcr/MaintainedRepository.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.argeo.jcr; - -import javax.jcr.Repository; - -/** Abstracts maintenance operations on a {@link Repository} */ -public interface MaintainedRepository extends Repository { - -} diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/NodeMapper.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/NodeMapper.java deleted file mode 100644 index af792c3ca..000000000 --- a/org.argeo.server.jcr/newsrc/org/argeo/jcr/NodeMapper.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * 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.Node; -import javax.jcr.Session; - -public interface NodeMapper { - public Object load(Node node); - - public void update(Node node, Object obj); - - public Node save(Session session, String path, Object obj); - - public void setNodeMapperProvider(NodeMapperProvider nmp); -} diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/NodeMapperProvider.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/NodeMapperProvider.java deleted file mode 100644 index 07e623bfd..000000000 --- a/org.argeo.server.jcr/newsrc/org/argeo/jcr/NodeMapperProvider.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * 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.Node; - -/** Provides a node mapper relevant for this node. */ -public interface NodeMapperProvider { - - /** - * Node Mapper is chosen regarding the Jcr path of the node parameter - * @param Node node - * @return the node mapper or null if no relevant node mapper can be found. */ - public NodeMapper findNodeMapper(Node node); -} diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/PropertyDiff.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/PropertyDiff.java deleted file mode 100644 index bdd316f31..000000000 --- a/org.argeo.server.jcr/newsrc/org/argeo/jcr/PropertyDiff.java +++ /dev/null @@ -1,78 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * 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.Value; - -import org.argeo.ArgeoException; - -/** The result of the comparison of two JCR properties. */ -public class PropertyDiff { - public final static Integer MODIFIED = 0; - public final static Integer ADDED = 1; - public final static Integer REMOVED = 2; - - private final Integer type; - private final String relPath; - private final Value referenceValue; - private final Value newValue; - - public PropertyDiff(Integer type, String relPath, Value referenceValue, - Value newValue) { - super(); - - if (type == MODIFIED) { - if (referenceValue == null || newValue == null) - throw new ArgeoException( - "Reference and new values must be specified."); - } else if (type == ADDED) { - if (referenceValue != null || newValue == null) - throw new ArgeoException( - "New value and only it must be specified."); - } else if (type == REMOVED) { - if (referenceValue == null || newValue != null) - throw new ArgeoException( - "Reference value and only it must be specified."); - } else { - throw new ArgeoException("Unkown diff type " + type); - } - - if (relPath == null) - throw new ArgeoException("Relative path must be specified"); - - this.type = type; - this.relPath = relPath; - this.referenceValue = referenceValue; - this.newValue = newValue; - } - - public Integer getType() { - return type; - } - - public String getRelPath() { - return relPath; - } - - public Value getReferenceValue() { - return referenceValue; - } - - public Value getNewValue() { - return newValue; - } - -} diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/RepositoryRegister.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/RepositoryRegister.java deleted file mode 100644 index 2e3d4550d..000000000 --- a/org.argeo.server.jcr/newsrc/org/argeo/jcr/RepositoryRegister.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * 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 java.util.Map; - -import javax.jcr.Repository; -import javax.jcr.RepositoryFactory; - -/** Allows to register repositories by name. */ -public interface RepositoryRegister extends RepositoryFactory { - /** - * The registered {@link Repository} as a read-only map. Note that this - * method should be called for each access in order to be sure to be up to - * date in case repositories have registered/unregistered - */ - public Map getRepositories(); -} diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/ThreadBoundJcrSessionFactory.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/ThreadBoundJcrSessionFactory.java deleted file mode 100644 index 193f22c48..000000000 --- a/org.argeo.server.jcr/newsrc/org/argeo/jcr/ThreadBoundJcrSessionFactory.java +++ /dev/null @@ -1,304 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * 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 java.lang.reflect.InvocationHandler; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Proxy; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; - -import javax.jcr.LoginException; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.SimpleCredentials; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.argeo.ArgeoException; - -/** Proxy JCR sessions and attach them to calling threads. */ -public abstract class ThreadBoundJcrSessionFactory { - private final static Log log = LogFactory - .getLog(ThreadBoundJcrSessionFactory.class); - - private Repository repository; - /** can be injected as list, only used if repository is null */ - private List repositories; - - private ThreadLocal session = new ThreadLocal(); - private final Session proxiedSession; - /** If workspace is null, default will be used. */ - private String workspace = null; - - private String defaultUsername = "demo"; - private String defaultPassword = "demo"; - private Boolean forceDefaultCredentials = false; - - private boolean active = true; - - // monitoring - private final List threads = Collections - .synchronizedList(new ArrayList()); - private final Map activeSessions = Collections - .synchronizedMap(new HashMap()); - private MonitoringThread monitoringThread; - - public ThreadBoundJcrSessionFactory() { - Class[] interfaces = { Session.class }; - proxiedSession = (Session) Proxy.newProxyInstance( - ThreadBoundJcrSessionFactory.class.getClassLoader(), - interfaces, new JcrSessionInvocationHandler()); - } - - /** Logs in to the repository using various strategies. */ - protected synchronized Session login() { - if (!isActive()) - throw new ArgeoException("Thread bound session factory inactive"); - - // discard session previously attached to this thread - Thread thread = Thread.currentThread(); - if (activeSessions.containsKey(thread.getId())) { - Session oldSession = activeSessions.remove(thread.getId()); - oldSession.logout(); - session.remove(); - } - - Session newSession = null; - // first try to login without credentials, assuming the underlying login - // module will have dealt with authentication (typically using Spring - // Security) - if (!forceDefaultCredentials) - try { - newSession = repository().login(workspace); - } catch (LoginException e1) { - log.warn("Cannot login without credentials: " + e1.getMessage()); - // invalid credentials, go to the next step - } catch (RepositoryException e1) { - // other kind of exception, fail - throw new ArgeoException("Cannot log in to repository", e1); - } - - // log using default username / password (useful for testing purposes) - if (newSession == null) - try { - SimpleCredentials sc = new SimpleCredentials(defaultUsername, - defaultPassword.toCharArray()); - newSession = repository().login(sc, workspace); - } catch (RepositoryException e) { - throw new ArgeoException("Cannot log in to repository", e); - } - - session.set(newSession); - // Log and monitor new session - if (log.isTraceEnabled()) - log.trace("Logged in to JCR session " + newSession + "; userId=" - + newSession.getUserID()); - - // monitoring - activeSessions.put(thread.getId(), newSession); - threads.add(thread); - return newSession; - } - - public Object getObject() { - return proxiedSession; - } - - public void init() throws Exception { - monitoringThread = new MonitoringThread(); - monitoringThread.start(); - } - - public synchronized void dispose() throws Exception { - if (activeSessions.size() == 0) - return; - - if (log.isTraceEnabled()) - log.trace("Cleaning up " + activeSessions.size() - + " active JCR sessions..."); - - deactivate(); - for (Session sess : activeSessions.values()) { - JcrUtils.logoutQuietly(sess); - } - activeSessions.clear(); - } - - protected Boolean isActive() { - return active; - } - - protected synchronized void deactivate() { - active = false; - notifyAll(); - } - - protected synchronized void removeSession(Thread thread) { - if (!isActive()) - return; - activeSessions.remove(thread.getId()); - threads.remove(thread); - } - - protected synchronized void cleanDeadThreads() { - if (!isActive()) - return; - Iterator it = threads.iterator(); - while (it.hasNext()) { - Thread thread = it.next(); - if (!thread.isAlive() && isActive()) { - if (activeSessions.containsKey(thread.getId())) { - Session session = activeSessions.get(thread.getId()); - activeSessions.remove(thread.getId()); - session.logout(); - if (log.isTraceEnabled()) - log.trace("Cleaned up JCR session (userID=" - + session.getUserID() + ") from dead thread " - + thread.getId()); - } - it.remove(); - } - } - try { - wait(1000); - } catch (InterruptedException e) { - // silent - } - } - - public Class getObjectType() { - return Session.class; - } - - public boolean isSingleton() { - return true; - } - - /** - * Called before a method is actually called, allowing to check the session - * or re-login it (e.g. if authentication has changed). The default - * implementation returns the session. - */ - protected Session preCall(Session session) { - return session; - } - - protected Repository repository() { - if (repository != null) - return repository; - if (repositories != null) { - // hardened for OSGi dynamic services - Iterator it = repositories.iterator(); - if (it.hasNext()) - return it.next(); - } - throw new ArgeoException("No repository injected"); - } - - // /** Useful for declarative registration of OSGi services (blueprint) */ - // public void register(Repository repository, Map params) { - // this.repository = repository; - // } - // - // /** Useful for declarative registration of OSGi services (blueprint) */ - // public void unregister(Repository repository, Map params) { - // this.repository = null; - // } - - public void setRepository(Repository repository) { - this.repository = repository; - } - - public void setRepositories(List repositories) { - this.repositories = repositories; - } - - public void setDefaultUsername(String defaultUsername) { - this.defaultUsername = defaultUsername; - } - - public void setDefaultPassword(String defaultPassword) { - this.defaultPassword = defaultPassword; - } - - public void setForceDefaultCredentials(Boolean forceDefaultCredentials) { - this.forceDefaultCredentials = forceDefaultCredentials; - } - - public void setWorkspace(String workspace) { - this.workspace = workspace; - } - - protected class JcrSessionInvocationHandler implements InvocationHandler { - - public Object invoke(Object proxy, Method method, Object[] args) - throws Throwable, RepositoryException { - Session threadSession = session.get(); - if (threadSession == null) { - if ("logout".equals(method.getName()))// no need to login - return Void.TYPE; - else if ("toString".equals(method.getName()))// maybe logging - return "Uninitialized Argeo thread bound JCR session"; - threadSession = login(); - } - - preCall(threadSession); - Object ret; - try { - ret = method.invoke(threadSession, args); - } catch (InvocationTargetException e) { - Throwable cause = e.getCause(); - if (cause instanceof RepositoryException) - throw (RepositoryException) cause; - else - throw cause; - } - if ("logout".equals(method.getName())) { - session.remove(); - Thread thread = Thread.currentThread(); - removeSession(thread); - if (log.isTraceEnabled()) - log.trace("Logged out JCR session (userId=" - + threadSession.getUserID() + ") on thread " - + thread.getId()); - } - return ret; - } - } - - /** Monitors registered thread in order to clean up dead ones. */ - private class MonitoringThread extends Thread { - - public MonitoringThread() { - super("ThreadBound JCR Session Monitor"); - } - - @Override - public void run() { - while (isActive()) { - cleanDeadThreads(); - } - } - - } -} diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/UserJcrUtils.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/UserJcrUtils.java deleted file mode 100644 index 1758802f8..000000000 --- a/org.argeo.server.jcr/newsrc/org/argeo/jcr/UserJcrUtils.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * 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.Node; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.query.Query; -import javax.jcr.query.qom.Constraint; -import javax.jcr.query.qom.DynamicOperand; -import javax.jcr.query.qom.QueryObjectModelFactory; -import javax.jcr.query.qom.Selector; -import javax.jcr.query.qom.StaticOperand; - -import org.argeo.ArgeoException; - -/** Utilities related to the user home and properties based on Argeo JCR model. */ -public class UserJcrUtils { - /** The home base path. Not yet configurable */ - public final static String DEFAULT_HOME_BASE_PATH = "/home"; - - /** - * Returns the home node of the user or null if none was found. - * - * @param session - * the session to use in order to perform the search, this can be - * a session with a different user ID than the one searched, - * typically when a system or admin session is used. - * @param username - * the username of the user - */ - public static Node getUserHome(Session session, String username) { - try { - // String homePath = UserJcrUtils.getUserHomePath(username); - // return session.itemExists(homePath) ? session.getNode(homePath) - // : null; - // kept for example of QOM queries - QueryObjectModelFactory qomf = session.getWorkspace() - .getQueryManager().getQOMFactory(); - Selector userHomeSel = qomf.selector(ArgeoTypes.ARGEO_USER_HOME, - "userHome"); - DynamicOperand userIdDop = qomf.propertyValue( - userHomeSel.getSelectorName(), ArgeoNames.ARGEO_USER_ID); - StaticOperand userIdSop = qomf.literal(session.getValueFactory() - .createValue(username)); - Constraint constraint = qomf.comparison(userIdDop, - QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, userIdSop); - Query query = qomf.createQuery(userHomeSel, constraint, null, null); - return JcrUtils.querySingleNode(query); - } catch (RepositoryException e) { - throw new ArgeoException("Cannot find home for user " + username, e); - } - } - - public static Node getUserProfile(Session session, String username) { - try { - QueryObjectModelFactory qomf = session.getWorkspace() - .getQueryManager().getQOMFactory(); - Selector userHomeSel = qomf.selector(ArgeoTypes.ARGEO_USER_PROFILE, - "userProfile"); - DynamicOperand userIdDop = qomf.propertyValue( - userHomeSel.getSelectorName(), ArgeoNames.ARGEO_USER_ID); - StaticOperand userIdSop = qomf.literal(session.getValueFactory() - .createValue(username)); - Constraint constraint = qomf.comparison(userIdDop, - QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, userIdSop); - Query query = qomf.createQuery(userHomeSel, constraint, null, null); - return JcrUtils.querySingleNode(query); - } catch (RepositoryException e) { - throw new ArgeoException( - "Cannot find profile for user " + username, e); - } - } - - /** Returns the home node of the session user or null if none was found. */ - public static Node getUserHome(Session session) { - String userID = session.getUserID(); - return getUserHome(session, userID); - } - - private UserJcrUtils() { - } -} diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/VersionDiff.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/VersionDiff.java deleted file mode 100644 index e6ae9130b..000000000 --- a/org.argeo.server.jcr/newsrc/org/argeo/jcr/VersionDiff.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * 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 java.util.Calendar; -import java.util.Map; - -/** - * Generic Object that enables the creation of history reports based on a JCR - * versionable node. userId and creation date are added to the map of - * PropertyDiff. - * - * These two fields might be null - * - */ -public class VersionDiff { - - private String userId; - private Map diffs; - private Calendar updateTime; - - public VersionDiff(String userId, Calendar updateTime, - Map diffs) { - this.userId = userId; - this.updateTime = updateTime; - this.diffs = diffs; - } - - public String getUserId() { - return userId; - } - - public Map getDiffs() { - return diffs; - } - - public Calendar getUpdateTime() { - return updateTime; - } -} diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/argeo.cnd b/org.argeo.server.jcr/newsrc/org/argeo/jcr/argeo.cnd deleted file mode 100644 index fbfea9dd9..000000000 --- a/org.argeo.server.jcr/newsrc/org/argeo/jcr/argeo.cnd +++ /dev/null @@ -1,72 +0,0 @@ - - -// GENERIC TYPES NOT AVAILABLE IN JCR -[argeo:link] > mix:created, mix:lastModified -mixin -// URI(s) -- argeo:uri (STRING) m - -[argeo:references] > nt:unstructured -- * (REFERENCE) * - -// DATA MODEL -[argeo:dataModel] > mix:created, mix:lastModified, mix:versionable -mixin -- argeo:uri (STRING) m -- argeo:dataModelVersion (STRING) m - -// USER NODES -// user should be lower case, between 3 and 15 characters long -[argeo:userHome] > mix:created, mix:lastModified -mixin -- argeo:userID (STRING) m -- argeo:remoteRoles (STRING) * -// deprecated. for backward compatibility: -+ argeo:profile (argeo:userProfile) -+ argeo:keyring (argeo:pbeSpec) -+ argeo:preferences (argeo:preferenceNode) - -[argeo:userProfile] > mix:created, mix:lastModified, mix:title, mix:versionable -mixin -- argeo:userID (STRING) m -- argeo:enabled (BOOLEAN) -- argeo:accountNonExpired (BOOLEAN) -- argeo:accountNonLocked (BOOLEAN) -- argeo:credentialsNonExpired (BOOLEAN) - -[argeo:preferenceNode] > mix:lastModified, mix:versionable -mixin -+ * (argeo:preferenceNode) * version - -[argeo:remoteRepository] > nt:unstructured -- argeo:uri (STRING) -- argeo:userID (STRING) -+ argeo:password (argeo:encrypted) - -// TABULAR CONTENT -[argeo:table] > nt:file -+ * (argeo:column) * - -[argeo:column] > mix:title -- jcr:requiredType (STRING) = 'STRING' - -[argeo:csv] > nt:resource - -// CRYPTO -[argeo:encrypted] > nt:base -mixin -// initialization vector used by some algorithms -- argeo:iv (BINARY) - -[argeo:pbeKeySpec] > nt:base -mixin -- argeo:secretKeyFactory (STRING) -- argeo:salt (BINARY) -- argeo:iterationCount (LONG) -- argeo:keyLength (LONG) -- argeo:secretKeyEncryption (STRING) - -[argeo:pbeSpec] > argeo:pbeKeySpec -mixin -- argeo:cipher (STRING) - diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/proxy/AbstractUrlProxy.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/proxy/AbstractUrlProxy.java deleted file mode 100644 index 8a66f3102..000000000 --- a/org.argeo.server.jcr/newsrc/org/argeo/jcr/proxy/AbstractUrlProxy.java +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * 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.proxy; - -import java.io.IOException; -import java.io.InputStream; -import java.net.URL; - -import javax.jcr.Binary; -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.nodetype.NodeType; - -import org.apache.commons.io.IOUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.argeo.ArgeoException; -import org.argeo.jcr.JcrUtils; - -/** Base class for URL based proxys. */ -public abstract class AbstractUrlProxy implements ResourceProxy { - private final static Log log = LogFactory.getLog(AbstractUrlProxy.class); - - private Repository jcrRepository; - private Session jcrAdminSession; - private String proxyWorkspace = "proxy"; - - protected abstract Node retrieve(Session session, String path); - - void init() { - try { - jcrAdminSession = JcrUtils.loginOrCreateWorkspace(jcrRepository, - proxyWorkspace); - beforeInitSessionSave(jcrAdminSession); - if (jcrAdminSession.hasPendingChanges()) - jcrAdminSession.save(); - } catch (Exception e) { - JcrUtils.discardQuietly(jcrAdminSession); - throw new ArgeoException("Cannot initialize Maven proxy", e); - } - } - - /** - * Called before the (admin) session is saved at the end of the - * initialization. Does nothing by default, to be overridden. - */ - protected void beforeInitSessionSave(Session session) - throws RepositoryException { - } - - void destroy() { - JcrUtils.logoutQuietly(jcrAdminSession); - } - - /** - * Called before the (admin) session is logged out when resources are - * released. Does nothing by default, to be overridden. - */ - protected void beforeDestroySessionLogout() throws RepositoryException { - } - - public Node proxy(String path) { - // we open a JCR session with client credentials in order not to use the - // admin session in multiple thread or make it a bottleneck. - Node nodeAdmin = null; - Node nodeClient = null; - Session clientSession = null; - try { - clientSession = jcrRepository.login(proxyWorkspace); - if (!clientSession.itemExists(path) - || shouldUpdate(clientSession, path)) { - nodeAdmin = retrieveAndSave(path); - if (nodeAdmin != null) - nodeClient = clientSession.getNode(path); - } else - nodeClient = clientSession.getNode(path); - return nodeClient; - } catch (RepositoryException e) { - throw new ArgeoException("Cannot proxy " + path, e); - } finally { - if (nodeClient == null) - JcrUtils.logoutQuietly(clientSession); - } - } - - protected synchronized Node retrieveAndSave(String path) { - try { - Node node = retrieve(jcrAdminSession, path); - if (node == null) - return null; - jcrAdminSession.save(); - return node; - } catch (RepositoryException e) { - JcrUtils.discardQuietly(jcrAdminSession); - throw new ArgeoException("Cannot retrieve and save " + path, e); - } finally { - notifyAll(); - } - } - - /** Session is not saved */ - protected synchronized Node proxyUrl(Session session, String remoteUrl, - String path) throws RepositoryException { - Node node = null; - if (session.itemExists(path)) { - // throw new ArgeoException("Node " + path + " already exists"); - } - InputStream in = null; - try { - URL u = new URL(remoteUrl); - in = u.openStream(); - node = importFile(session, path, in); - } catch (IOException e) { - if (log.isDebugEnabled()) { - log.debug("Cannot read " + remoteUrl + ", skipping... " - + e.getMessage()); - // log.trace("Cannot read because of ", e); - } - JcrUtils.discardQuietly(session); - } finally { - IOUtils.closeQuietly(in); - } - return node; - } - - protected synchronized Node importFile(Session session, String path, - InputStream in) throws RepositoryException { - Binary binary = null; - try { - Node content = null; - Node node = null; - if (!session.itemExists(path)) { - node = JcrUtils.mkdirs(session, path, NodeType.NT_FILE, - NodeType.NT_FOLDER, false); - content = node.addNode(Node.JCR_CONTENT, NodeType.NT_RESOURCE); - } else { - node = session.getNode(path); - content = node.getNode(Node.JCR_CONTENT); - } - binary = session.getValueFactory().createBinary(in); - content.setProperty(Property.JCR_DATA, binary); - JcrUtils.updateLastModifiedAndParents(node, null); - return node; - } finally { - JcrUtils.closeQuietly(binary); - } - } - - /** Whether the file should be updated. */ - protected Boolean shouldUpdate(Session clientSession, String nodePath) { - return false; - } - - public void setJcrRepository(Repository jcrRepository) { - this.jcrRepository = jcrRepository; - } - - public void setProxyWorkspace(String localWorkspace) { - this.proxyWorkspace = localWorkspace; - } - -} diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/proxy/ResourceProxy.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/proxy/ResourceProxy.java deleted file mode 100644 index b4fb33286..000000000 --- a/org.argeo.server.jcr/newsrc/org/argeo/jcr/proxy/ResourceProxy.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * 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.proxy; - -import javax.jcr.Node; - -/** A proxy which nows how to resolve and synchronize relative URLs */ -public interface ResourceProxy { - /** - * Proxy the file referenced by this relative path in the underlying - * repository. A new session is created by each call, so the underlying - * session of the returned node must be closed by the caller. - * - * @return the proxied Node, null if the resource was not found - * (e.g. HTTP 404) - */ - public Node proxy(String relativePath); -} diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/security/JcrAuthorizations.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/security/JcrAuthorizations.java deleted file mode 100644 index 491f8a6fe..000000000 --- a/org.argeo.server.jcr/newsrc/org/argeo/jcr/security/JcrAuthorizations.java +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * 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.security; - -import java.security.Principal; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import javax.jcr.Repository; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.security.AccessControlManager; -import javax.jcr.security.Privilege; - -import org.argeo.ArgeoException; -import org.argeo.jcr.JcrUtils; -import org.argeo.util.security.SimplePrincipal; - -/** Apply authorizations to a JCR repository. */ -public class JcrAuthorizations implements Runnable { - // private final static Log log = - // LogFactory.getLog(JcrAuthorizations.class); - - private Repository repository; - private String workspace = null; - - private String securityWorkspace = "security"; - - /** - * key := privilege1,privilege2/path/to/node
- * value := group1,group2,user1 - */ - private Map principalPrivileges = new HashMap(); - - public void run() { - String currentWorkspace = workspace; - Session session = null; - try { - if (workspace != null && workspace.equals("*")) { - session = repository.login(); - String[] workspaces = session.getWorkspace() - .getAccessibleWorkspaceNames(); - JcrUtils.logoutQuietly(session); - for (String wksp : workspaces) { - currentWorkspace = wksp; - if (currentWorkspace.equals(securityWorkspace)) - continue; - session = repository.login(currentWorkspace); - initAuthorizations(session); - JcrUtils.logoutQuietly(session); - } - } else { - session = repository.login(workspace); - initAuthorizations(session); - } - } catch (Exception e) { - JcrUtils.discardQuietly(session); - throw new ArgeoException( - "Cannot set authorizations " + principalPrivileges - + " on workspace " + currentWorkspace, e); - } finally { - JcrUtils.logoutQuietly(session); - } - } - - protected void processWorkspace(String workspace) { - Session session = null; - try { - session = repository.login(workspace); - initAuthorizations(session); - } catch (Exception e) { - JcrUtils.discardQuietly(session); - throw new ArgeoException("Cannot set authorizations " - + principalPrivileges + " on repository " + repository, e); - } finally { - JcrUtils.logoutQuietly(session); - } - } - - /** @deprecated call {@link #run()} instead. */ - @Deprecated - public void init() { - run(); - } - - protected void initAuthorizations(Session session) - throws RepositoryException { - AccessControlManager acm = session.getAccessControlManager(); - - for (String privileges : principalPrivileges.keySet()) { - String path = null; - int slashIndex = privileges.indexOf('/'); - if (slashIndex == 0) { - throw new ArgeoException("Privilege " + privileges - + " badly formatted it starts with /"); - } else if (slashIndex > 0) { - path = privileges.substring(slashIndex); - privileges = privileges.substring(0, slashIndex); - } - - if (path == null) - path = "/"; - - List privs = new ArrayList(); - for (String priv : privileges.split(",")) { - privs.add(acm.privilegeFromName(priv)); - } - - String principalNames = principalPrivileges.get(privileges); - for (String principalName : principalNames.split(",")) { - Principal principal = getOrCreatePrincipal(session, - principalName); - JcrUtils.addPrivileges(session, path, principal, privs); - // if (log.isDebugEnabled()) { - // StringBuffer privBuf = new StringBuffer(); - // for (Privilege priv : privs) - // privBuf.append(priv.getName()); - // log.debug("Added privileges " + privBuf + " to " - // + principal.getName() + " on " + path + " in '" - // + session.getWorkspace().getName() + "'"); - // } - } - } - - // if (log.isDebugEnabled()) - // log.debug("JCR authorizations applied on '" - // + session.getWorkspace().getName() + "'"); - } - - /** - * Returns a {@link SimplePrincipal}, does not check whether it exists since - * such capabilities is not provided by the standard JCR API. Can be - * overridden to provide smarter handling - */ - protected Principal getOrCreatePrincipal(Session session, - String principalName) throws RepositoryException { - return new SimplePrincipal(principalName); - } - - // public static void addPrivileges(Session session, Principal principal, - // String path, List privs) throws RepositoryException { - // AccessControlManager acm = session.getAccessControlManager(); - // // search for an access control list - // AccessControlList acl = null; - // AccessControlPolicyIterator policyIterator = acm - // .getApplicablePolicies(path); - // if (policyIterator.hasNext()) { - // while (policyIterator.hasNext()) { - // AccessControlPolicy acp = policyIterator - // .nextAccessControlPolicy(); - // if (acp instanceof AccessControlList) - // acl = ((AccessControlList) acp); - // } - // } else { - // AccessControlPolicy[] existingPolicies = acm.getPolicies(path); - // for (AccessControlPolicy acp : existingPolicies) { - // if (acp instanceof AccessControlList) - // acl = ((AccessControlList) acp); - // } - // } - // - // if (acl != null) { - // acl.addAccessControlEntry(principal, - // privs.toArray(new Privilege[privs.size()])); - // acm.setPolicy(path, acl); - // session.save(); - // if (log.isDebugEnabled()) { - // StringBuffer buf = new StringBuffer(""); - // for (int i = 0; i < privs.size(); i++) { - // if (i != 0) - // buf.append(','); - // buf.append(privs.get(i).getName()); - // } - // log.debug("Added privilege(s) '" + buf + "' to '" - // + principal.getName() + "' on " + path - // + " from workspace '" - // + session.getWorkspace().getName() + "'"); - // } - // } else { - // throw new ArgeoException("Don't know how to apply privileges " - // + privs + " to " + principal + " on " + path - // + " from workspace '" + session.getWorkspace().getName() - // + "'"); - // } - // } - - @Deprecated - public void setGroupPrivileges(Map groupPrivileges) { - this.principalPrivileges = groupPrivileges; - } - - public void setPrincipalPrivileges(Map principalPrivileges) { - this.principalPrivileges = principalPrivileges; - } - - public void setRepository(Repository repository) { - this.repository = repository; - } - - public void setWorkspace(String workspace) { - this.workspace = workspace; - } - - public void setSecurityWorkspace(String securityWorkspace) { - this.securityWorkspace = securityWorkspace; - } - -} diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/spring/BeanNodeMapper.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/spring/BeanNodeMapper.java deleted file mode 100644 index 9f70f5c58..000000000 --- a/org.argeo.server.jcr/newsrc/org/argeo/jcr/spring/BeanNodeMapper.java +++ /dev/null @@ -1,649 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * 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.spring; - -import java.beans.PropertyDescriptor; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.Date; -import java.util.GregorianCalendar; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.StringTokenizer; - -import javax.jcr.Binary; -import javax.jcr.ItemNotFoundException; -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.Property; -import javax.jcr.PropertyIterator; -import javax.jcr.PropertyType; -import javax.jcr.RepositoryException; -import javax.jcr.Session; -import javax.jcr.Value; -import javax.jcr.ValueFactory; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.argeo.ArgeoException; -import org.argeo.jcr.JcrUtils; -import org.argeo.jcr.NodeMapper; -import org.argeo.jcr.NodeMapperProvider; -import org.springframework.beans.BeanWrapper; -import org.springframework.beans.BeanWrapperImpl; - -public class BeanNodeMapper implements NodeMapper { - private final static Log log = LogFactory.getLog(BeanNodeMapper.class); - - private final static String NODE_VALUE = "value"; - - // private String keyNode = "bean:key"; - private String uuidProperty = "uuid"; - private String classProperty = "class"; - - private Boolean versioning = false; - private Boolean strictUuidReference = false; - - // TODO define a primaryNodeType Strategy - private String primaryNodeType = null; - - private ClassLoader classLoader = getClass().getClassLoader(); - - private NodeMapperProvider nodeMapperProvider; - - /** - * exposed method to retrieve a bean from a node - */ - public Object load(Node node) { - try { - if (nodeMapperProvider != null) { - NodeMapper nodeMapper = nodeMapperProvider.findNodeMapper(node); - if (nodeMapper != this) { - return nodeMapper.load(node); - } - } - return nodeToBean(node); - } catch (RepositoryException e) { - throw new ArgeoException("Cannot load object from node " + node, e); - } - } - - /** Update an existing node with an object */ - public void update(Node node, Object obj) { - try { - if (nodeMapperProvider != null) { - - NodeMapper nodeMapper = nodeMapperProvider.findNodeMapper(node); - if (nodeMapper != this) { - nodeMapper.update(node, obj); - } else - beanToNode(createBeanWrapper(obj), node); - } else - beanToNode(createBeanWrapper(obj), node); - } catch (RepositoryException e) { - throw new ArgeoException("Cannot update node " + node + " with " - + obj, e); - } - } - - /** - * if no storage path is given; we use canonical path - * - * @see this.storagePath() - */ - public Node save(Session session, Object obj) { - return save(session, storagePath(obj), obj); - } - - /** - * Create a new node to store an object. If the parentNode doesn't exist, it - * is created - * - * the primaryNodeType may be initialized before - */ - public Node save(Session session, String path, Object obj) { - try { - final Node node; - String parentPath = JcrUtils.parentPath(path); - // find or create parent node - Node parentNode; - if (session.itemExists(path)) - parentNode = (Node) session.getItem(parentPath); - else { - parentNode = JcrUtils.mkdirs(session, parentPath, null, null, - versioning); - } - // create node - - if (primaryNodeType != null) - node = parentNode.addNode(JcrUtils.lastPathElement(path), - primaryNodeType); - else - node = parentNode.addNode(JcrUtils.lastPathElement(path)); - - // Check specific cases - if (nodeMapperProvider != null) { - NodeMapper nodeMapper = nodeMapperProvider.findNodeMapper(node); - if (nodeMapper != this) { - nodeMapper.update(node, obj); - return node; - } - } - update(node, obj); - return node; - } catch (ArgeoException e) { - throw e; - } catch (Exception e) { - throw new ArgeoException("Cannot save or update " + obj + " under " - + path, e); - } - } - - /** - * Parse the FQN of a class to string with '/' delimiters Prefix the - * returned string with "/objects/" - */ - public String storagePath(Object obj) { - String clss = obj.getClass().getName(); - StringBuffer buf = new StringBuffer("/objects/"); - StringTokenizer st = new StringTokenizer(clss, "."); - while (st.hasMoreTokens()) { - buf.append(st.nextToken()).append('/'); - } - buf.append(obj.toString()); - return buf.toString(); - } - - @SuppressWarnings("unchecked") - /** - * Transforms a node into an object of the class defined by classProperty Property - */ - protected Object nodeToBean(Node node) throws RepositoryException { - if (log.isTraceEnabled()) - log.trace("Load " + node); - - try { - String clssName = node.getProperty(classProperty).getValue() - .getString(); - - BeanWrapper beanWrapper = createBeanWrapper(loadClass(clssName)); - - // process properties - PropertyIterator propIt = node.getProperties(); - props: while (propIt.hasNext()) { - Property prop = propIt.nextProperty(); - if (!beanWrapper.isWritableProperty(prop.getName())) - continue props; - - PropertyDescriptor pd = beanWrapper.getPropertyDescriptor(prop - .getName()); - Class propClass = pd.getPropertyType(); - - if (log.isTraceEnabled()) - log.trace("Load " + prop + ", propClass=" + propClass - + ", property descriptor=" + pd); - - // primitive list - if (propClass != null && List.class.isAssignableFrom(propClass)) { - List lst = new ArrayList(); - Class valuesClass = classFromProperty(prop); - if (valuesClass != null) - for (Value value : prop.getValues()) { - lst.add(asObject(value, valuesClass)); - } - continue props; - } - - // Case of other type of property accepted by jcr - // Long, Double, String, Binary, Date, Boolean, Name - Object value = asObject(prop.getValue(), pd.getPropertyType()); - if (value != null) - beanWrapper.setPropertyValue(prop.getName(), value); - } - - // process children nodes - NodeIterator nodeIt = node.getNodes(); - nodes: while (nodeIt.hasNext()) { - Node childNode = nodeIt.nextNode(); - String name = childNode.getName(); - if (!beanWrapper.isWritableProperty(name)) - continue nodes; - - PropertyDescriptor pd = beanWrapper.getPropertyDescriptor(name); - Class propClass = pd.getPropertyType(); - - // objects list - if (propClass != null && List.class.isAssignableFrom(propClass)) { - String lstClass = childNode.getProperty(classProperty) - .getString(); - List lst; - try { - lst = (List) loadClass(lstClass).newInstance(); - } catch (Exception e) { - lst = new ArrayList(); - } - - if (childNode.hasNodes()) { - // Look for children nodes - NodeIterator valuesIt = childNode.getNodes(); - while (valuesIt.hasNext()) { - Node lstValueNode = valuesIt.nextNode(); - Object lstValue = nodeToBean(lstValueNode); - lst.add(lstValue); - } - } else { - // look for a property with the same name which will - // provide - // primitives - Property childProp = childNode.getProperty(childNode - .getName()); - Class valuesClass = classFromProperty(childProp); - if (valuesClass != null) - if (childProp.getDefinition().isMultiple()) - for (Value value : childProp.getValues()) { - lst.add(asObject(value, valuesClass)); - } - else - lst.add(asObject(childProp.getValue(), - valuesClass)); - } - beanWrapper.setPropertyValue(name, lst); - continue nodes; - } - - // objects map - if (propClass != null && Map.class.isAssignableFrom(propClass)) { - String mapClass = childNode.getProperty(classProperty) - .getString(); - Map map; - try { - map = (Map) loadClass(mapClass) - .newInstance(); - } catch (Exception e) { - map = new HashMap(); - } - - // properties - PropertyIterator keysPropIt = childNode.getProperties(); - keyProps: while (keysPropIt.hasNext()) { - Property keyProp = keysPropIt.nextProperty(); - // FIXME: use property editor - String key = keyProp.getName(); - if (classProperty.equals(key)) - continue keyProps; - - Class keyPropClass = classFromProperty(keyProp); - if (keyPropClass != null) { - Object mapValue = asObject(keyProp.getValue(), - keyPropClass); - map.put(key, mapValue); - } - } - - // node - NodeIterator keysIt = childNode.getNodes(); - while (keysIt.hasNext()) { - Node mapValueNode = keysIt.nextNode(); - // FIXME: use property editor - Object key = mapValueNode.getName(); - - Object mapValue = nodeToBean(mapValueNode); - - map.put(key, mapValue); - } - beanWrapper.setPropertyValue(name, map); - continue nodes; - } - - // default - Object value = nodeToBean(childNode); - beanWrapper.setPropertyValue(name, value); - - } - return beanWrapper.getWrappedInstance(); - } catch (Exception e) { - throw new ArgeoException("Cannot map node " + node, e); - } - } - - /** - * Transforms an object to the specified jcr Node in order to persist it. - * - * @param beanWrapper - * @param node - * @throws RepositoryException - */ - protected void beanToNode(BeanWrapper beanWrapper, Node node) - throws RepositoryException { - properties: for (PropertyDescriptor pd : beanWrapper - .getPropertyDescriptors()) { - String name = pd.getName(); - if (!beanWrapper.isReadableProperty(name)) - continue properties;// skip - - Object value = beanWrapper.getPropertyValue(name); - if (value == null) { - // remove values when updating - if (node.hasProperty(name)) - node.setProperty(name, (Value) null); - if (node.hasNode(name)) - node.getNode(name).remove(); - - continue properties; - } - - // if (uuidProperty != null && uuidProperty.equals(name)) { - // // node.addMixin(ArgeoJcrConstants.MIX_REFERENCEABLE); - // node.setProperty(ArgeoJcrConstants.JCR_UUID, value.toString()); - // continue properties; - // } - - if ("class".equals(name)) { - if (classProperty != null) { - node.setProperty(classProperty, - ((Class) value).getName()); - // TODO: store a class hierarchy? - } - continue properties; - } - - // Some bean reference other classes. We must deal with this case - if (value instanceof Class) { - node.setProperty(name, ((Class) value).getName()); - continue properties; - } - - Value val = asValue(node.getSession(), value); - if (val != null) { - node.setProperty(name, val); - continue properties; - } - - if (value instanceof List) { - List lst = (List) value; - addList(node, name, lst); - continue properties; - } - - if (value instanceof Map) { - Map map = (Map) value; - addMap(node, name, map); - continue properties; - } - - BeanWrapper child = createBeanWrapper(value); - // TODO: delegate to another mapper - - // TODO: deal with references - // Node childNode = findChildReference(session, child); - // if (childNode != null) { - // node.setProperty(name, childNode); - // continue properties; - // } - - // default case (recursive) - if (node.hasNode(name)) {// update - // TODO: optimize - node.getNode(name).remove(); - } - Node childNode = node.addNode(name); - beanToNode(child, childNode); - } - } - - /** - * Process specific case of list - * - * @param node - * @param name - * @param lst - * @throws RepositoryException - */ - protected void addList(Node node, String name, List lst) - throws RepositoryException { - if (node.hasNode(name)) {// update - // TODO: optimize - node.getNode(name).remove(); - } - - Node listNode = node.addNode(name); - listNode.setProperty(classProperty, lst.getClass().getName()); - Value[] values = new Value[lst.size()]; - boolean atLeastOneSet = false; - for (int i = 0; i < lst.size(); i++) { - Object lstValue = lst.get(i); - values[i] = asValue(node.getSession(), lstValue); - if (values[i] != null) { - atLeastOneSet = true; - } else { - Node childNode = findChildReference(node.getSession(), - createBeanWrapper(lstValue)); - if (childNode != null) { - values[i] = node.getSession().getValueFactory() - .createValue(childNode); - atLeastOneSet = true; - } - } - } - - // will be either properties or nodes, not both - if (!atLeastOneSet && lst.size() != 0) { - for (Object lstValue : lst) { - Node childNode = listNode.addNode(NODE_VALUE); - beanToNode(createBeanWrapper(lstValue), childNode); - } - } else { - listNode.setProperty(name, values); - } - } - - /** - * Process specific case of maps. - * - * @param node - * @param name - * @param map - * @throws RepositoryException - */ - protected void addMap(Node node, String name, Map map) - throws RepositoryException { - if (node.hasNode(name)) {// update - // TODO: optimize - node.getNode(name).remove(); - } - - Node mapNode = node.addNode(name); - mapNode.setProperty(classProperty, map.getClass().getName()); - for (Object key : map.keySet()) { - Object mapValue = map.get(key); - // PropertyEditor pe = beanWrapper.findCustomEditor(key.getClass(), - // null); - String keyStr; - // if (pe == null) { - if (key instanceof CharSequence) - keyStr = key.toString(); - else - throw new ArgeoException( - "Cannot find property editor for class " - + key.getClass()); - // } else { - // pe.setValue(key); - // keyStr = pe.getAsText(); - // } - // TODO: check string format - - Value mapVal = asValue(node.getSession(), mapValue); - if (mapVal != null) - mapNode.setProperty(keyStr, mapVal); - else { - Node entryNode = mapNode.addNode(keyStr); - beanToNode(createBeanWrapper(mapValue), entryNode); - } - - } - - } - - protected BeanWrapper createBeanWrapper(Object obj) { - return new BeanWrapperImpl(obj); - } - - protected BeanWrapper createBeanWrapper(Class clss) { - return new BeanWrapperImpl(clss); - } - - /** Returns null if value cannot be found */ - protected Value asValue(Session session, Object value) - throws RepositoryException { - ValueFactory valueFactory = session.getValueFactory(); - if (value instanceof Integer) - return valueFactory.createValue((Integer) value); - else if (value instanceof Long) - return valueFactory.createValue((Long) value); - else if (value instanceof Float) - return valueFactory.createValue((Float) value); - else if (value instanceof Double) - return valueFactory.createValue((Double) value); - else if (value instanceof Boolean) - return valueFactory.createValue((Boolean) value); - else if (value instanceof Calendar) - return valueFactory.createValue((Calendar) value); - else if (value instanceof Date) { - Calendar cal = new GregorianCalendar(); - cal.setTime((Date) value); - return valueFactory.createValue(cal); - } else if (value instanceof CharSequence) - return valueFactory.createValue(value.toString()); - else if (value instanceof InputStream) { - Binary binary = session.getValueFactory().createBinary( - (InputStream) value); - return valueFactory.createValue(binary); - } else - return null; - } - - protected Class classFromProperty(Property property) - throws RepositoryException { - switch (property.getType()) { - case PropertyType.LONG: - return Long.class; - case PropertyType.DOUBLE: - return Double.class; - case PropertyType.STRING: - return String.class; - case PropertyType.BOOLEAN: - return Boolean.class; - case PropertyType.DATE: - return Calendar.class; - case PropertyType.NAME: - return null; - default: - throw new ArgeoException("Cannot find class for property " - + property + ", type=" - + PropertyType.nameFromValue(property.getType())); - } - } - - protected Object asObject(Value value, Class propClass) - throws RepositoryException { - if (propClass.equals(Integer.class)) - return (int) value.getLong(); - else if (propClass.equals(Long.class)) - return value.getLong(); - else if (propClass.equals(Float.class)) - return (float) value.getDouble(); - else if (propClass.equals(Double.class)) - return value.getDouble(); - else if (propClass.equals(Boolean.class)) - return value.getBoolean(); - else if (CharSequence.class.isAssignableFrom(propClass)) - return value.getString(); - else if (InputStream.class.isAssignableFrom(propClass)) - return value.getBinary().getStream(); - else if (Calendar.class.isAssignableFrom(propClass)) - return value.getDate(); - else if (Date.class.isAssignableFrom(propClass)) - return value.getDate().getTime(); - else - return null; - } - - protected Node findChildReference(Session session, BeanWrapper child) - throws RepositoryException { - if (child.isReadableProperty(uuidProperty)) { - String childUuid = child.getPropertyValue(uuidProperty).toString(); - try { - return session.getNodeByIdentifier(childUuid); - } catch (ItemNotFoundException e) { - if (strictUuidReference) - throw new ArgeoException("No node found with uuid " - + childUuid, e); - } - } - return null; - } - - protected Class loadClass(String name) { - // log.debug("Class loader: " + classLoader); - try { - return classLoader.loadClass(name); - } catch (ClassNotFoundException e) { - throw new ArgeoException("Cannot load class " + name, e); - } - } - - protected String propertyName(String name) { - return name; - } - - public void setVersioning(Boolean versioning) { - this.versioning = versioning; - } - - public void setUuidProperty(String uuidProperty) { - this.uuidProperty = uuidProperty; - } - - public void setClassProperty(String classProperty) { - this.classProperty = classProperty; - } - - public void setStrictUuidReference(Boolean strictUuidReference) { - this.strictUuidReference = strictUuidReference; - } - - public void setPrimaryNodeType(String primaryNodeType) { - this.primaryNodeType = primaryNodeType; - } - - public void setClassLoader(ClassLoader classLoader) { - this.classLoader = classLoader; - } - - public void setNodeMapperProvider(NodeMapperProvider nodeMapperProvider) { - this.nodeMapperProvider = nodeMapperProvider; - } - - public String getPrimaryNodeType() { - return this.primaryNodeType; - } - - public String getClassProperty() { - return this.classProperty; - } -} diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/spring/ThreadBoundSession.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/spring/ThreadBoundSession.java deleted file mode 100644 index a46bef1fe..000000000 --- a/org.argeo.server.jcr/newsrc/org/argeo/jcr/spring/ThreadBoundSession.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * 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.spring; - -import org.argeo.jcr.ThreadBoundJcrSessionFactory; -import org.springframework.beans.factory.DisposableBean; -import org.springframework.beans.factory.FactoryBean; -import org.springframework.beans.factory.InitializingBean; - -public class ThreadBoundSession extends ThreadBoundJcrSessionFactory implements FactoryBean, InitializingBean, DisposableBean{ - public void afterPropertiesSet() throws Exception { - init(); - } - - public void destroy() throws Exception { - dispose(); - } - -} diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/tabular/JcrTabularRowIterator.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/tabular/JcrTabularRowIterator.java deleted file mode 100644 index ca0635c95..000000000 --- a/org.argeo.server.jcr/newsrc/org/argeo/jcr/tabular/JcrTabularRowIterator.java +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * 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.tabular; - -import java.io.InputStream; -import java.util.ArrayList; -import java.util.List; -import java.util.concurrent.ArrayBlockingQueue; - -import javax.jcr.Binary; -import javax.jcr.Node; -import javax.jcr.NodeIterator; -import javax.jcr.Property; -import javax.jcr.PropertyType; -import javax.jcr.RepositoryException; - -import org.apache.commons.io.IOUtils; -import org.argeo.ArgeoException; -import org.argeo.jcr.ArgeoTypes; -import org.argeo.util.CsvParser; -import org.argeo.util.tabular.ArrayTabularRow; -import org.argeo.util.tabular.TabularColumn; -import org.argeo.util.tabular.TabularRow; -import org.argeo.util.tabular.TabularRowIterator; - -/** Iterates over the rows of a {@link ArgeoTypes#ARGEO_TABLE} node. */ -public class JcrTabularRowIterator implements TabularRowIterator { - private Boolean hasNext = null; - private Boolean parsingCompleted = false; - - private Long currentRowNumber = 0l; - - private List header = new ArrayList(); - - /** referenced so that we can close it */ - private Binary binary; - private InputStream in; - - private CsvParser csvParser; - private ArrayBlockingQueue> textLines; - - public JcrTabularRowIterator(Node tableNode) { - try { - for (NodeIterator it = tableNode.getNodes(); it.hasNext();) { - Node node = it.nextNode(); - if (node.isNodeType(ArgeoTypes.ARGEO_COLUMN)) { - Integer type = PropertyType.valueFromName(node.getProperty( - Property.JCR_REQUIRED_TYPE).getString()); - TabularColumn tc = new TabularColumn(node.getProperty( - Property.JCR_TITLE).getString(), type); - header.add(tc); - } - } - Node contentNode = tableNode.getNode(Property.JCR_CONTENT); - if (contentNode.isNodeType(ArgeoTypes.ARGEO_CSV)) { - textLines = new ArrayBlockingQueue>(1000); - csvParser = new CsvParser() { - protected void processLine(Integer lineNumber, - List header, List tokens) { - try { - textLines.put(tokens); - } catch (InterruptedException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - // textLines.add(tokens); - if (hasNext == null) { - hasNext = true; - synchronized (JcrTabularRowIterator.this) { - JcrTabularRowIterator.this.notifyAll(); - } - } - } - }; - csvParser.setNoHeader(true); - binary = contentNode.getProperty(Property.JCR_DATA).getBinary(); - in = binary.getStream(); - Thread thread = new Thread(contentNode.getPath() + " reader") { - public void run() { - try { - csvParser.parse(in); - } finally { - parsingCompleted = true; - IOUtils.closeQuietly(in); - } - } - }; - thread.start(); - } - } catch (RepositoryException e) { - throw new ArgeoException("Cannot read table " + tableNode, e); - } - } - - public synchronized boolean hasNext() { - // we don't know if there is anything available - // while (hasNext == null) - // try { - // wait(); - // } catch (InterruptedException e) { - // // silent - // // FIXME better deal with interruption - // Thread.currentThread().interrupt(); - // break; - // } - - // buffer not empty - if (!textLines.isEmpty()) - return true; - - // maybe the parsing is finished but the flag has not been set - while (!parsingCompleted && textLines.isEmpty()) - try { - wait(100); - } catch (InterruptedException e) { - // silent - // FIXME better deal with interruption - Thread.currentThread().interrupt(); - break; - } - - // buffer not empty - if (!textLines.isEmpty()) - return true; - - // (parsingCompleted && textLines.isEmpty()) - return false; - - // if (!hasNext && textLines.isEmpty()) { - // if (in != null) { - // IOUtils.closeQuietly(in); - // in = null; - // } - // if (binary != null) { - // JcrUtils.closeQuietly(binary); - // binary = null; - // } - // return false; - // } else - // return true; - } - - public synchronized TabularRow next() { - try { - List tokens = textLines.take(); - List objs = new ArrayList(tokens.size()); - for (String token : tokens) { - // TODO convert to other formats using header - objs.add(token); - } - currentRowNumber++; - return new ArrayTabularRow(objs); - } catch (InterruptedException e) { - // silent - // FIXME better deal with interruption - } - return null; - } - - public void remove() { - throw new UnsupportedOperationException(); - } - - public Long getCurrentRowNumber() { - return currentRowNumber; - } - - public List getHeader() { - return header; - } - -} diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/tabular/JcrTabularWriter.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/tabular/JcrTabularWriter.java deleted file mode 100644 index 718ff2374..000000000 --- a/org.argeo.server.jcr/newsrc/org/argeo/jcr/tabular/JcrTabularWriter.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * 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.tabular; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.InputStream; -import java.util.List; - -import javax.jcr.Binary; -import javax.jcr.Node; -import javax.jcr.Property; -import javax.jcr.PropertyType; -import javax.jcr.RepositoryException; - -import org.apache.commons.io.IOUtils; -import org.argeo.ArgeoException; -import org.argeo.jcr.ArgeoTypes; -import org.argeo.jcr.JcrUtils; -import org.argeo.util.CsvWriter; -import org.argeo.util.tabular.TabularColumn; -import org.argeo.util.tabular.TabularWriter; - -/** Write / reference tabular content in a JCR repository. */ -public class JcrTabularWriter implements TabularWriter { - private Node contentNode; - private ByteArrayOutputStream out; - private CsvWriter csvWriter; - - @SuppressWarnings("unused") - private final List columns; - - /** Creates a table node */ - public JcrTabularWriter(Node tableNode, List columns, - String contentNodeType) { - try { - this.columns = columns; - for (TabularColumn column : columns) { - String normalized = JcrUtils.replaceInvalidChars(column - .getName()); - Node columnNode = tableNode.addNode(normalized, - ArgeoTypes.ARGEO_COLUMN); - columnNode.setProperty(Property.JCR_TITLE, column.getName()); - if (column.getType() != null) - columnNode.setProperty(Property.JCR_REQUIRED_TYPE, - PropertyType.nameFromValue(column.getType())); - else - columnNode.setProperty(Property.JCR_REQUIRED_TYPE, - PropertyType.TYPENAME_STRING); - } - contentNode = tableNode.addNode(Property.JCR_CONTENT, - contentNodeType); - if (contentNodeType.equals(ArgeoTypes.ARGEO_CSV)) { - contentNode.setProperty(Property.JCR_MIMETYPE, "text/csv"); - contentNode.setProperty(Property.JCR_ENCODING, "UTF-8"); - out = new ByteArrayOutputStream(); - csvWriter = new CsvWriter(out); - } - } catch (RepositoryException e) { - throw new ArgeoException("Cannot create table node " + tableNode, e); - } - } - - public void appendRow(Object[] row) { - csvWriter.writeLine(row); - } - - public void close() { - Binary binary = null; - InputStream in = null; - try { - // TODO parallelize with pipes and writing from another thread - in = new ByteArrayInputStream(out.toByteArray()); - binary = contentNode.getSession().getValueFactory() - .createBinary(in); - contentNode.setProperty(Property.JCR_DATA, binary); - } catch (RepositoryException e) { - throw new ArgeoException("Cannot store data in " + contentNode, e); - } finally { - IOUtils.closeQuietly(in); - JcrUtils.closeQuietly(binary); - } - } -} diff --git a/org.argeo.server.jcr/newsrc/org/argeo/jcr/unit/AbstractJcrTestCase.java b/org.argeo.server.jcr/newsrc/org/argeo/jcr/unit/AbstractJcrTestCase.java deleted file mode 100644 index 530605eac..000000000 --- a/org.argeo.server.jcr/newsrc/org/argeo/jcr/unit/AbstractJcrTestCase.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (C) 2007-2012 Argeo GmbH - * - * 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.unit; - -import java.io.File; - -import javax.jcr.Repository; -import javax.jcr.Session; -import javax.jcr.SimpleCredentials; - -import junit.framework.TestCase; - -import org.apache.commons.io.FileUtils; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.argeo.ArgeoException; - -public abstract class AbstractJcrTestCase extends TestCase { - private final static Log log = LogFactory.getLog(AbstractJcrTestCase.class); - - private Repository repository; - private Session session = null; - - protected abstract File getRepositoryFile() throws Exception; - - protected abstract Repository createRepository() throws Exception; - - @Override - protected void setUp() throws Exception { - File homeDir = getHomeDir(); - FileUtils.deleteDirectory(homeDir); - repository = createRepository(); - } - - protected File getHomeDir() { - File homeDir = new File(System.getProperty("java.io.tmpdir"), - AbstractJcrTestCase.class.getSimpleName() + "-" - + System.getProperty("user.name")); - return homeDir; - } - - @Override - protected void tearDown() throws Exception { - if (session != null) { - session.logout(); - if (log.isDebugEnabled()) - log.debug("Logout session"); - } - } - - protected Session session() { - if (session == null) { - try { - if (log.isDebugEnabled()) - log.debug("Login session"); - session = getRepository().login( - new SimpleCredentials("demo", "demo".toCharArray())); - } catch (Exception e) { - throw new ArgeoException("Cannot login to repository", e); - } - } - return session; - } - - protected Repository getRepository() { - return repository; - } - - /** - * enables children class to set an existing repository in case it is not - * deleted on startup, to test migration by instance - */ - protected void setRepository(Repository repository) { - this.repository = repository; - } -} diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/ArgeoJcrConstants.java b/org.argeo.server.jcr/src/org/argeo/jcr/ArgeoJcrConstants.java new file mode 100644 index 000000000..b9b513a30 --- /dev/null +++ b/org.argeo.server.jcr/src/org/argeo/jcr/ArgeoJcrConstants.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * 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; + +/** Argeo model specific constants */ +public interface ArgeoJcrConstants { + public final static String ARGEO_BASE_PATH = "/argeo:system"; + public final static String DATA_MODELS_BASE_PATH = ARGEO_BASE_PATH + + "/argeo:dataModels"; + public final static String PEOPLE_BASE_PATH = ARGEO_BASE_PATH + + "/argeo:people"; + + // parameters (typically for call to a RepositoryFactory) + public final static String JCR_REPOSITORY_ALIAS = "argeo.jcr.repository.alias"; + public final static String JCR_REPOSITORY_URI = "argeo.jcr.repository.uri"; + + // standard aliases + public final static String ALIAS_NODE = "node"; + +} diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/ArgeoJcrUtils.java b/org.argeo.server.jcr/src/org/argeo/jcr/ArgeoJcrUtils.java new file mode 100644 index 000000000..dccb06cf4 --- /dev/null +++ b/org.argeo.server.jcr/src/org/argeo/jcr/ArgeoJcrUtils.java @@ -0,0 +1,80 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * 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 java.util.HashMap; +import java.util.Map; + +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.RepositoryFactory; + +import org.argeo.ArgeoException; + +/** Utilities related to Argeo model in JCR */ +public class ArgeoJcrUtils implements ArgeoJcrConstants { + /** + * Wraps the call to the repository factory based on parameter + * {@link ArgeoJcrConstants#JCR_REPOSITORY_ALIAS} in order to simplify it + * and protect against future API changes. + */ + public static Repository getRepositoryByAlias( + RepositoryFactory repositoryFactory, String alias) { + try { + Map parameters = new HashMap(); + parameters.put(JCR_REPOSITORY_ALIAS, alias); + return repositoryFactory.getRepository(parameters); + } catch (RepositoryException e) { + throw new ArgeoException( + "Unexpected exception when trying to retrieve repository with alias " + + alias, e); + } + } + + /** + * 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) { + 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( + "Unexpected exception when trying to retrieve repository with uri " + + uri, e); + } + } + + private ArgeoJcrUtils() { + } + +} diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/ArgeoNames.java b/org.argeo.server.jcr/src/org/argeo/jcr/ArgeoNames.java new file mode 100644 index 000000000..6e3eca999 --- /dev/null +++ b/org.argeo.server.jcr/src/org/argeo/jcr/ArgeoNames.java @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * 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; + +/** JCR names in the http://www.argeo.org/argeo namespace */ +public interface ArgeoNames { + public final static String ARGEO_NAMESPACE = "http://www.argeo.org/ns/argeo"; + public final static String ARGEO = "argeo"; + + public final static String ARGEO_URI = "argeo:uri"; + public final static String ARGEO_USER_ID = "argeo:userID"; + public final static String ARGEO_PREFERENCES = "argeo:preferences"; + public final static String ARGEO_DATA_MODEL_VERSION = "argeo:dataModelVersion"; + + public final static String ARGEO_REMOTE = "argeo:remote"; + public final static String ARGEO_PASSWORD = "argeo:password"; + public final static String ARGEO_REMOTE_ROLES = "argeo:remoteRoles"; + + // user profile + public final static String ARGEO_PROFILE = "argeo:profile"; + + // spring security + public final static String ARGEO_ENABLED = "argeo:enabled"; + public final static String ARGEO_ACCOUNT_NON_EXPIRED = "argeo:accountNonExpired"; + public final static String ARGEO_ACCOUNT_NON_LOCKED = "argeo:accountNonLocked"; + public final static String ARGEO_CREDENTIALS_NON_EXPIRED = "argeo:credentialsNonExpired"; + + // personal details + public final static String ARGEO_FIRST_NAME = "argeo:firstName"; + public final static String ARGEO_LAST_NAME = "argeo:lastName"; + public final static String ARGEO_PRIMARY_EMAIL = "argeo:primaryEmail"; + public final static String ARGEO_PRIMARY_ORGANIZATION = "argeo:primaryOrganization"; + + // tabular + public final static String ARGEO_IS_KEY = "argeo:isKey"; + + // crypto + public final static String ARGEO_IV = "argeo:iv"; + public final static String ARGEO_SECRET_KEY_FACTORY = "argeo:secretKeyFactory"; + public final static String ARGEO_SALT = "argeo:salt"; + public final static String ARGEO_ITERATION_COUNT = "argeo:iterationCount"; + public final static String ARGEO_KEY_LENGTH = "argeo:keyLength"; + public final static String ARGEO_SECRET_KEY_ENCRYPTION = "argeo:secretKeyEncryption"; + public final static String ARGEO_CIPHER = "argeo:cipher"; + public final static String ARGEO_KEYRING = "argeo:keyring"; +} diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/ArgeoTypes.java b/org.argeo.server.jcr/src/org/argeo/jcr/ArgeoTypes.java new file mode 100644 index 000000000..a11ead53f --- /dev/null +++ b/org.argeo.server.jcr/src/org/argeo/jcr/ArgeoTypes.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * 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; + +/** JCR types in the http://www.argeo.org/argeo namespace */ +public interface ArgeoTypes { + public final static String ARGEO_LINK = "argeo:link"; + public final static String ARGEO_USER_HOME = "argeo:userHome"; + public final static String ARGEO_USER_PROFILE = "argeo:userProfile"; + public final static String ARGEO_REMOTE_REPOSITORY = "argeo:remoteRepository"; + public final static String ARGEO_PREFERENCE_NODE = "argeo:preferenceNode"; + + // data model + public final static String ARGEO_DATA_MODEL = "argeo:dataModel"; + + // tabular + public final static String ARGEO_TABLE = "argeo:table"; + public final static String ARGEO_COLUMN = "argeo:column"; + public final static String ARGEO_CSV = "argeo:csv"; + + // crypto + public final static String ARGEO_ENCRYPTED = "argeo:encrypted"; + public final static String ARGEO_PBE_SPEC = "argeo:pbeSpec"; + +} diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/CollectionNodeIterator.java b/org.argeo.server.jcr/src/org/argeo/jcr/CollectionNodeIterator.java new file mode 100644 index 000000000..a65907a7a --- /dev/null +++ b/org.argeo.server.jcr/src/org/argeo/jcr/CollectionNodeIterator.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * 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 java.util.Collection; +import java.util.Iterator; +import java.util.NoSuchElementException; + +import javax.jcr.Node; +import javax.jcr.NodeIterator; + +/** Wraps a collection of nodes in order to read it as a {@link NodeIterator} */ +public class CollectionNodeIterator implements NodeIterator { + private final Long collectionSize; + private final Iterator iterator; + private Integer position = 0; + + public CollectionNodeIterator(Collection nodes) { + super(); + this.collectionSize = (long) nodes.size(); + this.iterator = nodes.iterator(); + } + + public void skip(long skipNum) { + if (skipNum < 0) + throw new IllegalArgumentException( + "Skip count has to be positive: " + skipNum); + + for (long i = 0; i < skipNum; i++) { + if (!hasNext()) + throw new NoSuchElementException("Last element past (position=" + + getPosition() + ")"); + nextNode(); + } + } + + public long getSize() { + return collectionSize; + } + + public long getPosition() { + return position; + } + + public boolean hasNext() { + return iterator.hasNext(); + } + + public Object next() { + return nextNode(); + } + + public void remove() { + iterator.remove(); + } + + public Node nextNode() { + Node node = iterator.next(); + position++; + return node; + } + +} diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/DefaultJcrListener.java b/org.argeo.server.jcr/src/org/argeo/jcr/DefaultJcrListener.java new file mode 100644 index 000000000..253b3055d --- /dev/null +++ b/org.argeo.server.jcr/src/org/argeo/jcr/DefaultJcrListener.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * 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.RepositoryException; +import javax.jcr.Session; +import javax.jcr.observation.Event; +import javax.jcr.observation.EventIterator; +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; + +/** To be overridden */ +public class DefaultJcrListener implements EventListener { + private final static Log log = LogFactory.getLog(DefaultJcrListener.class); + private Session session; + private String path = "/"; + private Boolean deep = true; + + public void start() { + try { + addEventListener(session().getWorkspace().getObservationManager()); + if (log.isDebugEnabled()) + log.debug("Registered JCR event listener on " + path); + } catch (Exception e) { + throw new ArgeoException("Cannot register event listener", e); + } + } + + public void stop() { + try { + session().getWorkspace().getObservationManager() + .removeEventListener(this); + if (log.isDebugEnabled()) + log.debug("Unregistered JCR event listener on " + path); + } catch (Exception e) { + throw new ArgeoException("Cannot unregister event listener", e); + } + } + + /** Default is listen to all events */ + protected Integer getEvents() { + return Event.NODE_ADDED | Event.NODE_REMOVED | Event.PROPERTY_ADDED + | Event.PROPERTY_CHANGED | Event.PROPERTY_REMOVED; + } + + /** To be overidden */ + public void onEvent(EventIterator events) { + while (events.hasNext()) { + Event event = events.nextEvent(); + log.debug(event); + } + } + + /** To be overidden */ + protected void addEventListener(ObservationManager observationManager) + throws RepositoryException { + observationManager.addEventListener(this, getEvents(), path, deep, + null, null, false); + } + + private Session session() { + return session; + } + + public void setPath(String path) { + this.path = path; + } + + public void setDeep(Boolean deep) { + this.deep = deep; + } + + public void setSession(Session session) { + this.session = session; + } + +} diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/DefaultRepositoryFactory.java b/org.argeo.server.jcr/src/org/argeo/jcr/DefaultRepositoryFactory.java new file mode 100644 index 000000000..a7b30e7be --- /dev/null +++ b/org.argeo.server.jcr/src/org/argeo/jcr/DefaultRepositoryFactory.java @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * 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 java.net.URI; +import java.net.URISyntaxException; +import java.util.Map; +import java.util.Properties; + +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.RepositoryFactory; + +import org.argeo.ArgeoException; + +/** + * Simple implementation of {@link RepositoryFactory}, supporting OSGi aliases. + */ +public class DefaultRepositoryFactory extends DefaultRepositoryRegister + implements RepositoryFactory, ArgeoJcrConstants { + @SuppressWarnings("rawtypes") + public Repository getRepository(Map parameters) throws RepositoryException { + if (parameters.containsKey(JCR_REPOSITORY_ALIAS)) { + String alias = parameters.get(JCR_REPOSITORY_ALIAS).toString(); + return getRepositoryByAlias(alias); + } else if (parameters.containsKey(JCR_REPOSITORY_URI)) { + String uri = parameters.get(JCR_REPOSITORY_URI).toString(); + return getRepositoryByAlias(getAliasFromURI(uri)); + } + return null; + } + + protected String getAliasFromURI(String uri) { + try { + URI uriObj = new URI(uri); + String alias = uriObj.getPath(); + if (alias.charAt(0) == '/') + alias = alias.substring(1); + if (alias.charAt(alias.length() - 1) == '/') + alias = alias.substring(0, alias.length() - 1); + return alias; + } catch (URISyntaxException e) { + throw new ArgeoException("Cannot interpret URI " + uri, e); + } + } + + /** + * Retrieve a repository by alias + * + * @return the repository registered with alias or null if none + */ + protected Repository getRepositoryByAlias(String alias) { + if (getRepositories().containsKey(alias)) + return getRepositories().get(alias); + else + return null; + } + + protected void publish(String alias, Repository repository, + Properties properties) { + register(repository, properties); + } + +} diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/DefaultRepositoryRegister.java b/org.argeo.server.jcr/src/org/argeo/jcr/DefaultRepositoryRegister.java new file mode 100644 index 000000000..f13c84e3b --- /dev/null +++ b/org.argeo.server.jcr/src/org/argeo/jcr/DefaultRepositoryRegister.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * 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 java.util.Collections; +import java.util.Map; +import java.util.Observable; +import java.util.TreeMap; + +import javax.jcr.Repository; +import javax.jcr.RepositoryException; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +public class DefaultRepositoryRegister extends Observable implements + RepositoryRegister, ArgeoJcrConstants { + private final static Log log = LogFactory + .getLog(DefaultRepositoryRegister.class); + + /** Read only map which will be directly exposed. */ + private Map repositories = Collections + .unmodifiableMap(new TreeMap()); + + @SuppressWarnings("rawtypes") + public synchronized Repository getRepository(Map parameters) + throws RepositoryException { + if (!parameters.containsKey(JCR_REPOSITORY_ALIAS)) + throw new RepositoryException("Parameter " + JCR_REPOSITORY_ALIAS + + " has to be defined."); + String alias = parameters.get(JCR_REPOSITORY_ALIAS).toString(); + if (!repositories.containsKey(alias)) + throw new RepositoryException( + "No repository registered with alias " + alias); + + return repositories.get(alias); + } + + /** Access to the read-only map */ + public synchronized Map getRepositories() { + return repositories; + } + + /** Registers a service, typically called when OSGi services are bound. */ + @SuppressWarnings("rawtypes") + public synchronized void register(Repository repository, Map properties) { + // TODO: also check bean name? + String alias; + if (properties == null || !properties.containsKey(JCR_REPOSITORY_ALIAS)) { + log.warn("Cannot register a repository if no " + + JCR_REPOSITORY_ALIAS + " property is speecified."); + return; + } + alias = properties.get(JCR_REPOSITORY_ALIAS).toString(); + Map map = new TreeMap( + repositories); + map.put(alias, repository); + repositories = Collections.unmodifiableMap(map); + setChanged(); + notifyObservers(alias); + } + + /** Unregisters a service, typically called when OSGi services are unbound. */ + @SuppressWarnings("rawtypes") + public synchronized void unregister(Repository repository, Map properties) { + // TODO: also check bean name? + if (properties == null || !properties.containsKey(JCR_REPOSITORY_ALIAS)) { + log.warn("Cannot unregister a repository without property " + + JCR_REPOSITORY_ALIAS); + return; + } + + String alias = properties.get(JCR_REPOSITORY_ALIAS).toString(); + Map map = new TreeMap( + repositories); + map.put(alias, repository); + repositories = Collections.unmodifiableMap(map); + setChanged(); + notifyObservers(alias); + } +} diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/JcrCallback.java b/org.argeo.server.jcr/src/org/argeo/jcr/JcrCallback.java new file mode 100644 index 000000000..0c4706f84 --- /dev/null +++ b/org.argeo.server.jcr/src/org/argeo/jcr/JcrCallback.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * 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.Session; + +/** An arbitrary execution on a JCR session, optionally returning a result. */ +public interface JcrCallback { + public Object execute(Session session); +} diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/JcrRepositoryWrapper.java b/org.argeo.server.jcr/src/org/argeo/jcr/JcrRepositoryWrapper.java new file mode 100644 index 000000000..f993c2f58 --- /dev/null +++ b/org.argeo.server.jcr/src/org/argeo/jcr/JcrRepositoryWrapper.java @@ -0,0 +1,152 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * 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/org.argeo.server.jcr/src/org/argeo/jcr/JcrResourceAdapter.java b/org.argeo.server.jcr/src/org/argeo/jcr/JcrResourceAdapter.java new file mode 100644 index 000000000..0b1a98cf0 --- /dev/null +++ b/org.argeo.server.jcr/src/org/argeo/jcr/JcrResourceAdapter.java @@ -0,0 +1,245 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * 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 java.io.InputStream; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.List; + +import javax.jcr.Binary; +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.nodetype.NodeType; +import javax.jcr.version.Version; +import javax.jcr.version.VersionHistory; +import javax.jcr.version.VersionIterator; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.ArgeoException; + +/** + * Bridge Spring resources and JCR folder / files semantics (nt:folder / + * nt:file), supporting versioning as well. + */ +public class JcrResourceAdapter { + private final static Log log = LogFactory.getLog(JcrResourceAdapter.class); + + private Session session; + + private Boolean versioning = true; + private String defaultEncoding = "UTF-8"; + + // private String restoreBase = "/.restore"; + + public JcrResourceAdapter() { + } + + public JcrResourceAdapter(Session session) { + this.session = session; + } + + public void mkdirs(String path) { + JcrUtils.mkdirs(session(), path, NodeType.NT_FOLDER, + NodeType.NT_FOLDER, versioning); + } + + public void create(String path, InputStream in, String mimeType) { + try { + if (session().itemExists(path)) { + throw new ArgeoException("Node " + path + " already exists."); + } + + int index = path.lastIndexOf('/'); + String parentPath = path.substring(0, index); + if (parentPath.equals("")) + parentPath = "/"; + String fileName = path.substring(index + 1); + if (!session().itemExists(parentPath)) + throw new ArgeoException("Parent folder of node " + path + + " does not exist: " + parentPath); + + Node folderNode = (Node) session().getItem(parentPath); + Node fileNode = folderNode.addNode(fileName, "nt:file"); + + Node contentNode = fileNode.addNode(Property.JCR_CONTENT, + "nt:resource"); + if (mimeType != null) + contentNode.setProperty(Property.JCR_MIMETYPE, mimeType); + contentNode.setProperty(Property.JCR_ENCODING, defaultEncoding); + Binary binary = session().getValueFactory().createBinary(in); + contentNode.setProperty(Property.JCR_DATA, binary); + JcrUtils.closeQuietly(binary); + Calendar lastModified = Calendar.getInstance(); + // lastModified.setTimeInMillis(file.lastModified()); + contentNode.setProperty(Property.JCR_LAST_MODIFIED, lastModified); + // resNode.addMixin("mix:referenceable"); + + if (versioning) + fileNode.addMixin("mix:versionable"); + + session().save(); + + if (versioning) + session().getWorkspace().getVersionManager() + .checkin(fileNode.getPath()); + + if (log.isDebugEnabled()) + log.debug("Created " + path); + } catch (Exception e) { + throw new ArgeoException("Cannot create node for " + path, e); + } + + } + + public void update(String path, InputStream in) { + try { + + if (!session().itemExists(path)) { + String type = null; + // FIXME: using javax.activation leads to conflict between Java + // 1.5 and 1.6 (since javax.activation was included in Java 1.6) + // String type = new MimetypesFileTypeMap() + // .getContentType(FilenameUtils.getName(path)); + create(path, in, type); + return; + } + + Node fileNode = (Node) session().getItem(path); + Node contentNode = fileNode.getNode(Property.JCR_CONTENT); + if (versioning) + session().getWorkspace().getVersionManager() + .checkout(fileNode.getPath()); + Binary binary = session().getValueFactory().createBinary(in); + contentNode.setProperty(Property.JCR_DATA, binary); + JcrUtils.closeQuietly(binary); + Calendar lastModified = Calendar.getInstance(); + // lastModified.setTimeInMillis(file.lastModified()); + contentNode.setProperty(Property.JCR_LAST_MODIFIED, lastModified); + + session().save(); + if (versioning) + session().getWorkspace().getVersionManager() + .checkin(fileNode.getPath()); + + if (log.isDebugEnabled()) + log.debug("Updated " + path); + } catch (Exception e) { + throw new ArgeoException("Cannot update node " + path, e); + } + } + + public List listVersions(String path) { + if (!versioning) + throw new ArgeoException("Versioning is not activated"); + + try { + List versions = new ArrayList(); + Node fileNode = (Node) session().getItem(path); + VersionHistory history = session().getWorkspace() + .getVersionManager().getVersionHistory(fileNode.getPath()); + for (VersionIterator it = history.getAllVersions(); it.hasNext();) { + Version version = (Version) it.next(); + versions.add(version.getCreated()); + if (log.isTraceEnabled()) { + log.debug(version); + // debug(version); + } + } + return versions; + } catch (Exception e) { + throw new ArgeoException("Cannot list version of node " + path, e); + } + } + + public InputStream retrieve(String path) { + try { + Node node = (Node) session().getItem( + path + "/" + Property.JCR_CONTENT); + Property property = node.getProperty(Property.JCR_DATA); + return property.getBinary().getStream(); + } catch (Exception e) { + throw new ArgeoException("Cannot retrieve " + path, e); + } + } + + public synchronized InputStream retrieve(String path, Integer revision) { + if (!versioning) + throw new ArgeoException("Versioning is not activated"); + + try { + Node fileNode = (Node) session().getItem(path); + VersionHistory history = session().getWorkspace() + .getVersionManager().getVersionHistory(fileNode.getPath()); + int count = 0; + Version version = null; + for (VersionIterator it = history.getAllVersions(); it.hasNext();) { + version = (Version) it.next(); + if (count == revision + 1) { + InputStream in = fromVersion(version); + if (log.isDebugEnabled()) + log.debug("Retrieved " + path + " at revision " + + revision); + return in; + } + count++; + } + } catch (Exception e) { + throw new ArgeoException("Cannot retrieve version " + revision + + " of " + path, e); + } + + throw new ArgeoException("Version " + revision + + " does not exist for node " + path); + } + + protected InputStream fromVersion(Version version) + throws RepositoryException { + Node frozenNode = version.getNode("jcr:frozenNode"); + InputStream in = frozenNode.getNode(Property.JCR_CONTENT) + .getProperty(Property.JCR_DATA).getBinary().getStream(); + return in; + } + + protected Session session() { + return session; + } + + public void setVersioning(Boolean versioning) { + this.versioning = versioning; + } + + public void setDefaultEncoding(String defaultEncoding) { + this.defaultEncoding = defaultEncoding; + } + + protected String fill(Integer number) { + int size = 4; + String str = number.toString(); + for (int i = str.length(); i < size; i++) { + str = "0" + str; + } + return str; + } + + public void setSession(Session session) { + this.session = session; + } + +} diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/JcrUrlStreamHandler.java b/org.argeo.server.jcr/src/org/argeo/jcr/JcrUrlStreamHandler.java new file mode 100644 index 000000000..a77763937 --- /dev/null +++ b/org.argeo.server.jcr/src/org/argeo/jcr/JcrUrlStreamHandler.java @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * 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 java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; + +import javax.jcr.Item; +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.nodetype.NodeType; + +/** URL stream handler able to deal with nt:file node and properties. NOT FINISHED */ +public class JcrUrlStreamHandler extends URLStreamHandler { + private final Session session; + + public JcrUrlStreamHandler(Session session) { + this.session = session; + } + + @Override + protected URLConnection openConnection(final URL u) throws IOException { + // TODO Auto-generated method stub + return new URLConnection(u) { + + @Override + public void connect() throws IOException { + String itemPath = u.getPath(); + try { + if (!session.itemExists(itemPath)) + throw new IOException("No item under " + itemPath); + + Item item = session.getItem(u.getPath()); + if (item.isNode()) { + // this should be a nt:file node + Node node = (Node) item; + if (!node.getPrimaryNodeType().isNodeType( + NodeType.NT_FILE)) + throw new IOException("Node " + node + " is not a " + + NodeType.NT_FILE); + + } else { + Property property = (Property) item; + if(property.getType()==PropertyType.BINARY){ + //Binary binary = property.getBinary(); + + } + } + } catch (RepositoryException e) { + IOException ioe = new IOException( + "Unexpected JCR exception"); + ioe.initCause(e); + throw ioe; + } + } + + @Override + public InputStream getInputStream() throws IOException { + // TODO Auto-generated method stub + return super.getInputStream(); + } + + }; + } + +} diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/JcrUtils.java b/org.argeo.server.jcr/src/org/argeo/jcr/JcrUtils.java new file mode 100644 index 000000000..2176e757c --- /dev/null +++ b/org.argeo.server.jcr/src/org/argeo/jcr/JcrUtils.java @@ -0,0 +1,1590 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * 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 java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.Principal; +import java.text.DateFormat; +import java.text.ParseException; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.TreeMap; + +import javax.jcr.Binary; +import javax.jcr.NamespaceRegistry; +import javax.jcr.NoSuchWorkspaceException; +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.Property; +import javax.jcr.PropertyIterator; +import javax.jcr.PropertyType; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.Value; +import javax.jcr.Workspace; +import javax.jcr.nodetype.NodeType; +import javax.jcr.observation.EventListener; +import javax.jcr.query.Query; +import javax.jcr.query.QueryResult; +import javax.jcr.security.AccessControlEntry; +import javax.jcr.security.AccessControlList; +import javax.jcr.security.AccessControlManager; +import javax.jcr.security.AccessControlPolicy; +import javax.jcr.security.AccessControlPolicyIterator; +import javax.jcr.security.Privilege; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.ArgeoException; +import org.argeo.ArgeoMonitor; +import org.argeo.util.security.DigestUtils; +import org.argeo.util.security.SimplePrincipal; + +/** Utility methods to simplify common JCR operations. */ +public class JcrUtils implements ArgeoJcrConstants { + + final private static Log log = LogFactory.getLog(JcrUtils.class); + + /** + * Not complete yet. See + * http://www.day.com/specs/jcr/2.0/3_Repository_Model.html#3.2.2%20Local + * %20Names + */ + public final static char[] INVALID_NAME_CHARACTERS = { '/', ':', '[', ']', + '|', '*', /* + * invalid XML chars : + */ + '<', '>', '&' }; + + /** Prevents instantiation */ + private JcrUtils() { + } + + /** + * Queries one single node. + * + * @return one single node or null if none was found + * @throws ArgeoException + * if more than one node was found + */ + public static Node querySingleNode(Query query) { + NodeIterator nodeIterator; + try { + QueryResult queryResult = query.execute(); + nodeIterator = queryResult.getNodes(); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot execute query " + query, e); + } + Node node; + if (nodeIterator.hasNext()) + node = nodeIterator.nextNode(); + else + return null; + + if (nodeIterator.hasNext()) + throw new ArgeoException("Query returned more than one node."); + return node; + } + + /** Retrieves the node name from the provided path */ + public static String nodeNameFromPath(String path) { + if (path.equals("/")) + return ""; + if (path.charAt(0) != '/') + throw new ArgeoException("Path " + path + " must start with a '/'"); + String pathT = path; + if (pathT.charAt(pathT.length() - 1) == '/') + pathT = pathT.substring(0, pathT.length() - 2); + + int index = pathT.lastIndexOf('/'); + return pathT.substring(index + 1); + } + + /** Retrieves the parent path of the provided path */ + public static String parentPath(String path) { + if (path.equals("/")) + throw new ArgeoException("Root path '/' has no parent path"); + if (path.charAt(0) != '/') + throw new ArgeoException("Path " + path + " must start with a '/'"); + String pathT = path; + if (pathT.charAt(pathT.length() - 1) == '/') + pathT = pathT.substring(0, pathT.length() - 2); + + int index = pathT.lastIndexOf('/'); + return pathT.substring(0, index); + } + + /** The provided data as a path ('/' at the end, not the beginning) */ + public static String dateAsPath(Calendar cal) { + return dateAsPath(cal, false); + } + + /** + * Creates a deep path based on a URL: + * http://subdomain.example.com/to/content?args => + * com/example/subdomain/to/content + */ + public static String urlAsPath(String url) { + try { + URL u = new URL(url); + StringBuffer path = new StringBuffer(url.length()); + // invert host + path.append(hostAsPath(u.getHost())); + // we don't put port since it may not always be there and may change + path.append(u.getPath()); + return path.toString(); + } catch (MalformedURLException e) { + throw new ArgeoException("Cannot generate URL path for " + url, e); + } + } + + /** Set the {@link NodeType#NT_ADDRESS} properties based on this URL. */ + public static void urlToAddressProperties(Node node, String url) { + try { + URL u = new URL(url); + node.setProperty(Property.JCR_PROTOCOL, u.getProtocol()); + node.setProperty(Property.JCR_HOST, u.getHost()); + node.setProperty(Property.JCR_PORT, Integer.toString(u.getPort())); + node.setProperty(Property.JCR_PATH, normalizePath(u.getPath())); + } catch (Exception e) { + throw new ArgeoException("Cannot set URL " + url + + " as nt:address properties", e); + } + } + + /** Build URL based on the {@link NodeType#NT_ADDRESS} properties. */ + public static String urlFromAddressProperties(Node node) { + try { + URL u = new URL( + node.getProperty(Property.JCR_PROTOCOL).getString(), node + .getProperty(Property.JCR_HOST).getString(), + (int) node.getProperty(Property.JCR_PORT).getLong(), node + .getProperty(Property.JCR_PATH).getString()); + return u.toString(); + } catch (Exception e) { + throw new ArgeoException( + "Cannot get URL from nt:address properties of " + node, e); + } + } + + /* + * PATH UTILITIES + */ + + /** Make sure that: starts with '/', do not end with '/', do not have '//' */ + public static String normalizePath(String path) { + List tokens = tokenize(path); + StringBuffer buf = new StringBuffer(path.length()); + for (String token : tokens) { + buf.append('/'); + buf.append(token); + } + return buf.toString(); + } + + /** + * Creates a path from a FQDN, inverting the order of the component: + * www.argeo.org => org.argeo.www + */ + public static String hostAsPath(String host) { + StringBuffer path = new StringBuffer(host.length()); + String[] hostTokens = host.split("\\."); + for (int i = hostTokens.length - 1; i >= 0; i--) { + path.append(hostTokens[i]); + if (i != 0) + path.append('/'); + } + return path.toString(); + } + + /** + * Creates a path from a UUID (e.g. 6ebda899-217d-4bf1-abe4-2839085c8f3c => + * 6ebda899-217d/4bf1/abe4/2839085c8f3c/). '/' at the end, not the beginning + */ + public static String uuidAsPath(String uuid) { + StringBuffer path = new StringBuffer(uuid.length()); + String[] tokens = uuid.split("-"); + for (int i = 0; i < tokens.length; i++) { + path.append(tokens[i]); + if (i != 0) + path.append('/'); + } + return path.toString(); + } + + /** + * The provided data as a path ('/' at the end, not the beginning) + * + * @param cal + * the date + * @param addHour + * whether to add hour as well + */ + public static String dateAsPath(Calendar cal, Boolean addHour) { + StringBuffer buf = new StringBuffer(14); + buf.append('Y'); + buf.append(cal.get(Calendar.YEAR)); + buf.append('/'); + + int month = cal.get(Calendar.MONTH) + 1; + buf.append('M'); + if (month < 10) + buf.append(0); + buf.append(month); + buf.append('/'); + + int day = cal.get(Calendar.DAY_OF_MONTH); + buf.append('D'); + if (day < 10) + buf.append(0); + buf.append(day); + buf.append('/'); + + if (addHour) { + int hour = cal.get(Calendar.HOUR_OF_DAY); + buf.append('H'); + if (hour < 10) + buf.append(0); + buf.append(hour); + buf.append('/'); + } + return buf.toString(); + + } + + /** Converts in one call a string into a gregorian calendar. */ + public static Calendar parseCalendar(DateFormat dateFormat, String value) { + try { + Date date = dateFormat.parse(value); + Calendar calendar = new GregorianCalendar(); + calendar.setTime(date); + return calendar; + } catch (ParseException e) { + throw new ArgeoException("Cannot parse " + value + + " with date format " + dateFormat, e); + } + + } + + /** The last element of a path. */ + public static String lastPathElement(String path) { + if (path.charAt(path.length() - 1) == '/') + throw new ArgeoException("Path " + path + " cannot end with '/'"); + int index = path.lastIndexOf('/'); + if (index < 0) + return path; + return path.substring(index + 1); + } + + /** + * Call {@link Node#getName()} without exceptions (useful in super + * constructors). + */ + public static String getNameQuietly(Node node) { + try { + return node.getName(); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot get name from " + node, e); + } + } + + /** + * Call {@link Node#getProperty(String)} without exceptions (useful in super + * constructors). + */ + public static String getStringPropertyQuietly(Node node, String propertyName) { + try { + return node.getProperty(propertyName).getString(); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot get name from " + node, e); + } + } + + /** + * Routine that get the child with this name, adding id it does not already + * exist + */ + public static Node getOrAdd(Node parent, String childName, + String childPrimaryNodeType) throws RepositoryException { + return parent.hasNode(childName) ? parent.getNode(childName) : parent + .addNode(childName, childPrimaryNodeType); + } + + /** + * Routine that get the child with this name, adding id it does not already + * exist + */ + public static Node getOrAdd(Node parent, String childName) + throws RepositoryException { + return parent.hasNode(childName) ? parent.getNode(childName) : parent + .addNode(childName); + } + + /** Convert a {@link NodeIterator} to a list of {@link Node} */ + public static List nodeIteratorToList(NodeIterator nodeIterator) { + List nodes = new ArrayList(); + while (nodeIterator.hasNext()) { + nodes.add(nodeIterator.nextNode()); + } + return nodes; + } + + /* + * PROPERTIES + */ + + /** + * Concisely get the string value of a property or null if this node doesn't + * have this property + */ + public static String get(Node node, String propertyName) { + try { + if (!node.hasProperty(propertyName)) + return null; + return node.getProperty(propertyName).getString(); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot get property " + propertyName + + " of " + node, e); + } + } + + /** Concisely get the boolean value of a property */ + public static Boolean check(Node node, String propertyName) { + try { + return node.getProperty(propertyName).getBoolean(); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot get property " + propertyName + + " of " + node, e); + } + } + + /** Concisely get the bytes array value of a property */ + public static byte[] getBytes(Node node, String propertyName) { + try { + return getBinaryAsBytes(node.getProperty(propertyName)); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot get property " + propertyName + + " of " + node, e); + } + } + + /** Creates the nodes making path, if they don't exist. */ + public static Node mkdirs(Session session, String path) { + return mkdirs(session, path, null, null, false); + } + + /** + * use {@link #mkdirs(Session, String, String, String, Boolean)} instead. + * + * @deprecated + */ + @Deprecated + public static Node mkdirs(Session session, String path, String type, + Boolean versioning) { + return mkdirs(session, path, type, type, false); + } + + /** + * @param type + * the type of the leaf node + */ + public static Node mkdirs(Session session, String path, String type) { + return mkdirs(session, path, type, null, false); + } + + /** + * Create sub nodes relative to a parent node + * + * @param nodeType + * the type of the leaf node + */ + public static Node mkdirs(Node parentNode, String relativePath, + String nodeType) { + return mkdirs(parentNode, relativePath, nodeType, null); + } + + /** + * Create sub nodes relative to a parent node + * + * @param nodeType + * the type of the leaf node + */ + public static Node mkdirs(Node parentNode, String relativePath, + String nodeType, String intermediaryNodeType) { + List tokens = tokenize(relativePath); + Node currParent = parentNode; + try { + for (int i = 0; i < tokens.size(); i++) { + String name = tokens.get(i); + if (currParent.hasNode(name)) { + currParent = currParent.getNode(name); + } else { + if (i != (tokens.size() - 1)) {// intermediary + currParent = currParent.addNode(name, + intermediaryNodeType); + } else {// leaf + currParent = currParent.addNode(name, nodeType); + } + } + } + return currParent; + } catch (RepositoryException e) { + throw new ArgeoException("Cannot mkdirs relative path " + + relativePath + " from " + parentNode, e); + } + } + + /** + * Synchronized and save is performed, to avoid race conditions in + * initializers leading to duplicate nodes. + */ + public synchronized static Node mkdirsSafe(Session session, String path, + String type) { + try { + if (session.hasPendingChanges()) + throw new ArgeoException( + "Session has pending changes, save them first."); + Node node = mkdirs(session, path, type); + session.save(); + return node; + } catch (RepositoryException e) { + discardQuietly(session); + throw new ArgeoException("Cannot safely make directories", e); + } + } + + public synchronized static Node mkdirsSafe(Session session, String path) { + return mkdirsSafe(session, path, null); + } + + /** + * Creates the nodes making path, if they don't exist. This is up to the + * caller to save the session. Use with caution since it can create + * duplicate nodes if used concurrently. + */ + public static Node mkdirs(Session session, String path, String type, + String intermediaryNodeType, Boolean versioning) { + try { + if (path.equals('/')) + return session.getRootNode(); + + if (session.itemExists(path)) { + Node node = session.getNode(path); + // check type + if (type != null && !node.isNodeType(type) + && !node.getPath().equals("/")) + throw new ArgeoException("Node " + node + + " exists but is of type " + + node.getPrimaryNodeType().getName() + + " not of type " + type); + // TODO: check versioning + return node; + } + + StringBuffer current = new StringBuffer("/"); + Node currentNode = session.getRootNode(); + Iterator it = tokenize(path).iterator(); + while (it.hasNext()) { + String part = it.next(); + current.append(part).append('/'); + if (!session.itemExists(current.toString())) { + if (!it.hasNext() && type != null) + currentNode = currentNode.addNode(part, type); + else if (it.hasNext() && intermediaryNodeType != null) + currentNode = currentNode.addNode(part, + intermediaryNodeType); + else + currentNode = currentNode.addNode(part); + if (versioning) + currentNode.addMixin(NodeType.MIX_VERSIONABLE); + if (log.isTraceEnabled()) + log.debug("Added folder " + part + " as " + current); + } else { + currentNode = (Node) session.getItem(current.toString()); + } + } + return currentNode; + } catch (RepositoryException e) { + discardQuietly(session); + throw new ArgeoException("Cannot mkdirs " + path, e); + } finally { + } + } + + /** Convert a path to the list of its tokens */ + public static List tokenize(String path) { + List tokens = new ArrayList(); + boolean optimized = false; + if (!optimized) { + String[] rawTokens = path.split("/"); + for (String token : rawTokens) { + if (!token.equals("")) + tokens.add(token); + } + } else { + StringBuffer curr = new StringBuffer(); + char[] arr = path.toCharArray(); + chars: for (int i = 0; i < arr.length; i++) { + char c = arr[i]; + if (c == '/') { + if (i == 0 || (i == arr.length - 1)) + continue chars; + if (curr.length() > 0) { + tokens.add(curr.toString()); + curr = new StringBuffer(); + } + } else + curr.append(c); + } + if (curr.length() > 0) { + tokens.add(curr.toString()); + curr = new StringBuffer(); + } + } + return Collections.unmodifiableList(tokens); + } + + /** + * Safe and repository implementation independent registration of a + * namespace. + */ + public static void registerNamespaceSafely(Session session, String prefix, + String uri) { + try { + registerNamespaceSafely(session.getWorkspace() + .getNamespaceRegistry(), prefix, uri); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot find namespace registry", e); + } + } + + /** + * Safe and repository implementation independent registration of a + * namespace. + */ + public static void registerNamespaceSafely(NamespaceRegistry nr, + String prefix, String uri) { + try { + String[] prefixes = nr.getPrefixes(); + for (String pref : prefixes) + if (pref.equals(prefix)) { + String registeredUri = nr.getURI(pref); + if (!registeredUri.equals(uri)) + throw new ArgeoException("Prefix " + pref + + " already registered for URI " + + registeredUri + + " which is different from provided URI " + + uri); + else + return;// skip + } + nr.registerNamespace(prefix, uri); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot register namespace " + uri + + " under prefix " + prefix, e); + } + } + + /** Recursively outputs the contents of the given node. */ + public static void debug(Node node) { + debug(node, log); + } + + /** Recursively outputs the contents of the given node. */ + public static void debug(Node node, Log log) { + try { + // First output the node path + log.debug(node.getPath()); + // Skip the virtual (and large!) jcr:system subtree + if (node.getName().equals("jcr:system")) { + return; + } + + // Then the children nodes (recursive) + NodeIterator it = node.getNodes(); + while (it.hasNext()) { + Node childNode = it.nextNode(); + debug(childNode, log); + } + + // Then output the properties + PropertyIterator properties = node.getProperties(); + // log.debug("Property are : "); + + properties: while (properties.hasNext()) { + Property property = properties.nextProperty(); + if (property.getType() == PropertyType.BINARY) + continue properties;// skip + if (property.getDefinition().isMultiple()) { + // A multi-valued property, print all values + Value[] values = property.getValues(); + for (int i = 0; i < values.length; i++) { + log.debug(property.getPath() + "=" + + values[i].getString()); + } + } else { + // A single-valued property + log.debug(property.getPath() + "=" + property.getString()); + } + } + } catch (Exception e) { + log.error("Could not debug " + node, e); + } + + } + + /** Logs the effective access control policies */ + public static void logEffectiveAccessPolicies(Node node) { + try { + logEffectiveAccessPolicies(node.getSession(), node.getPath()); + } catch (RepositoryException e) { + log.error("Cannot log effective access policies of " + node, e); + } + } + + /** Logs the effective access control policies */ + public static void logEffectiveAccessPolicies(Session session, String path) { + if (!log.isDebugEnabled()) + return; + + try { + AccessControlPolicy[] effectivePolicies = session + .getAccessControlManager().getEffectivePolicies(path); + if (effectivePolicies.length > 0) { + for (AccessControlPolicy policy : effectivePolicies) { + if (policy instanceof AccessControlList) { + AccessControlList acl = (AccessControlList) policy; + log.debug("Access control list for " + path + "\n" + + accessControlListSummary(acl)); + } + } + } else { + log.debug("No effective access control policy for " + path); + } + } catch (RepositoryException e) { + log.error("Cannot log effective access policies of " + path, e); + } + } + + /** Returns a human-readable summary of this access control list. */ + public static String accessControlListSummary(AccessControlList acl) { + StringBuffer buf = new StringBuffer(""); + try { + for (AccessControlEntry ace : acl.getAccessControlEntries()) { + buf.append('\t').append(ace.getPrincipal().getName()) + .append('\n'); + for (Privilege priv : ace.getPrivileges()) + buf.append("\t\t").append(priv.getName()).append('\n'); + } + return buf.toString(); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot write summary of " + acl, e); + } + } + + /** + * Copies recursively the content of a node to another one. Do NOT copy the + * property values of {@link NodeType#MIX_CREATED} and + * {@link NodeType#MIX_LAST_MODIFIED}, but update the + * {@link Property#JCR_LAST_MODIFIED} and + * {@link Property#JCR_LAST_MODIFIED_BY} properties if the target node has + * the {@link NodeType#MIX_LAST_MODIFIED} mixin. + */ + public static void copy(Node fromNode, Node toNode) { + try { + if (toNode.getDefinition().isProtected()) + return; + + // process properties + PropertyIterator pit = fromNode.getProperties(); + properties: while (pit.hasNext()) { + Property fromProperty = pit.nextProperty(); + String propertyName = fromProperty.getName(); + if (toNode.hasProperty(propertyName) + && toNode.getProperty(propertyName).getDefinition() + .isProtected()) + continue properties; + + if (fromProperty.getDefinition().isProtected()) + continue properties; + + if (propertyName.equals("jcr:created") + || propertyName.equals("jcr:createdBy") + || propertyName.equals("jcr:lastModified") + || propertyName.equals("jcr:lastModifiedBy")) + continue properties; + + if (fromProperty.isMultiple()) { + toNode.setProperty(propertyName, fromProperty.getValues()); + } else { + toNode.setProperty(propertyName, fromProperty.getValue()); + } + } + + // update jcr:lastModified and jcr:lastModifiedBy in toNode in case + // they existed, before adding the mixins + updateLastModified(toNode); + + // add mixins + for (NodeType mixinType : fromNode.getMixinNodeTypes()) { + toNode.addMixin(mixinType.getName()); + } + + // process children nodes + NodeIterator nit = fromNode.getNodes(); + while (nit.hasNext()) { + Node fromChild = nit.nextNode(); + Integer index = fromChild.getIndex(); + String nodeRelPath = fromChild.getName() + "[" + index + "]"; + Node toChild; + if (toNode.hasNode(nodeRelPath)) + toChild = toNode.getNode(nodeRelPath); + else + toChild = toNode.addNode(fromChild.getName(), fromChild + .getPrimaryNodeType().getName()); + copy(fromChild, toChild); + } + } catch (RepositoryException e) { + throw new ArgeoException("Cannot copy " + fromNode + " to " + + toNode, e); + } + } + + /** + * Check whether all first-level properties (except jcr:* properties) are + * equal. Skip jcr:* properties + */ + public static Boolean allPropertiesEquals(Node reference, Node observed, + Boolean onlyCommonProperties) { + try { + PropertyIterator pit = reference.getProperties(); + props: while (pit.hasNext()) { + Property propReference = pit.nextProperty(); + String propName = propReference.getName(); + if (propName.startsWith("jcr:")) + continue props; + + if (!observed.hasProperty(propName)) + if (onlyCommonProperties) + continue props; + else + return false; + // TODO: deal with multiple property values? + if (!observed.getProperty(propName).getValue() + .equals(propReference.getValue())) + return false; + } + return true; + } catch (RepositoryException e) { + throw new ArgeoException("Cannot check all properties equals of " + + reference + " and " + observed, e); + } + } + + public static Map diffProperties(Node reference, + Node observed) { + Map diffs = new TreeMap(); + diffPropertiesLevel(diffs, null, reference, observed); + return diffs; + } + + /** + * Compare the properties of two nodes. Recursivity to child nodes is not + * yet supported. Skip jcr:* properties. + */ + static void diffPropertiesLevel(Map diffs, + String baseRelPath, Node reference, Node observed) { + try { + // check removed and modified + PropertyIterator pit = reference.getProperties(); + props: while (pit.hasNext()) { + Property p = pit.nextProperty(); + String name = p.getName(); + if (name.startsWith("jcr:")) + continue props; + + if (!observed.hasProperty(name)) { + String relPath = propertyRelPath(baseRelPath, name); + PropertyDiff pDiff = new PropertyDiff(PropertyDiff.REMOVED, + relPath, p.getValue(), null); + diffs.put(relPath, pDiff); + } else { + if (p.isMultiple()) { + // FIXME implement multiple + } else { + Value referenceValue = p.getValue(); + Value newValue = observed.getProperty(name).getValue(); + if (!referenceValue.equals(newValue)) { + String relPath = propertyRelPath(baseRelPath, name); + PropertyDiff pDiff = new PropertyDiff( + PropertyDiff.MODIFIED, relPath, + referenceValue, newValue); + diffs.put(relPath, pDiff); + } + } + } + } + // check added + pit = observed.getProperties(); + props: while (pit.hasNext()) { + Property p = pit.nextProperty(); + String name = p.getName(); + if (name.startsWith("jcr:")) + continue props; + if (!reference.hasProperty(name)) { + if (p.isMultiple()) { + // FIXME implement multiple + } else { + String relPath = propertyRelPath(baseRelPath, name); + PropertyDiff pDiff = new PropertyDiff( + PropertyDiff.ADDED, relPath, null, p.getValue()); + diffs.put(relPath, pDiff); + } + } + } + } catch (RepositoryException e) { + throw new ArgeoException("Cannot diff " + reference + " and " + + observed, e); + } + } + + /** + * Compare only a restricted list of properties of two nodes. No + * recursivity. + * + */ + public static Map diffProperties(Node reference, + Node observed, List properties) { + Map diffs = new TreeMap(); + try { + Iterator pit = properties.iterator(); + + props: while (pit.hasNext()) { + String name = pit.next(); + if (!reference.hasProperty(name)) { + if (!observed.hasProperty(name)) + continue props; + Value val = observed.getProperty(name).getValue(); + try { + // empty String but not null + if ("".equals(val.getString())) + continue props; + } catch (Exception e) { + // not parseable as String, silent + } + PropertyDiff pDiff = new PropertyDiff(PropertyDiff.ADDED, + name, null, val); + diffs.put(name, pDiff); + } else if (!observed.hasProperty(name)) { + PropertyDiff pDiff = new PropertyDiff(PropertyDiff.REMOVED, + name, reference.getProperty(name).getValue(), null); + diffs.put(name, pDiff); + } else { + Value referenceValue = reference.getProperty(name) + .getValue(); + Value newValue = observed.getProperty(name).getValue(); + if (!referenceValue.equals(newValue)) { + PropertyDiff pDiff = new PropertyDiff( + PropertyDiff.MODIFIED, name, referenceValue, + newValue); + diffs.put(name, pDiff); + } + } + } + } catch (RepositoryException e) { + throw new ArgeoException("Cannot diff " + reference + " and " + + observed, e); + } + return diffs; + } + + /** Builds a property relPath to be used in the diff. */ + private static String propertyRelPath(String baseRelPath, + String propertyName) { + if (baseRelPath == null) + return propertyName; + else + return baseRelPath + '/' + propertyName; + } + + /** + * Normalizes a name so that it can be stored in contexts not supporting + * names with ':' (typically databases). Replaces ':' by '_'. + */ + public static String normalize(String name) { + return name.replace(':', '_'); + } + + /** + * Replaces characters which are invalid in a JCR name by '_'. Currently not + * exhaustive. + * + * @see JcrUtils#INVALID_NAME_CHARACTERS + */ + public static String replaceInvalidChars(String name) { + return replaceInvalidChars(name, '_'); + } + + /** + * Replaces characters which are invalid in a JCR name. Currently not + * exhaustive. + * + * @see JcrUtils#INVALID_NAME_CHARACTERS + */ + public static String replaceInvalidChars(String name, char replacement) { + boolean modified = false; + char[] arr = name.toCharArray(); + for (int i = 0; i < arr.length; i++) { + char c = arr[i]; + invalid: for (char invalid : INVALID_NAME_CHARACTERS) { + if (c == invalid) { + arr[i] = replacement; + modified = true; + break invalid; + } + } + } + if (modified) + return new String(arr); + else + // do not create new object if unnecessary + return name; + } + + /** + * Removes forbidden characters from a path, replacing them with '_' + * + * @deprecated use {@link #replaceInvalidChars(String)} instead + */ + public static String removeForbiddenCharacters(String str) { + return str.replace('[', '_').replace(']', '_').replace('/', '_') + .replace('*', '_'); + + } + + /** Cleanly disposes a {@link Binary} even if it is null. */ + public static void closeQuietly(Binary binary) { + if (binary == null) + return; + binary.dispose(); + } + + /** Retrieve a {@link Binary} as a byte array */ + public static byte[] getBinaryAsBytes(Property property) { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + InputStream in = null; + Binary binary = null; + try { + binary = property.getBinary(); + in = binary.getStream(); + IOUtils.copy(in, out); + return out.toByteArray(); + } catch (Exception e) { + throw new ArgeoException("Cannot read binary " + property + + " as bytes", e); + } finally { + IOUtils.closeQuietly(out); + IOUtils.closeQuietly(in); + closeQuietly(binary); + } + } + + /** Writes a {@link Binary} from a byte array */ + public static void setBinaryAsBytes(Node node, String property, byte[] bytes) { + InputStream in = null; + Binary binary = null; + try { + in = new ByteArrayInputStream(bytes); + binary = node.getSession().getValueFactory().createBinary(in); + node.setProperty(property, binary); + } catch (Exception e) { + throw new ArgeoException("Cannot read binary " + property + + " as bytes", e); + } finally { + IOUtils.closeQuietly(in); + closeQuietly(binary); + } + } + + /** + * Creates depth from a string (typically a username) by adding levels based + * on its first characters: "aBcD",2 => a/aB + */ + public static String firstCharsToPath(String str, Integer nbrOfChars) { + if (str.length() < nbrOfChars) + throw new ArgeoException("String " + str + + " length must be greater or equal than " + nbrOfChars); + StringBuffer path = new StringBuffer(""); + StringBuffer curr = new StringBuffer(""); + for (int i = 0; i < nbrOfChars; i++) { + curr.append(str.charAt(i)); + path.append(curr); + if (i < nbrOfChars - 1) + path.append('/'); + } + return path.toString(); + } + + /** + * Discards the current changes in the session attached to this node. To be + * used typically in a catch block. + * + * @see #discardQuietly(Session) + */ + public static void discardUnderlyingSessionQuietly(Node node) { + try { + discardQuietly(node.getSession()); + } catch (RepositoryException e) { + log.warn("Cannot quietly discard session of node " + node + ": " + + e.getMessage()); + } + } + + /** + * Discards the current changes in a session by calling + * {@link Session#refresh(boolean)} with false, only logging + * potential errors when doing so. To be used typically in a catch block. + */ + public static void discardQuietly(Session session) { + try { + if (session != null) + session.refresh(false); + } catch (RepositoryException e) { + log.warn("Cannot quietly discard session " + session + ": " + + e.getMessage()); + } + } + + /** + * Login to a workspace with implicit credentials, creates the workspace + * with these credentials if it does not already exist. + */ + public static Session loginOrCreateWorkspace(Repository repository, + String workspaceName) throws RepositoryException { + Session workspaceSession = null; + Session defaultSession = null; + try { + try { + workspaceSession = repository.login(workspaceName); + } catch (NoSuchWorkspaceException e) { + // try to create workspace + defaultSession = repository.login(); + defaultSession.getWorkspace().createWorkspace(workspaceName); + workspaceSession = repository.login(workspaceName); + } + return workspaceSession; + } finally { + logoutQuietly(defaultSession); + } + } + + /** Logs out the session, not throwing any exception, even if it is null. */ + public static void logoutQuietly(Session session) { + try { + if (session != null) + if (session.isLive()) + session.logout(); + } catch (Exception e) { + // silent + } + } + + /** + * Convenient method to add a listener. uuids passed as null, deep=true, + * local=true, only one node type + */ + public static void addListener(Session session, EventListener listener, + int eventTypes, String basePath, String nodeType) { + try { + session.getWorkspace() + .getObservationManager() + .addEventListener( + listener, + eventTypes, + basePath, + true, + null, + nodeType == null ? null : new String[] { nodeType }, + true); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot add JCR listener " + listener + + " to session " + session, e); + } + } + + /** Removes a listener without throwing exception */ + public static void removeListenerQuietly(Session session, + EventListener listener) { + if (session == null || !session.isLive()) + return; + try { + session.getWorkspace().getObservationManager() + .removeEventListener(listener); + } catch (RepositoryException e) { + // silent + } + } + + /** + * Quietly unregisters an {@link EventListener} from the udnerlying + * workspace of this node. + */ + public static void unregisterQuietly(Node node, EventListener eventListener) { + try { + unregisterQuietly(node.getSession().getWorkspace(), eventListener); + } catch (RepositoryException e) { + // silent + if (log.isTraceEnabled()) + log.trace("Could not unregister event listener " + + eventListener); + } + } + + /** Quietly unregisters an {@link EventListener} from this workspace */ + public static void unregisterQuietly(Workspace workspace, + EventListener eventListener) { + if (eventListener == null) + return; + try { + workspace.getObservationManager() + .removeEventListener(eventListener); + } catch (RepositoryException e) { + // silent + if (log.isTraceEnabled()) + log.trace("Could not unregister event listener " + + eventListener); + } + } + + /** + * If this node is has the {@link NodeType#MIX_LAST_MODIFIED} mixin, it + * updates the {@link Property#JCR_LAST_MODIFIED} property with the current + * time and the {@link Property#JCR_LAST_MODIFIED_BY} property with the + * underlying session user id. In Jackrabbit 2.x, these properties + * are not automatically updated, hence the need for manual update. The + * session is not saved. + */ + public static void updateLastModified(Node node) { + try { + if (!node.isNodeType(NodeType.MIX_LAST_MODIFIED)) + node.addMixin(NodeType.MIX_LAST_MODIFIED); + node.setProperty(Property.JCR_LAST_MODIFIED, + new GregorianCalendar()); + node.setProperty(Property.JCR_LAST_MODIFIED_BY, node.getSession() + .getUserID()); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot update last modified on " + node, + e); + } + } + + /** + * Update lastModified recursively until this parent. + * + * @param node + * the node + * @param untilPath + * the base path, null is equivalent to "/" + */ + public static void updateLastModifiedAndParents(Node node, String untilPath) { + try { + if (untilPath != null && !node.getPath().startsWith(untilPath)) + throw new ArgeoException(node + " is not under " + untilPath); + updateLastModified(node); + if (untilPath == null) { + if (!node.getPath().equals("/")) + updateLastModifiedAndParents(node.getParent(), untilPath); + } else { + if (!node.getPath().equals(untilPath)) + updateLastModifiedAndParents(node.getParent(), untilPath); + } + } catch (RepositoryException e) { + throw new ArgeoException("Cannot update lastModified from " + node + + " until " + untilPath, e); + } + } + + /** + * Returns a String representing the short version (see Node type + * Notation attributes grammar) of the main business attributes of this + * property definition + * + * @param prop + */ + public static String getPropertyDefinitionAsString(Property prop) { + StringBuffer sbuf = new StringBuffer(); + try { + if (prop.getDefinition().isAutoCreated()) + sbuf.append("a"); + if (prop.getDefinition().isMandatory()) + sbuf.append("m"); + if (prop.getDefinition().isProtected()) + sbuf.append("p"); + if (prop.getDefinition().isMultiple()) + sbuf.append("*"); + } catch (RepositoryException re) { + throw new ArgeoException( + "unexpected error while getting property definition as String", + re); + } + return sbuf.toString(); + } + + /** + * Estimate the sub tree size from current node. Computation is based on the + * Jcr {@link Property.getLength()} method. Note : it is not the exact size + * used on the disk by the current part of the JCR Tree. + */ + + public static long getNodeApproxSize(Node node) { + long curNodeSize = 0; + try { + PropertyIterator pi = node.getProperties(); + while (pi.hasNext()) { + Property prop = pi.nextProperty(); + if (prop.isMultiple()) { + int nb = prop.getLengths().length; + for (int i = 0; i < nb; i++) { + curNodeSize += (prop.getLengths()[i] > 0 ? prop + .getLengths()[i] : 0); + } + } else + curNodeSize += (prop.getLength() > 0 ? prop.getLength() : 0); + } + + NodeIterator ni = node.getNodes(); + while (ni.hasNext()) + curNodeSize += getNodeApproxSize(ni.nextNode()); + return curNodeSize; + } catch (RepositoryException re) { + throw new ArgeoException( + "Unexpected error while recursively determining node size.", + re); + } + } + + /* + * SECURITY + */ + + /** + * Convenience method for adding a single privilege to a principal (user or + * role), typically jcr:all + */ + public synchronized static void addPrivilege(Session session, String path, + String principal, String privilege) throws RepositoryException { + List privileges = new ArrayList(); + privileges.add(session.getAccessControlManager().privilegeFromName( + privilege)); + addPrivileges(session, path, new SimplePrincipal(principal), privileges); + } + + /** + * Add privileges on a path to a {@link Principal}. The path must already + * exist. Session is saved. Synchronized to prevent concurrent modifications + * of the same node. + */ + public synchronized static Boolean addPrivileges(Session session, + String path, Principal principal, List privs) + throws RepositoryException { + // make sure the session is in line with the persisted state + session.refresh(false); + AccessControlManager acm = session.getAccessControlManager(); + AccessControlList acl = getAccessControlList(acm, path); + + accessControlEntries: for (AccessControlEntry ace : acl + .getAccessControlEntries()) { + Principal currentPrincipal = ace.getPrincipal(); + if (currentPrincipal.getName().equals(principal.getName())) { + Privilege[] currentPrivileges = ace.getPrivileges(); + if (currentPrivileges.length != privs.size()) + break accessControlEntries; + for (int i = 0; i < currentPrivileges.length; i++) { + Privilege currP = currentPrivileges[i]; + Privilege p = privs.get(i); + if (!currP.getName().equals(p.getName())) { + break accessControlEntries; + } + } + return false; + } + } + + Privilege[] privileges = privs.toArray(new Privilege[privs.size()]); + acl.addAccessControlEntry(principal, privileges); + acm.setPolicy(path, acl); + if (log.isDebugEnabled()) { + StringBuffer privBuf = new StringBuffer(); + for (Privilege priv : privs) + privBuf.append(priv.getName()); + log.debug("Added privileges " + privBuf + " to " + + principal.getName() + " on " + path + " in '" + + session.getWorkspace().getName() + "'"); + } + session.refresh(true); + session.save(); + return true; + } + + /** Gets access control list for this path, throws exception if not found */ + public synchronized static AccessControlList getAccessControlList( + AccessControlManager acm, String path) throws RepositoryException { + // search for an access control list + AccessControlList acl = null; + AccessControlPolicyIterator policyIterator = acm + .getApplicablePolicies(path); + if (policyIterator.hasNext()) { + while (policyIterator.hasNext()) { + AccessControlPolicy acp = policyIterator + .nextAccessControlPolicy(); + if (acp instanceof AccessControlList) + acl = ((AccessControlList) acp); + } + } else { + AccessControlPolicy[] existingPolicies = acm.getPolicies(path); + for (AccessControlPolicy acp : existingPolicies) { + if (acp instanceof AccessControlList) + acl = ((AccessControlList) acp); + } + } + if (acl != null) + return acl; + else + throw new ArgeoException("ACL not found at " + path); + } + + /** Clear authorizations for a user at this path */ + public synchronized static void clearAccessControList(Session session, + String path, String username) throws RepositoryException { + AccessControlManager acm = session.getAccessControlManager(); + AccessControlList acl = getAccessControlList(acm, path); + for (AccessControlEntry ace : acl.getAccessControlEntries()) { + if (ace.getPrincipal().getName().equals(username)) { + acl.removeAccessControlEntry(ace); + } + } + } + + /* + * FILES UTILITIES + */ + /** + * Creates the nodes making the path as {@link NodeType#NT_FOLDER} + */ + public static Node mkfolders(Session session, String path) { + return mkdirs(session, path, NodeType.NT_FOLDER, NodeType.NT_FOLDER, + false); + } + + /** + * Copy only nt:folder and nt:file, without their additional types and + * properties. + * + * @param recursive + * if true copies folders as well, otherwise only first level + * files + * @return how many files were copied + */ + @SuppressWarnings("resource") + public static Long copyFiles(Node fromNode, Node toNode, Boolean recursive, + ArgeoMonitor monitor) { + long count = 0l; + + Binary binary = null; + InputStream in = null; + try { + NodeIterator fromChildren = fromNode.getNodes(); + while (fromChildren.hasNext()) { + if (monitor != null && monitor.isCanceled()) + throw new ArgeoException( + "Copy cancelled before it was completed"); + + Node fromChild = fromChildren.nextNode(); + String fileName = fromChild.getName(); + if (fromChild.isNodeType(NodeType.NT_FILE)) { + if (monitor != null) + monitor.subTask("Copy " + fileName); + binary = fromChild.getNode(Node.JCR_CONTENT) + .getProperty(Property.JCR_DATA).getBinary(); + in = binary.getStream(); + copyStreamAsFile(toNode, fileName, in); + IOUtils.closeQuietly(in); + closeQuietly(binary); + + // save session + toNode.getSession().save(); + count++; + + if (log.isDebugEnabled()) + log.debug("Copied file " + fromChild.getPath()); + if (monitor != null) + monitor.worked(1); + } else if (fromChild.isNodeType(NodeType.NT_FOLDER) + && recursive) { + Node toChildFolder; + if (toNode.hasNode(fileName)) { + toChildFolder = toNode.getNode(fileName); + if (!toChildFolder.isNodeType(NodeType.NT_FOLDER)) + throw new ArgeoException(toChildFolder + + " is not of type nt:folder"); + } else { + toChildFolder = toNode.addNode(fileName, + NodeType.NT_FOLDER); + + // save session + toNode.getSession().save(); + } + count = count + + copyFiles(fromChild, toChildFolder, recursive, + monitor); + } + } + return count; + } catch (RepositoryException e) { + throw new ArgeoException("Cannot copy files between " + fromNode + + " and " + toNode); + } finally { + // in case there was an exception + IOUtils.closeQuietly(in); + closeQuietly(binary); + } + } + + /** + * Iteratively count all file nodes in subtree, inefficient but can be + * useful when query are poorly supported, such as in remoting. + */ + public static Long countFiles(Node node) { + Long localCount = 0l; + try { + for (NodeIterator nit = node.getNodes(); nit.hasNext();) { + Node child = nit.nextNode(); + if (child.isNodeType(NodeType.NT_FOLDER)) + localCount = localCount + countFiles(child); + else if (child.isNodeType(NodeType.NT_FILE)) + localCount = localCount + 1; + } + } catch (RepositoryException e) { + throw new ArgeoException("Cannot count all children of " + node); + } + return localCount; + } + + /** + * Copy a file as an nt:file, assuming an nt:folder hierarchy. The session + * is NOT saved. + * + * @return the created file node + */ + public static Node copyFile(Node folderNode, File file) { + InputStream in = null; + try { + in = new FileInputStream(file); + return copyStreamAsFile(folderNode, file.getName(), in); + } catch (IOException e) { + throw new ArgeoException("Cannot copy file " + file + " under " + + folderNode, e); + } finally { + IOUtils.closeQuietly(in); + } + } + + /** Copy bytes as an nt:file */ + public static Node copyBytesAsFile(Node folderNode, String fileName, + byte[] bytes) { + InputStream in = null; + try { + in = new ByteArrayInputStream(bytes); + return copyStreamAsFile(folderNode, fileName, in); + } catch (Exception e) { + throw new ArgeoException("Cannot copy file " + fileName + " under " + + folderNode, e); + } finally { + IOUtils.closeQuietly(in); + } + } + + /** + * Copy a stream as an nt:file, assuming an nt:folder hierarchy. The session + * is NOT saved. + * + * @return the created file node + */ + public static Node copyStreamAsFile(Node folderNode, String fileName, + InputStream in) { + Binary binary = null; + try { + Node fileNode; + Node contentNode; + if (folderNode.hasNode(fileName)) { + fileNode = folderNode.getNode(fileName); + if (!fileNode.isNodeType(NodeType.NT_FILE)) + throw new ArgeoException(fileNode + + " is not of type nt:file"); + // we assume that the content node is already there + contentNode = fileNode.getNode(Node.JCR_CONTENT); + } else { + fileNode = folderNode.addNode(fileName, NodeType.NT_FILE); + contentNode = fileNode.addNode(Node.JCR_CONTENT, + NodeType.NT_RESOURCE); + } + binary = contentNode.getSession().getValueFactory() + .createBinary(in); + contentNode.setProperty(Property.JCR_DATA, binary); + return fileNode; + } catch (Exception e) { + throw new ArgeoException("Cannot create file node " + fileName + + " under " + folderNode, e); + } finally { + closeQuietly(binary); + } + } + + /** Computes the checksum of an nt:file */ + public static String checksumFile(Node fileNode, String algorithm) { + Binary data = null; + InputStream in = null; + try { + data = fileNode.getNode(Node.JCR_CONTENT) + .getProperty(Property.JCR_DATA).getBinary(); + in = data.getStream(); + return DigestUtils.digest(algorithm, in); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot checksum file " + fileNode, e); + } finally { + IOUtils.closeQuietly(in); + closeQuietly(data); + } + } + +} diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/MaintainedRepository.java b/org.argeo.server.jcr/src/org/argeo/jcr/MaintainedRepository.java new file mode 100644 index 000000000..702d47a5c --- /dev/null +++ b/org.argeo.server.jcr/src/org/argeo/jcr/MaintainedRepository.java @@ -0,0 +1,8 @@ +package org.argeo.jcr; + +import javax.jcr.Repository; + +/** Abstracts maintenance operations on a {@link Repository} */ +public interface MaintainedRepository extends Repository { + +} diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/NodeMapper.java b/org.argeo.server.jcr/src/org/argeo/jcr/NodeMapper.java new file mode 100644 index 000000000..af792c3ca --- /dev/null +++ b/org.argeo.server.jcr/src/org/argeo/jcr/NodeMapper.java @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * 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.Node; +import javax.jcr.Session; + +public interface NodeMapper { + public Object load(Node node); + + public void update(Node node, Object obj); + + public Node save(Session session, String path, Object obj); + + public void setNodeMapperProvider(NodeMapperProvider nmp); +} diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/NodeMapperProvider.java b/org.argeo.server.jcr/src/org/argeo/jcr/NodeMapperProvider.java new file mode 100644 index 000000000..07e623bfd --- /dev/null +++ b/org.argeo.server.jcr/src/org/argeo/jcr/NodeMapperProvider.java @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * 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.Node; + +/** Provides a node mapper relevant for this node. */ +public interface NodeMapperProvider { + + /** + * Node Mapper is chosen regarding the Jcr path of the node parameter + * @param Node node + * @return the node mapper or null if no relevant node mapper can be found. */ + public NodeMapper findNodeMapper(Node node); +} diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/PropertyDiff.java b/org.argeo.server.jcr/src/org/argeo/jcr/PropertyDiff.java new file mode 100644 index 000000000..bdd316f31 --- /dev/null +++ b/org.argeo.server.jcr/src/org/argeo/jcr/PropertyDiff.java @@ -0,0 +1,78 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * 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.Value; + +import org.argeo.ArgeoException; + +/** The result of the comparison of two JCR properties. */ +public class PropertyDiff { + public final static Integer MODIFIED = 0; + public final static Integer ADDED = 1; + public final static Integer REMOVED = 2; + + private final Integer type; + private final String relPath; + private final Value referenceValue; + private final Value newValue; + + public PropertyDiff(Integer type, String relPath, Value referenceValue, + Value newValue) { + super(); + + if (type == MODIFIED) { + if (referenceValue == null || newValue == null) + throw new ArgeoException( + "Reference and new values must be specified."); + } else if (type == ADDED) { + if (referenceValue != null || newValue == null) + throw new ArgeoException( + "New value and only it must be specified."); + } else if (type == REMOVED) { + if (referenceValue == null || newValue != null) + throw new ArgeoException( + "Reference value and only it must be specified."); + } else { + throw new ArgeoException("Unkown diff type " + type); + } + + if (relPath == null) + throw new ArgeoException("Relative path must be specified"); + + this.type = type; + this.relPath = relPath; + this.referenceValue = referenceValue; + this.newValue = newValue; + } + + public Integer getType() { + return type; + } + + public String getRelPath() { + return relPath; + } + + public Value getReferenceValue() { + return referenceValue; + } + + public Value getNewValue() { + return newValue; + } + +} diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/RepositoryRegister.java b/org.argeo.server.jcr/src/org/argeo/jcr/RepositoryRegister.java new file mode 100644 index 000000000..2e3d4550d --- /dev/null +++ b/org.argeo.server.jcr/src/org/argeo/jcr/RepositoryRegister.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * 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 java.util.Map; + +import javax.jcr.Repository; +import javax.jcr.RepositoryFactory; + +/** Allows to register repositories by name. */ +public interface RepositoryRegister extends RepositoryFactory { + /** + * The registered {@link Repository} as a read-only map. Note that this + * method should be called for each access in order to be sure to be up to + * date in case repositories have registered/unregistered + */ + public Map getRepositories(); +} diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/ThreadBoundJcrSessionFactory.java b/org.argeo.server.jcr/src/org/argeo/jcr/ThreadBoundJcrSessionFactory.java new file mode 100644 index 000000000..193f22c48 --- /dev/null +++ b/org.argeo.server.jcr/src/org/argeo/jcr/ThreadBoundJcrSessionFactory.java @@ -0,0 +1,304 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * 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 java.lang.reflect.InvocationHandler; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import javax.jcr.LoginException; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.SimpleCredentials; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.ArgeoException; + +/** Proxy JCR sessions and attach them to calling threads. */ +public abstract class ThreadBoundJcrSessionFactory { + private final static Log log = LogFactory + .getLog(ThreadBoundJcrSessionFactory.class); + + private Repository repository; + /** can be injected as list, only used if repository is null */ + private List repositories; + + private ThreadLocal session = new ThreadLocal(); + private final Session proxiedSession; + /** If workspace is null, default will be used. */ + private String workspace = null; + + private String defaultUsername = "demo"; + private String defaultPassword = "demo"; + private Boolean forceDefaultCredentials = false; + + private boolean active = true; + + // monitoring + private final List threads = Collections + .synchronizedList(new ArrayList()); + private final Map activeSessions = Collections + .synchronizedMap(new HashMap()); + private MonitoringThread monitoringThread; + + public ThreadBoundJcrSessionFactory() { + Class[] interfaces = { Session.class }; + proxiedSession = (Session) Proxy.newProxyInstance( + ThreadBoundJcrSessionFactory.class.getClassLoader(), + interfaces, new JcrSessionInvocationHandler()); + } + + /** Logs in to the repository using various strategies. */ + protected synchronized Session login() { + if (!isActive()) + throw new ArgeoException("Thread bound session factory inactive"); + + // discard session previously attached to this thread + Thread thread = Thread.currentThread(); + if (activeSessions.containsKey(thread.getId())) { + Session oldSession = activeSessions.remove(thread.getId()); + oldSession.logout(); + session.remove(); + } + + Session newSession = null; + // first try to login without credentials, assuming the underlying login + // module will have dealt with authentication (typically using Spring + // Security) + if (!forceDefaultCredentials) + try { + newSession = repository().login(workspace); + } catch (LoginException e1) { + log.warn("Cannot login without credentials: " + e1.getMessage()); + // invalid credentials, go to the next step + } catch (RepositoryException e1) { + // other kind of exception, fail + throw new ArgeoException("Cannot log in to repository", e1); + } + + // log using default username / password (useful for testing purposes) + if (newSession == null) + try { + SimpleCredentials sc = new SimpleCredentials(defaultUsername, + defaultPassword.toCharArray()); + newSession = repository().login(sc, workspace); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot log in to repository", e); + } + + session.set(newSession); + // Log and monitor new session + if (log.isTraceEnabled()) + log.trace("Logged in to JCR session " + newSession + "; userId=" + + newSession.getUserID()); + + // monitoring + activeSessions.put(thread.getId(), newSession); + threads.add(thread); + return newSession; + } + + public Object getObject() { + return proxiedSession; + } + + public void init() throws Exception { + monitoringThread = new MonitoringThread(); + monitoringThread.start(); + } + + public synchronized void dispose() throws Exception { + if (activeSessions.size() == 0) + return; + + if (log.isTraceEnabled()) + log.trace("Cleaning up " + activeSessions.size() + + " active JCR sessions..."); + + deactivate(); + for (Session sess : activeSessions.values()) { + JcrUtils.logoutQuietly(sess); + } + activeSessions.clear(); + } + + protected Boolean isActive() { + return active; + } + + protected synchronized void deactivate() { + active = false; + notifyAll(); + } + + protected synchronized void removeSession(Thread thread) { + if (!isActive()) + return; + activeSessions.remove(thread.getId()); + threads.remove(thread); + } + + protected synchronized void cleanDeadThreads() { + if (!isActive()) + return; + Iterator it = threads.iterator(); + while (it.hasNext()) { + Thread thread = it.next(); + if (!thread.isAlive() && isActive()) { + if (activeSessions.containsKey(thread.getId())) { + Session session = activeSessions.get(thread.getId()); + activeSessions.remove(thread.getId()); + session.logout(); + if (log.isTraceEnabled()) + log.trace("Cleaned up JCR session (userID=" + + session.getUserID() + ") from dead thread " + + thread.getId()); + } + it.remove(); + } + } + try { + wait(1000); + } catch (InterruptedException e) { + // silent + } + } + + public Class getObjectType() { + return Session.class; + } + + public boolean isSingleton() { + return true; + } + + /** + * Called before a method is actually called, allowing to check the session + * or re-login it (e.g. if authentication has changed). The default + * implementation returns the session. + */ + protected Session preCall(Session session) { + return session; + } + + protected Repository repository() { + if (repository != null) + return repository; + if (repositories != null) { + // hardened for OSGi dynamic services + Iterator it = repositories.iterator(); + if (it.hasNext()) + return it.next(); + } + throw new ArgeoException("No repository injected"); + } + + // /** Useful for declarative registration of OSGi services (blueprint) */ + // public void register(Repository repository, Map params) { + // this.repository = repository; + // } + // + // /** Useful for declarative registration of OSGi services (blueprint) */ + // public void unregister(Repository repository, Map params) { + // this.repository = null; + // } + + public void setRepository(Repository repository) { + this.repository = repository; + } + + public void setRepositories(List repositories) { + this.repositories = repositories; + } + + public void setDefaultUsername(String defaultUsername) { + this.defaultUsername = defaultUsername; + } + + public void setDefaultPassword(String defaultPassword) { + this.defaultPassword = defaultPassword; + } + + public void setForceDefaultCredentials(Boolean forceDefaultCredentials) { + this.forceDefaultCredentials = forceDefaultCredentials; + } + + public void setWorkspace(String workspace) { + this.workspace = workspace; + } + + protected class JcrSessionInvocationHandler implements InvocationHandler { + + public Object invoke(Object proxy, Method method, Object[] args) + throws Throwable, RepositoryException { + Session threadSession = session.get(); + if (threadSession == null) { + if ("logout".equals(method.getName()))// no need to login + return Void.TYPE; + else if ("toString".equals(method.getName()))// maybe logging + return "Uninitialized Argeo thread bound JCR session"; + threadSession = login(); + } + + preCall(threadSession); + Object ret; + try { + ret = method.invoke(threadSession, args); + } catch (InvocationTargetException e) { + Throwable cause = e.getCause(); + if (cause instanceof RepositoryException) + throw (RepositoryException) cause; + else + throw cause; + } + if ("logout".equals(method.getName())) { + session.remove(); + Thread thread = Thread.currentThread(); + removeSession(thread); + if (log.isTraceEnabled()) + log.trace("Logged out JCR session (userId=" + + threadSession.getUserID() + ") on thread " + + thread.getId()); + } + return ret; + } + } + + /** Monitors registered thread in order to clean up dead ones. */ + private class MonitoringThread extends Thread { + + public MonitoringThread() { + super("ThreadBound JCR Session Monitor"); + } + + @Override + public void run() { + while (isActive()) { + cleanDeadThreads(); + } + } + + } +} diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/UserJcrUtils.java b/org.argeo.server.jcr/src/org/argeo/jcr/UserJcrUtils.java new file mode 100644 index 000000000..1758802f8 --- /dev/null +++ b/org.argeo.server.jcr/src/org/argeo/jcr/UserJcrUtils.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * 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.Node; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.query.Query; +import javax.jcr.query.qom.Constraint; +import javax.jcr.query.qom.DynamicOperand; +import javax.jcr.query.qom.QueryObjectModelFactory; +import javax.jcr.query.qom.Selector; +import javax.jcr.query.qom.StaticOperand; + +import org.argeo.ArgeoException; + +/** Utilities related to the user home and properties based on Argeo JCR model. */ +public class UserJcrUtils { + /** The home base path. Not yet configurable */ + public final static String DEFAULT_HOME_BASE_PATH = "/home"; + + /** + * Returns the home node of the user or null if none was found. + * + * @param session + * the session to use in order to perform the search, this can be + * a session with a different user ID than the one searched, + * typically when a system or admin session is used. + * @param username + * the username of the user + */ + public static Node getUserHome(Session session, String username) { + try { + // String homePath = UserJcrUtils.getUserHomePath(username); + // return session.itemExists(homePath) ? session.getNode(homePath) + // : null; + // kept for example of QOM queries + QueryObjectModelFactory qomf = session.getWorkspace() + .getQueryManager().getQOMFactory(); + Selector userHomeSel = qomf.selector(ArgeoTypes.ARGEO_USER_HOME, + "userHome"); + DynamicOperand userIdDop = qomf.propertyValue( + userHomeSel.getSelectorName(), ArgeoNames.ARGEO_USER_ID); + StaticOperand userIdSop = qomf.literal(session.getValueFactory() + .createValue(username)); + Constraint constraint = qomf.comparison(userIdDop, + QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, userIdSop); + Query query = qomf.createQuery(userHomeSel, constraint, null, null); + return JcrUtils.querySingleNode(query); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot find home for user " + username, e); + } + } + + public static Node getUserProfile(Session session, String username) { + try { + QueryObjectModelFactory qomf = session.getWorkspace() + .getQueryManager().getQOMFactory(); + Selector userHomeSel = qomf.selector(ArgeoTypes.ARGEO_USER_PROFILE, + "userProfile"); + DynamicOperand userIdDop = qomf.propertyValue( + userHomeSel.getSelectorName(), ArgeoNames.ARGEO_USER_ID); + StaticOperand userIdSop = qomf.literal(session.getValueFactory() + .createValue(username)); + Constraint constraint = qomf.comparison(userIdDop, + QueryObjectModelFactory.JCR_OPERATOR_EQUAL_TO, userIdSop); + Query query = qomf.createQuery(userHomeSel, constraint, null, null); + return JcrUtils.querySingleNode(query); + } catch (RepositoryException e) { + throw new ArgeoException( + "Cannot find profile for user " + username, e); + } + } + + /** Returns the home node of the session user or null if none was found. */ + public static Node getUserHome(Session session) { + String userID = session.getUserID(); + return getUserHome(session, userID); + } + + private UserJcrUtils() { + } +} diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/VersionDiff.java b/org.argeo.server.jcr/src/org/argeo/jcr/VersionDiff.java new file mode 100644 index 000000000..e6ae9130b --- /dev/null +++ b/org.argeo.server.jcr/src/org/argeo/jcr/VersionDiff.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * 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 java.util.Calendar; +import java.util.Map; + +/** + * Generic Object that enables the creation of history reports based on a JCR + * versionable node. userId and creation date are added to the map of + * PropertyDiff. + * + * These two fields might be null + * + */ +public class VersionDiff { + + private String userId; + private Map diffs; + private Calendar updateTime; + + public VersionDiff(String userId, Calendar updateTime, + Map diffs) { + this.userId = userId; + this.updateTime = updateTime; + this.diffs = diffs; + } + + public String getUserId() { + return userId; + } + + public Map getDiffs() { + return diffs; + } + + public Calendar getUpdateTime() { + return updateTime; + } +} diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/argeo.cnd b/org.argeo.server.jcr/src/org/argeo/jcr/argeo.cnd new file mode 100644 index 000000000..fbfea9dd9 --- /dev/null +++ b/org.argeo.server.jcr/src/org/argeo/jcr/argeo.cnd @@ -0,0 +1,72 @@ + + +// GENERIC TYPES NOT AVAILABLE IN JCR +[argeo:link] > mix:created, mix:lastModified +mixin +// URI(s) +- argeo:uri (STRING) m + +[argeo:references] > nt:unstructured +- * (REFERENCE) * + +// DATA MODEL +[argeo:dataModel] > mix:created, mix:lastModified, mix:versionable +mixin +- argeo:uri (STRING) m +- argeo:dataModelVersion (STRING) m + +// USER NODES +// user should be lower case, between 3 and 15 characters long +[argeo:userHome] > mix:created, mix:lastModified +mixin +- argeo:userID (STRING) m +- argeo:remoteRoles (STRING) * +// deprecated. for backward compatibility: ++ argeo:profile (argeo:userProfile) ++ argeo:keyring (argeo:pbeSpec) ++ argeo:preferences (argeo:preferenceNode) + +[argeo:userProfile] > mix:created, mix:lastModified, mix:title, mix:versionable +mixin +- argeo:userID (STRING) m +- argeo:enabled (BOOLEAN) +- argeo:accountNonExpired (BOOLEAN) +- argeo:accountNonLocked (BOOLEAN) +- argeo:credentialsNonExpired (BOOLEAN) + +[argeo:preferenceNode] > mix:lastModified, mix:versionable +mixin ++ * (argeo:preferenceNode) * version + +[argeo:remoteRepository] > nt:unstructured +- argeo:uri (STRING) +- argeo:userID (STRING) ++ argeo:password (argeo:encrypted) + +// TABULAR CONTENT +[argeo:table] > nt:file ++ * (argeo:column) * + +[argeo:column] > mix:title +- jcr:requiredType (STRING) = 'STRING' + +[argeo:csv] > nt:resource + +// CRYPTO +[argeo:encrypted] > nt:base +mixin +// initialization vector used by some algorithms +- argeo:iv (BINARY) + +[argeo:pbeKeySpec] > nt:base +mixin +- argeo:secretKeyFactory (STRING) +- argeo:salt (BINARY) +- argeo:iterationCount (LONG) +- argeo:keyLength (LONG) +- argeo:secretKeyEncryption (STRING) + +[argeo:pbeSpec] > argeo:pbeKeySpec +mixin +- argeo:cipher (STRING) + diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/proxy/AbstractUrlProxy.java b/org.argeo.server.jcr/src/org/argeo/jcr/proxy/AbstractUrlProxy.java new file mode 100644 index 000000000..8a66f3102 --- /dev/null +++ b/org.argeo.server.jcr/src/org/argeo/jcr/proxy/AbstractUrlProxy.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * 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.proxy; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; + +import javax.jcr.Binary; +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.nodetype.NodeType; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.ArgeoException; +import org.argeo.jcr.JcrUtils; + +/** Base class for URL based proxys. */ +public abstract class AbstractUrlProxy implements ResourceProxy { + private final static Log log = LogFactory.getLog(AbstractUrlProxy.class); + + private Repository jcrRepository; + private Session jcrAdminSession; + private String proxyWorkspace = "proxy"; + + protected abstract Node retrieve(Session session, String path); + + void init() { + try { + jcrAdminSession = JcrUtils.loginOrCreateWorkspace(jcrRepository, + proxyWorkspace); + beforeInitSessionSave(jcrAdminSession); + if (jcrAdminSession.hasPendingChanges()) + jcrAdminSession.save(); + } catch (Exception e) { + JcrUtils.discardQuietly(jcrAdminSession); + throw new ArgeoException("Cannot initialize Maven proxy", e); + } + } + + /** + * Called before the (admin) session is saved at the end of the + * initialization. Does nothing by default, to be overridden. + */ + protected void beforeInitSessionSave(Session session) + throws RepositoryException { + } + + void destroy() { + JcrUtils.logoutQuietly(jcrAdminSession); + } + + /** + * Called before the (admin) session is logged out when resources are + * released. Does nothing by default, to be overridden. + */ + protected void beforeDestroySessionLogout() throws RepositoryException { + } + + public Node proxy(String path) { + // we open a JCR session with client credentials in order not to use the + // admin session in multiple thread or make it a bottleneck. + Node nodeAdmin = null; + Node nodeClient = null; + Session clientSession = null; + try { + clientSession = jcrRepository.login(proxyWorkspace); + if (!clientSession.itemExists(path) + || shouldUpdate(clientSession, path)) { + nodeAdmin = retrieveAndSave(path); + if (nodeAdmin != null) + nodeClient = clientSession.getNode(path); + } else + nodeClient = clientSession.getNode(path); + return nodeClient; + } catch (RepositoryException e) { + throw new ArgeoException("Cannot proxy " + path, e); + } finally { + if (nodeClient == null) + JcrUtils.logoutQuietly(clientSession); + } + } + + protected synchronized Node retrieveAndSave(String path) { + try { + Node node = retrieve(jcrAdminSession, path); + if (node == null) + return null; + jcrAdminSession.save(); + return node; + } catch (RepositoryException e) { + JcrUtils.discardQuietly(jcrAdminSession); + throw new ArgeoException("Cannot retrieve and save " + path, e); + } finally { + notifyAll(); + } + } + + /** Session is not saved */ + protected synchronized Node proxyUrl(Session session, String remoteUrl, + String path) throws RepositoryException { + Node node = null; + if (session.itemExists(path)) { + // throw new ArgeoException("Node " + path + " already exists"); + } + InputStream in = null; + try { + URL u = new URL(remoteUrl); + in = u.openStream(); + node = importFile(session, path, in); + } catch (IOException e) { + if (log.isDebugEnabled()) { + log.debug("Cannot read " + remoteUrl + ", skipping... " + + e.getMessage()); + // log.trace("Cannot read because of ", e); + } + JcrUtils.discardQuietly(session); + } finally { + IOUtils.closeQuietly(in); + } + return node; + } + + protected synchronized Node importFile(Session session, String path, + InputStream in) throws RepositoryException { + Binary binary = null; + try { + Node content = null; + Node node = null; + if (!session.itemExists(path)) { + node = JcrUtils.mkdirs(session, path, NodeType.NT_FILE, + NodeType.NT_FOLDER, false); + content = node.addNode(Node.JCR_CONTENT, NodeType.NT_RESOURCE); + } else { + node = session.getNode(path); + content = node.getNode(Node.JCR_CONTENT); + } + binary = session.getValueFactory().createBinary(in); + content.setProperty(Property.JCR_DATA, binary); + JcrUtils.updateLastModifiedAndParents(node, null); + return node; + } finally { + JcrUtils.closeQuietly(binary); + } + } + + /** Whether the file should be updated. */ + protected Boolean shouldUpdate(Session clientSession, String nodePath) { + return false; + } + + public void setJcrRepository(Repository jcrRepository) { + this.jcrRepository = jcrRepository; + } + + public void setProxyWorkspace(String localWorkspace) { + this.proxyWorkspace = localWorkspace; + } + +} diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/proxy/ResourceProxy.java b/org.argeo.server.jcr/src/org/argeo/jcr/proxy/ResourceProxy.java new file mode 100644 index 000000000..b4fb33286 --- /dev/null +++ b/org.argeo.server.jcr/src/org/argeo/jcr/proxy/ResourceProxy.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * 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.proxy; + +import javax.jcr.Node; + +/** A proxy which nows how to resolve and synchronize relative URLs */ +public interface ResourceProxy { + /** + * Proxy the file referenced by this relative path in the underlying + * repository. A new session is created by each call, so the underlying + * session of the returned node must be closed by the caller. + * + * @return the proxied Node, null if the resource was not found + * (e.g. HTTP 404) + */ + public Node proxy(String relativePath); +} diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/security/JcrAuthorizations.java b/org.argeo.server.jcr/src/org/argeo/jcr/security/JcrAuthorizations.java new file mode 100644 index 000000000..491f8a6fe --- /dev/null +++ b/org.argeo.server.jcr/src/org/argeo/jcr/security/JcrAuthorizations.java @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * 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.security; + +import java.security.Principal; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.jcr.Repository; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.security.AccessControlManager; +import javax.jcr.security.Privilege; + +import org.argeo.ArgeoException; +import org.argeo.jcr.JcrUtils; +import org.argeo.util.security.SimplePrincipal; + +/** Apply authorizations to a JCR repository. */ +public class JcrAuthorizations implements Runnable { + // private final static Log log = + // LogFactory.getLog(JcrAuthorizations.class); + + private Repository repository; + private String workspace = null; + + private String securityWorkspace = "security"; + + /** + * key := privilege1,privilege2/path/to/node
+ * value := group1,group2,user1 + */ + private Map principalPrivileges = new HashMap(); + + public void run() { + String currentWorkspace = workspace; + Session session = null; + try { + if (workspace != null && workspace.equals("*")) { + session = repository.login(); + String[] workspaces = session.getWorkspace() + .getAccessibleWorkspaceNames(); + JcrUtils.logoutQuietly(session); + for (String wksp : workspaces) { + currentWorkspace = wksp; + if (currentWorkspace.equals(securityWorkspace)) + continue; + session = repository.login(currentWorkspace); + initAuthorizations(session); + JcrUtils.logoutQuietly(session); + } + } else { + session = repository.login(workspace); + initAuthorizations(session); + } + } catch (Exception e) { + JcrUtils.discardQuietly(session); + throw new ArgeoException( + "Cannot set authorizations " + principalPrivileges + + " on workspace " + currentWorkspace, e); + } finally { + JcrUtils.logoutQuietly(session); + } + } + + protected void processWorkspace(String workspace) { + Session session = null; + try { + session = repository.login(workspace); + initAuthorizations(session); + } catch (Exception e) { + JcrUtils.discardQuietly(session); + throw new ArgeoException("Cannot set authorizations " + + principalPrivileges + " on repository " + repository, e); + } finally { + JcrUtils.logoutQuietly(session); + } + } + + /** @deprecated call {@link #run()} instead. */ + @Deprecated + public void init() { + run(); + } + + protected void initAuthorizations(Session session) + throws RepositoryException { + AccessControlManager acm = session.getAccessControlManager(); + + for (String privileges : principalPrivileges.keySet()) { + String path = null; + int slashIndex = privileges.indexOf('/'); + if (slashIndex == 0) { + throw new ArgeoException("Privilege " + privileges + + " badly formatted it starts with /"); + } else if (slashIndex > 0) { + path = privileges.substring(slashIndex); + privileges = privileges.substring(0, slashIndex); + } + + if (path == null) + path = "/"; + + List privs = new ArrayList(); + for (String priv : privileges.split(",")) { + privs.add(acm.privilegeFromName(priv)); + } + + String principalNames = principalPrivileges.get(privileges); + for (String principalName : principalNames.split(",")) { + Principal principal = getOrCreatePrincipal(session, + principalName); + JcrUtils.addPrivileges(session, path, principal, privs); + // if (log.isDebugEnabled()) { + // StringBuffer privBuf = new StringBuffer(); + // for (Privilege priv : privs) + // privBuf.append(priv.getName()); + // log.debug("Added privileges " + privBuf + " to " + // + principal.getName() + " on " + path + " in '" + // + session.getWorkspace().getName() + "'"); + // } + } + } + + // if (log.isDebugEnabled()) + // log.debug("JCR authorizations applied on '" + // + session.getWorkspace().getName() + "'"); + } + + /** + * Returns a {@link SimplePrincipal}, does not check whether it exists since + * such capabilities is not provided by the standard JCR API. Can be + * overridden to provide smarter handling + */ + protected Principal getOrCreatePrincipal(Session session, + String principalName) throws RepositoryException { + return new SimplePrincipal(principalName); + } + + // public static void addPrivileges(Session session, Principal principal, + // String path, List privs) throws RepositoryException { + // AccessControlManager acm = session.getAccessControlManager(); + // // search for an access control list + // AccessControlList acl = null; + // AccessControlPolicyIterator policyIterator = acm + // .getApplicablePolicies(path); + // if (policyIterator.hasNext()) { + // while (policyIterator.hasNext()) { + // AccessControlPolicy acp = policyIterator + // .nextAccessControlPolicy(); + // if (acp instanceof AccessControlList) + // acl = ((AccessControlList) acp); + // } + // } else { + // AccessControlPolicy[] existingPolicies = acm.getPolicies(path); + // for (AccessControlPolicy acp : existingPolicies) { + // if (acp instanceof AccessControlList) + // acl = ((AccessControlList) acp); + // } + // } + // + // if (acl != null) { + // acl.addAccessControlEntry(principal, + // privs.toArray(new Privilege[privs.size()])); + // acm.setPolicy(path, acl); + // session.save(); + // if (log.isDebugEnabled()) { + // StringBuffer buf = new StringBuffer(""); + // for (int i = 0; i < privs.size(); i++) { + // if (i != 0) + // buf.append(','); + // buf.append(privs.get(i).getName()); + // } + // log.debug("Added privilege(s) '" + buf + "' to '" + // + principal.getName() + "' on " + path + // + " from workspace '" + // + session.getWorkspace().getName() + "'"); + // } + // } else { + // throw new ArgeoException("Don't know how to apply privileges " + // + privs + " to " + principal + " on " + path + // + " from workspace '" + session.getWorkspace().getName() + // + "'"); + // } + // } + + @Deprecated + public void setGroupPrivileges(Map groupPrivileges) { + this.principalPrivileges = groupPrivileges; + } + + public void setPrincipalPrivileges(Map principalPrivileges) { + this.principalPrivileges = principalPrivileges; + } + + public void setRepository(Repository repository) { + this.repository = repository; + } + + public void setWorkspace(String workspace) { + this.workspace = workspace; + } + + public void setSecurityWorkspace(String securityWorkspace) { + this.securityWorkspace = securityWorkspace; + } + +} diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/spring/BeanNodeMapper.java b/org.argeo.server.jcr/src/org/argeo/jcr/spring/BeanNodeMapper.java new file mode 100644 index 000000000..9f70f5c58 --- /dev/null +++ b/org.argeo.server.jcr/src/org/argeo/jcr/spring/BeanNodeMapper.java @@ -0,0 +1,649 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * 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.spring; + +import java.beans.PropertyDescriptor; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; + +import javax.jcr.Binary; +import javax.jcr.ItemNotFoundException; +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.Property; +import javax.jcr.PropertyIterator; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.Value; +import javax.jcr.ValueFactory; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.ArgeoException; +import org.argeo.jcr.JcrUtils; +import org.argeo.jcr.NodeMapper; +import org.argeo.jcr.NodeMapperProvider; +import org.springframework.beans.BeanWrapper; +import org.springframework.beans.BeanWrapperImpl; + +public class BeanNodeMapper implements NodeMapper { + private final static Log log = LogFactory.getLog(BeanNodeMapper.class); + + private final static String NODE_VALUE = "value"; + + // private String keyNode = "bean:key"; + private String uuidProperty = "uuid"; + private String classProperty = "class"; + + private Boolean versioning = false; + private Boolean strictUuidReference = false; + + // TODO define a primaryNodeType Strategy + private String primaryNodeType = null; + + private ClassLoader classLoader = getClass().getClassLoader(); + + private NodeMapperProvider nodeMapperProvider; + + /** + * exposed method to retrieve a bean from a node + */ + public Object load(Node node) { + try { + if (nodeMapperProvider != null) { + NodeMapper nodeMapper = nodeMapperProvider.findNodeMapper(node); + if (nodeMapper != this) { + return nodeMapper.load(node); + } + } + return nodeToBean(node); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot load object from node " + node, e); + } + } + + /** Update an existing node with an object */ + public void update(Node node, Object obj) { + try { + if (nodeMapperProvider != null) { + + NodeMapper nodeMapper = nodeMapperProvider.findNodeMapper(node); + if (nodeMapper != this) { + nodeMapper.update(node, obj); + } else + beanToNode(createBeanWrapper(obj), node); + } else + beanToNode(createBeanWrapper(obj), node); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot update node " + node + " with " + + obj, e); + } + } + + /** + * if no storage path is given; we use canonical path + * + * @see this.storagePath() + */ + public Node save(Session session, Object obj) { + return save(session, storagePath(obj), obj); + } + + /** + * Create a new node to store an object. If the parentNode doesn't exist, it + * is created + * + * the primaryNodeType may be initialized before + */ + public Node save(Session session, String path, Object obj) { + try { + final Node node; + String parentPath = JcrUtils.parentPath(path); + // find or create parent node + Node parentNode; + if (session.itemExists(path)) + parentNode = (Node) session.getItem(parentPath); + else { + parentNode = JcrUtils.mkdirs(session, parentPath, null, null, + versioning); + } + // create node + + if (primaryNodeType != null) + node = parentNode.addNode(JcrUtils.lastPathElement(path), + primaryNodeType); + else + node = parentNode.addNode(JcrUtils.lastPathElement(path)); + + // Check specific cases + if (nodeMapperProvider != null) { + NodeMapper nodeMapper = nodeMapperProvider.findNodeMapper(node); + if (nodeMapper != this) { + nodeMapper.update(node, obj); + return node; + } + } + update(node, obj); + return node; + } catch (ArgeoException e) { + throw e; + } catch (Exception e) { + throw new ArgeoException("Cannot save or update " + obj + " under " + + path, e); + } + } + + /** + * Parse the FQN of a class to string with '/' delimiters Prefix the + * returned string with "/objects/" + */ + public String storagePath(Object obj) { + String clss = obj.getClass().getName(); + StringBuffer buf = new StringBuffer("/objects/"); + StringTokenizer st = new StringTokenizer(clss, "."); + while (st.hasMoreTokens()) { + buf.append(st.nextToken()).append('/'); + } + buf.append(obj.toString()); + return buf.toString(); + } + + @SuppressWarnings("unchecked") + /** + * Transforms a node into an object of the class defined by classProperty Property + */ + protected Object nodeToBean(Node node) throws RepositoryException { + if (log.isTraceEnabled()) + log.trace("Load " + node); + + try { + String clssName = node.getProperty(classProperty).getValue() + .getString(); + + BeanWrapper beanWrapper = createBeanWrapper(loadClass(clssName)); + + // process properties + PropertyIterator propIt = node.getProperties(); + props: while (propIt.hasNext()) { + Property prop = propIt.nextProperty(); + if (!beanWrapper.isWritableProperty(prop.getName())) + continue props; + + PropertyDescriptor pd = beanWrapper.getPropertyDescriptor(prop + .getName()); + Class propClass = pd.getPropertyType(); + + if (log.isTraceEnabled()) + log.trace("Load " + prop + ", propClass=" + propClass + + ", property descriptor=" + pd); + + // primitive list + if (propClass != null && List.class.isAssignableFrom(propClass)) { + List lst = new ArrayList(); + Class valuesClass = classFromProperty(prop); + if (valuesClass != null) + for (Value value : prop.getValues()) { + lst.add(asObject(value, valuesClass)); + } + continue props; + } + + // Case of other type of property accepted by jcr + // Long, Double, String, Binary, Date, Boolean, Name + Object value = asObject(prop.getValue(), pd.getPropertyType()); + if (value != null) + beanWrapper.setPropertyValue(prop.getName(), value); + } + + // process children nodes + NodeIterator nodeIt = node.getNodes(); + nodes: while (nodeIt.hasNext()) { + Node childNode = nodeIt.nextNode(); + String name = childNode.getName(); + if (!beanWrapper.isWritableProperty(name)) + continue nodes; + + PropertyDescriptor pd = beanWrapper.getPropertyDescriptor(name); + Class propClass = pd.getPropertyType(); + + // objects list + if (propClass != null && List.class.isAssignableFrom(propClass)) { + String lstClass = childNode.getProperty(classProperty) + .getString(); + List lst; + try { + lst = (List) loadClass(lstClass).newInstance(); + } catch (Exception e) { + lst = new ArrayList(); + } + + if (childNode.hasNodes()) { + // Look for children nodes + NodeIterator valuesIt = childNode.getNodes(); + while (valuesIt.hasNext()) { + Node lstValueNode = valuesIt.nextNode(); + Object lstValue = nodeToBean(lstValueNode); + lst.add(lstValue); + } + } else { + // look for a property with the same name which will + // provide + // primitives + Property childProp = childNode.getProperty(childNode + .getName()); + Class valuesClass = classFromProperty(childProp); + if (valuesClass != null) + if (childProp.getDefinition().isMultiple()) + for (Value value : childProp.getValues()) { + lst.add(asObject(value, valuesClass)); + } + else + lst.add(asObject(childProp.getValue(), + valuesClass)); + } + beanWrapper.setPropertyValue(name, lst); + continue nodes; + } + + // objects map + if (propClass != null && Map.class.isAssignableFrom(propClass)) { + String mapClass = childNode.getProperty(classProperty) + .getString(); + Map map; + try { + map = (Map) loadClass(mapClass) + .newInstance(); + } catch (Exception e) { + map = new HashMap(); + } + + // properties + PropertyIterator keysPropIt = childNode.getProperties(); + keyProps: while (keysPropIt.hasNext()) { + Property keyProp = keysPropIt.nextProperty(); + // FIXME: use property editor + String key = keyProp.getName(); + if (classProperty.equals(key)) + continue keyProps; + + Class keyPropClass = classFromProperty(keyProp); + if (keyPropClass != null) { + Object mapValue = asObject(keyProp.getValue(), + keyPropClass); + map.put(key, mapValue); + } + } + + // node + NodeIterator keysIt = childNode.getNodes(); + while (keysIt.hasNext()) { + Node mapValueNode = keysIt.nextNode(); + // FIXME: use property editor + Object key = mapValueNode.getName(); + + Object mapValue = nodeToBean(mapValueNode); + + map.put(key, mapValue); + } + beanWrapper.setPropertyValue(name, map); + continue nodes; + } + + // default + Object value = nodeToBean(childNode); + beanWrapper.setPropertyValue(name, value); + + } + return beanWrapper.getWrappedInstance(); + } catch (Exception e) { + throw new ArgeoException("Cannot map node " + node, e); + } + } + + /** + * Transforms an object to the specified jcr Node in order to persist it. + * + * @param beanWrapper + * @param node + * @throws RepositoryException + */ + protected void beanToNode(BeanWrapper beanWrapper, Node node) + throws RepositoryException { + properties: for (PropertyDescriptor pd : beanWrapper + .getPropertyDescriptors()) { + String name = pd.getName(); + if (!beanWrapper.isReadableProperty(name)) + continue properties;// skip + + Object value = beanWrapper.getPropertyValue(name); + if (value == null) { + // remove values when updating + if (node.hasProperty(name)) + node.setProperty(name, (Value) null); + if (node.hasNode(name)) + node.getNode(name).remove(); + + continue properties; + } + + // if (uuidProperty != null && uuidProperty.equals(name)) { + // // node.addMixin(ArgeoJcrConstants.MIX_REFERENCEABLE); + // node.setProperty(ArgeoJcrConstants.JCR_UUID, value.toString()); + // continue properties; + // } + + if ("class".equals(name)) { + if (classProperty != null) { + node.setProperty(classProperty, + ((Class) value).getName()); + // TODO: store a class hierarchy? + } + continue properties; + } + + // Some bean reference other classes. We must deal with this case + if (value instanceof Class) { + node.setProperty(name, ((Class) value).getName()); + continue properties; + } + + Value val = asValue(node.getSession(), value); + if (val != null) { + node.setProperty(name, val); + continue properties; + } + + if (value instanceof List) { + List lst = (List) value; + addList(node, name, lst); + continue properties; + } + + if (value instanceof Map) { + Map map = (Map) value; + addMap(node, name, map); + continue properties; + } + + BeanWrapper child = createBeanWrapper(value); + // TODO: delegate to another mapper + + // TODO: deal with references + // Node childNode = findChildReference(session, child); + // if (childNode != null) { + // node.setProperty(name, childNode); + // continue properties; + // } + + // default case (recursive) + if (node.hasNode(name)) {// update + // TODO: optimize + node.getNode(name).remove(); + } + Node childNode = node.addNode(name); + beanToNode(child, childNode); + } + } + + /** + * Process specific case of list + * + * @param node + * @param name + * @param lst + * @throws RepositoryException + */ + protected void addList(Node node, String name, List lst) + throws RepositoryException { + if (node.hasNode(name)) {// update + // TODO: optimize + node.getNode(name).remove(); + } + + Node listNode = node.addNode(name); + listNode.setProperty(classProperty, lst.getClass().getName()); + Value[] values = new Value[lst.size()]; + boolean atLeastOneSet = false; + for (int i = 0; i < lst.size(); i++) { + Object lstValue = lst.get(i); + values[i] = asValue(node.getSession(), lstValue); + if (values[i] != null) { + atLeastOneSet = true; + } else { + Node childNode = findChildReference(node.getSession(), + createBeanWrapper(lstValue)); + if (childNode != null) { + values[i] = node.getSession().getValueFactory() + .createValue(childNode); + atLeastOneSet = true; + } + } + } + + // will be either properties or nodes, not both + if (!atLeastOneSet && lst.size() != 0) { + for (Object lstValue : lst) { + Node childNode = listNode.addNode(NODE_VALUE); + beanToNode(createBeanWrapper(lstValue), childNode); + } + } else { + listNode.setProperty(name, values); + } + } + + /** + * Process specific case of maps. + * + * @param node + * @param name + * @param map + * @throws RepositoryException + */ + protected void addMap(Node node, String name, Map map) + throws RepositoryException { + if (node.hasNode(name)) {// update + // TODO: optimize + node.getNode(name).remove(); + } + + Node mapNode = node.addNode(name); + mapNode.setProperty(classProperty, map.getClass().getName()); + for (Object key : map.keySet()) { + Object mapValue = map.get(key); + // PropertyEditor pe = beanWrapper.findCustomEditor(key.getClass(), + // null); + String keyStr; + // if (pe == null) { + if (key instanceof CharSequence) + keyStr = key.toString(); + else + throw new ArgeoException( + "Cannot find property editor for class " + + key.getClass()); + // } else { + // pe.setValue(key); + // keyStr = pe.getAsText(); + // } + // TODO: check string format + + Value mapVal = asValue(node.getSession(), mapValue); + if (mapVal != null) + mapNode.setProperty(keyStr, mapVal); + else { + Node entryNode = mapNode.addNode(keyStr); + beanToNode(createBeanWrapper(mapValue), entryNode); + } + + } + + } + + protected BeanWrapper createBeanWrapper(Object obj) { + return new BeanWrapperImpl(obj); + } + + protected BeanWrapper createBeanWrapper(Class clss) { + return new BeanWrapperImpl(clss); + } + + /** Returns null if value cannot be found */ + protected Value asValue(Session session, Object value) + throws RepositoryException { + ValueFactory valueFactory = session.getValueFactory(); + if (value instanceof Integer) + return valueFactory.createValue((Integer) value); + else if (value instanceof Long) + return valueFactory.createValue((Long) value); + else if (value instanceof Float) + return valueFactory.createValue((Float) value); + else if (value instanceof Double) + return valueFactory.createValue((Double) value); + else if (value instanceof Boolean) + return valueFactory.createValue((Boolean) value); + else if (value instanceof Calendar) + return valueFactory.createValue((Calendar) value); + else if (value instanceof Date) { + Calendar cal = new GregorianCalendar(); + cal.setTime((Date) value); + return valueFactory.createValue(cal); + } else if (value instanceof CharSequence) + return valueFactory.createValue(value.toString()); + else if (value instanceof InputStream) { + Binary binary = session.getValueFactory().createBinary( + (InputStream) value); + return valueFactory.createValue(binary); + } else + return null; + } + + protected Class classFromProperty(Property property) + throws RepositoryException { + switch (property.getType()) { + case PropertyType.LONG: + return Long.class; + case PropertyType.DOUBLE: + return Double.class; + case PropertyType.STRING: + return String.class; + case PropertyType.BOOLEAN: + return Boolean.class; + case PropertyType.DATE: + return Calendar.class; + case PropertyType.NAME: + return null; + default: + throw new ArgeoException("Cannot find class for property " + + property + ", type=" + + PropertyType.nameFromValue(property.getType())); + } + } + + protected Object asObject(Value value, Class propClass) + throws RepositoryException { + if (propClass.equals(Integer.class)) + return (int) value.getLong(); + else if (propClass.equals(Long.class)) + return value.getLong(); + else if (propClass.equals(Float.class)) + return (float) value.getDouble(); + else if (propClass.equals(Double.class)) + return value.getDouble(); + else if (propClass.equals(Boolean.class)) + return value.getBoolean(); + else if (CharSequence.class.isAssignableFrom(propClass)) + return value.getString(); + else if (InputStream.class.isAssignableFrom(propClass)) + return value.getBinary().getStream(); + else if (Calendar.class.isAssignableFrom(propClass)) + return value.getDate(); + else if (Date.class.isAssignableFrom(propClass)) + return value.getDate().getTime(); + else + return null; + } + + protected Node findChildReference(Session session, BeanWrapper child) + throws RepositoryException { + if (child.isReadableProperty(uuidProperty)) { + String childUuid = child.getPropertyValue(uuidProperty).toString(); + try { + return session.getNodeByIdentifier(childUuid); + } catch (ItemNotFoundException e) { + if (strictUuidReference) + throw new ArgeoException("No node found with uuid " + + childUuid, e); + } + } + return null; + } + + protected Class loadClass(String name) { + // log.debug("Class loader: " + classLoader); + try { + return classLoader.loadClass(name); + } catch (ClassNotFoundException e) { + throw new ArgeoException("Cannot load class " + name, e); + } + } + + protected String propertyName(String name) { + return name; + } + + public void setVersioning(Boolean versioning) { + this.versioning = versioning; + } + + public void setUuidProperty(String uuidProperty) { + this.uuidProperty = uuidProperty; + } + + public void setClassProperty(String classProperty) { + this.classProperty = classProperty; + } + + public void setStrictUuidReference(Boolean strictUuidReference) { + this.strictUuidReference = strictUuidReference; + } + + public void setPrimaryNodeType(String primaryNodeType) { + this.primaryNodeType = primaryNodeType; + } + + public void setClassLoader(ClassLoader classLoader) { + this.classLoader = classLoader; + } + + public void setNodeMapperProvider(NodeMapperProvider nodeMapperProvider) { + this.nodeMapperProvider = nodeMapperProvider; + } + + public String getPrimaryNodeType() { + return this.primaryNodeType; + } + + public String getClassProperty() { + return this.classProperty; + } +} diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/spring/ThreadBoundSession.java b/org.argeo.server.jcr/src/org/argeo/jcr/spring/ThreadBoundSession.java new file mode 100644 index 000000000..a46bef1fe --- /dev/null +++ b/org.argeo.server.jcr/src/org/argeo/jcr/spring/ThreadBoundSession.java @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * 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.spring; + +import org.argeo.jcr.ThreadBoundJcrSessionFactory; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.beans.factory.InitializingBean; + +public class ThreadBoundSession extends ThreadBoundJcrSessionFactory implements FactoryBean, InitializingBean, DisposableBean{ + public void afterPropertiesSet() throws Exception { + init(); + } + + public void destroy() throws Exception { + dispose(); + } + +} diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/tabular/JcrTabularRowIterator.java b/org.argeo.server.jcr/src/org/argeo/jcr/tabular/JcrTabularRowIterator.java new file mode 100644 index 000000000..ca0635c95 --- /dev/null +++ b/org.argeo.server.jcr/src/org/argeo/jcr/tabular/JcrTabularRowIterator.java @@ -0,0 +1,185 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * 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.tabular; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; + +import javax.jcr.Binary; +import javax.jcr.Node; +import javax.jcr.NodeIterator; +import javax.jcr.Property; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; + +import org.apache.commons.io.IOUtils; +import org.argeo.ArgeoException; +import org.argeo.jcr.ArgeoTypes; +import org.argeo.util.CsvParser; +import org.argeo.util.tabular.ArrayTabularRow; +import org.argeo.util.tabular.TabularColumn; +import org.argeo.util.tabular.TabularRow; +import org.argeo.util.tabular.TabularRowIterator; + +/** Iterates over the rows of a {@link ArgeoTypes#ARGEO_TABLE} node. */ +public class JcrTabularRowIterator implements TabularRowIterator { + private Boolean hasNext = null; + private Boolean parsingCompleted = false; + + private Long currentRowNumber = 0l; + + private List header = new ArrayList(); + + /** referenced so that we can close it */ + private Binary binary; + private InputStream in; + + private CsvParser csvParser; + private ArrayBlockingQueue> textLines; + + public JcrTabularRowIterator(Node tableNode) { + try { + for (NodeIterator it = tableNode.getNodes(); it.hasNext();) { + Node node = it.nextNode(); + if (node.isNodeType(ArgeoTypes.ARGEO_COLUMN)) { + Integer type = PropertyType.valueFromName(node.getProperty( + Property.JCR_REQUIRED_TYPE).getString()); + TabularColumn tc = new TabularColumn(node.getProperty( + Property.JCR_TITLE).getString(), type); + header.add(tc); + } + } + Node contentNode = tableNode.getNode(Property.JCR_CONTENT); + if (contentNode.isNodeType(ArgeoTypes.ARGEO_CSV)) { + textLines = new ArrayBlockingQueue>(1000); + csvParser = new CsvParser() { + protected void processLine(Integer lineNumber, + List header, List tokens) { + try { + textLines.put(tokens); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + // textLines.add(tokens); + if (hasNext == null) { + hasNext = true; + synchronized (JcrTabularRowIterator.this) { + JcrTabularRowIterator.this.notifyAll(); + } + } + } + }; + csvParser.setNoHeader(true); + binary = contentNode.getProperty(Property.JCR_DATA).getBinary(); + in = binary.getStream(); + Thread thread = new Thread(contentNode.getPath() + " reader") { + public void run() { + try { + csvParser.parse(in); + } finally { + parsingCompleted = true; + IOUtils.closeQuietly(in); + } + } + }; + thread.start(); + } + } catch (RepositoryException e) { + throw new ArgeoException("Cannot read table " + tableNode, e); + } + } + + public synchronized boolean hasNext() { + // we don't know if there is anything available + // while (hasNext == null) + // try { + // wait(); + // } catch (InterruptedException e) { + // // silent + // // FIXME better deal with interruption + // Thread.currentThread().interrupt(); + // break; + // } + + // buffer not empty + if (!textLines.isEmpty()) + return true; + + // maybe the parsing is finished but the flag has not been set + while (!parsingCompleted && textLines.isEmpty()) + try { + wait(100); + } catch (InterruptedException e) { + // silent + // FIXME better deal with interruption + Thread.currentThread().interrupt(); + break; + } + + // buffer not empty + if (!textLines.isEmpty()) + return true; + + // (parsingCompleted && textLines.isEmpty()) + return false; + + // if (!hasNext && textLines.isEmpty()) { + // if (in != null) { + // IOUtils.closeQuietly(in); + // in = null; + // } + // if (binary != null) { + // JcrUtils.closeQuietly(binary); + // binary = null; + // } + // return false; + // } else + // return true; + } + + public synchronized TabularRow next() { + try { + List tokens = textLines.take(); + List objs = new ArrayList(tokens.size()); + for (String token : tokens) { + // TODO convert to other formats using header + objs.add(token); + } + currentRowNumber++; + return new ArrayTabularRow(objs); + } catch (InterruptedException e) { + // silent + // FIXME better deal with interruption + } + return null; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + public Long getCurrentRowNumber() { + return currentRowNumber; + } + + public List getHeader() { + return header; + } + +} diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/tabular/JcrTabularWriter.java b/org.argeo.server.jcr/src/org/argeo/jcr/tabular/JcrTabularWriter.java new file mode 100644 index 000000000..718ff2374 --- /dev/null +++ b/org.argeo.server.jcr/src/org/argeo/jcr/tabular/JcrTabularWriter.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * 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.tabular; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.InputStream; +import java.util.List; + +import javax.jcr.Binary; +import javax.jcr.Node; +import javax.jcr.Property; +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; + +import org.apache.commons.io.IOUtils; +import org.argeo.ArgeoException; +import org.argeo.jcr.ArgeoTypes; +import org.argeo.jcr.JcrUtils; +import org.argeo.util.CsvWriter; +import org.argeo.util.tabular.TabularColumn; +import org.argeo.util.tabular.TabularWriter; + +/** Write / reference tabular content in a JCR repository. */ +public class JcrTabularWriter implements TabularWriter { + private Node contentNode; + private ByteArrayOutputStream out; + private CsvWriter csvWriter; + + @SuppressWarnings("unused") + private final List columns; + + /** Creates a table node */ + public JcrTabularWriter(Node tableNode, List columns, + String contentNodeType) { + try { + this.columns = columns; + for (TabularColumn column : columns) { + String normalized = JcrUtils.replaceInvalidChars(column + .getName()); + Node columnNode = tableNode.addNode(normalized, + ArgeoTypes.ARGEO_COLUMN); + columnNode.setProperty(Property.JCR_TITLE, column.getName()); + if (column.getType() != null) + columnNode.setProperty(Property.JCR_REQUIRED_TYPE, + PropertyType.nameFromValue(column.getType())); + else + columnNode.setProperty(Property.JCR_REQUIRED_TYPE, + PropertyType.TYPENAME_STRING); + } + contentNode = tableNode.addNode(Property.JCR_CONTENT, + contentNodeType); + if (contentNodeType.equals(ArgeoTypes.ARGEO_CSV)) { + contentNode.setProperty(Property.JCR_MIMETYPE, "text/csv"); + contentNode.setProperty(Property.JCR_ENCODING, "UTF-8"); + out = new ByteArrayOutputStream(); + csvWriter = new CsvWriter(out); + } + } catch (RepositoryException e) { + throw new ArgeoException("Cannot create table node " + tableNode, e); + } + } + + public void appendRow(Object[] row) { + csvWriter.writeLine(row); + } + + public void close() { + Binary binary = null; + InputStream in = null; + try { + // TODO parallelize with pipes and writing from another thread + in = new ByteArrayInputStream(out.toByteArray()); + binary = contentNode.getSession().getValueFactory() + .createBinary(in); + contentNode.setProperty(Property.JCR_DATA, binary); + } catch (RepositoryException e) { + throw new ArgeoException("Cannot store data in " + contentNode, e); + } finally { + IOUtils.closeQuietly(in); + JcrUtils.closeQuietly(binary); + } + } +} diff --git a/org.argeo.server.jcr/src/org/argeo/jcr/unit/AbstractJcrTestCase.java b/org.argeo.server.jcr/src/org/argeo/jcr/unit/AbstractJcrTestCase.java new file mode 100644 index 000000000..530605eac --- /dev/null +++ b/org.argeo.server.jcr/src/org/argeo/jcr/unit/AbstractJcrTestCase.java @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2007-2012 Argeo GmbH + * + * 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.unit; + +import java.io.File; + +import javax.jcr.Repository; +import javax.jcr.Session; +import javax.jcr.SimpleCredentials; + +import junit.framework.TestCase; + +import org.apache.commons.io.FileUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.argeo.ArgeoException; + +public abstract class AbstractJcrTestCase extends TestCase { + private final static Log log = LogFactory.getLog(AbstractJcrTestCase.class); + + private Repository repository; + private Session session = null; + + protected abstract File getRepositoryFile() throws Exception; + + protected abstract Repository createRepository() throws Exception; + + @Override + protected void setUp() throws Exception { + File homeDir = getHomeDir(); + FileUtils.deleteDirectory(homeDir); + repository = createRepository(); + } + + protected File getHomeDir() { + File homeDir = new File(System.getProperty("java.io.tmpdir"), + AbstractJcrTestCase.class.getSimpleName() + "-" + + System.getProperty("user.name")); + return homeDir; + } + + @Override + protected void tearDown() throws Exception { + if (session != null) { + session.logout(); + if (log.isDebugEnabled()) + log.debug("Logout session"); + } + } + + protected Session session() { + if (session == null) { + try { + if (log.isDebugEnabled()) + log.debug("Login session"); + session = getRepository().login( + new SimpleCredentials("demo", "demo".toCharArray())); + } catch (Exception e) { + throw new ArgeoException("Cannot login to repository", e); + } + } + return session; + } + + protected Repository getRepository() { + return repository; + } + + /** + * enables children class to set an existing repository in case it is not + * deleted on startup, to test migration by instance + */ + protected void setRepository(Repository repository) { + this.repository = repository; + } +}