<?xml version="1.0" encoding="UTF-8"?>
<classpath>
- <classpathentry kind="src" path="newsrc" />
- <classpathentry kind="src" path="ext/test" />
- <classpathentry kind="con"
- path="org.eclipse.pde.core.requiredPlugins" />
- <classpathentry kind="con"
- path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6" />
- <classpathentry kind="output" path="bin" />
+ <classpathentry kind="src" path="src"/>
+ <classpathentry kind="src" path="ext/test"/>
+ <classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+ <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/>
+ <classpathentry kind="output" path="bin"/>
</classpath>
+++ /dev/null
-/*
- * 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";
-
-}
+++ /dev/null
-/*
- * 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<String, String> parameters = new HashMap<String, String>();
- 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<String, String> parameters = new HashMap<String, String>();
- 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() {
- }
-
-}
+++ /dev/null
-/*
- * 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";
-}
+++ /dev/null
-/*
- * 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";
-
-}
+++ /dev/null
-/*
- * 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<Node> iterator;
- private Integer position = 0;
-
- public CollectionNodeIterator(Collection<Node> 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;
- }
-
-}
+++ /dev/null
-/*
- * 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;
- }
-
-}
+++ /dev/null
-/*
- * 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);
- }
-
-}
+++ /dev/null
-/*
- * 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<String, Repository> repositories = Collections
- .unmodifiableMap(new TreeMap<String, Repository>());
-
- @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<String, Repository> 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<String, Repository> map = new TreeMap<String, Repository>(
- 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<String, Repository> map = new TreeMap<String, Repository>(
- repositories);
- map.put(alias, repository);
- repositories = Collections.unmodifiableMap(map);
- setChanged();
- notifyObservers(alias);
- }
-}
+++ /dev/null
-/*
- * 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);
-}
+++ /dev/null
-/*
- * 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;
- }
-
-}
+++ /dev/null
-/*
- * 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<Calendar> listVersions(String path) {
- if (!versioning)
- throw new ArgeoException("Versioning is not activated");
-
- try {
- List<Calendar> versions = new ArrayList<Calendar>();
- 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;
- }
-
-}
+++ /dev/null
-/*
- * 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();
- }
-
- };
- }
-
-}
+++ /dev/null
-/*
- * 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<String> 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<Node> nodeIteratorToList(NodeIterator nodeIterator) {
- List<Node> nodes = new ArrayList<Node>();
- 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<String> 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<String> 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<String> tokenize(String path) {
- List<String> tokens = new ArrayList<String>();
- 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<String, PropertyDiff> diffProperties(Node reference,
- Node observed) {
- Map<String, PropertyDiff> diffs = new TreeMap<String, PropertyDiff>();
- 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<String, PropertyDiff> 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<String, PropertyDiff> diffProperties(Node reference,
- Node observed, List<String> properties) {
- Map<String, PropertyDiff> diffs = new TreeMap<String, PropertyDiff>();
- try {
- Iterator<String> 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 <code>false</code>, 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, <a
- * href="https://issues.apache.org/jira/browse/JCR-2233">these properties
- * are not automatically updated</a>, 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 <a
- * href="http://jackrabbit.apache.org/node-type-notation.html"> Node type
- * Notation </a> 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<Privilege> privileges = new ArrayList<Privilege>();
- 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<Privilege> 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);
- }
- }
-
-}
+++ /dev/null
-package org.argeo.jcr;
-
-import javax.jcr.Repository;
-
-/** Abstracts maintenance operations on a {@link Repository} */
-public interface MaintainedRepository extends Repository {
-
-}
+++ /dev/null
-/*
- * 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);
-}
+++ /dev/null
-/*
- * 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);
-}
+++ /dev/null
-/*
- * 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;
- }
-
-}
+++ /dev/null
-/*
- * 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<String, Repository> getRepositories();
-}
+++ /dev/null
-/*
- * 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<Repository> repositories;
-
- private ThreadLocal<Session> session = new ThreadLocal<Session>();
- 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<Thread> threads = Collections
- .synchronizedList(new ArrayList<Thread>());
- private final Map<Long, Session> activeSessions = Collections
- .synchronizedMap(new HashMap<Long, Session>());
- 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<Thread> 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<? extends Session> 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<Repository> 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<Repository> 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();
- }
- }
-
- }
-}
+++ /dev/null
-/*
- * 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() {
- }
-}
+++ /dev/null
-/*
- * 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<String, PropertyDiff> diffs;
- private Calendar updateTime;
-
- public VersionDiff(String userId, Calendar updateTime,
- Map<String, PropertyDiff> diffs) {
- this.userId = userId;
- this.updateTime = updateTime;
- this.diffs = diffs;
- }
-
- public String getUserId() {
- return userId;
- }
-
- public Map<String, PropertyDiff> getDiffs() {
- return diffs;
- }
-
- public Calendar getUpdateTime() {
- return updateTime;
- }
-}
+++ /dev/null
-<argeo = 'http://www.argeo.org/ns/argeo'>
-
-// 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)
-
+++ /dev/null
-/*
- * 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;
- }
-
-}
+++ /dev/null
-/*
- * 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, <code>null</code> if the resource was not found
- * (e.g. HTTP 404)
- */
- public Node proxy(String relativePath);
-}
+++ /dev/null
-/*
- * 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<br/>
- * value := group1,group2,user1
- */
- private Map<String, String> principalPrivileges = new HashMap<String, String>();
-
- 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<Privilege> privs = new ArrayList<Privilege>();
- 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<Privilege> 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<String, String> groupPrivileges) {
- this.principalPrivileges = groupPrivileges;
- }
-
- public void setPrincipalPrivileges(Map<String, String> 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;
- }
-
-}
+++ /dev/null
-/*
- * 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<Object> lst = new ArrayList<Object>();
- 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<Object> lst;
- try {
- lst = (List<Object>) loadClass(lstClass).newInstance();
- } catch (Exception e) {
- lst = new ArrayList<Object>();
- }
-
- 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<Object, Object> map;
- try {
- map = (Map<Object, Object>) loadClass(mapClass)
- .newInstance();
- } catch (Exception e) {
- map = new HashMap<Object, Object>();
- }
-
- // 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;
- }
-}
+++ /dev/null
-/*
- * 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();
- }
-
-}
+++ /dev/null
-/*
- * 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<TabularColumn> header = new ArrayList<TabularColumn>();
-
- /** referenced so that we can close it */
- private Binary binary;
- private InputStream in;
-
- private CsvParser csvParser;
- private ArrayBlockingQueue<List<String>> 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<List<String>>(1000);
- csvParser = new CsvParser() {
- protected void processLine(Integer lineNumber,
- List<String> header, List<String> 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<String> tokens = textLines.take();
- List<Object> objs = new ArrayList<Object>(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<TabularColumn> getHeader() {
- return header;
- }
-
-}
+++ /dev/null
-/*
- * 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<TabularColumn> columns;
-
- /** Creates a table node */
- public JcrTabularWriter(Node tableNode, List<TabularColumn> 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);
- }
- }
-}
+++ /dev/null
-/*
- * 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;
- }
-}
--- /dev/null
+/*
+ * 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";
+
+}
--- /dev/null
+/*
+ * 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<String, String> parameters = new HashMap<String, String>();
+ 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<String, String> parameters = new HashMap<String, String>();
+ 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() {
+ }
+
+}
--- /dev/null
+/*
+ * 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";
+}
--- /dev/null
+/*
+ * 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";
+
+}
--- /dev/null
+/*
+ * 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<Node> iterator;
+ private Integer position = 0;
+
+ public CollectionNodeIterator(Collection<Node> 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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);
+ }
+
+}
--- /dev/null
+/*
+ * 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<String, Repository> repositories = Collections
+ .unmodifiableMap(new TreeMap<String, Repository>());
+
+ @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<String, Repository> 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<String, Repository> map = new TreeMap<String, Repository>(
+ 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<String, Repository> map = new TreeMap<String, Repository>(
+ repositories);
+ map.put(alias, repository);
+ repositories = Collections.unmodifiableMap(map);
+ setChanged();
+ notifyObservers(alias);
+ }
+}
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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<Calendar> listVersions(String path) {
+ if (!versioning)
+ throw new ArgeoException("Versioning is not activated");
+
+ try {
+ List<Calendar> versions = new ArrayList<Calendar>();
+ 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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();
+ }
+
+ };
+ }
+
+}
--- /dev/null
+/*
+ * 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<String> 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<Node> nodeIteratorToList(NodeIterator nodeIterator) {
+ List<Node> nodes = new ArrayList<Node>();
+ 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<String> 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<String> 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<String> tokenize(String path) {
+ List<String> tokens = new ArrayList<String>();
+ 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<String, PropertyDiff> diffProperties(Node reference,
+ Node observed) {
+ Map<String, PropertyDiff> diffs = new TreeMap<String, PropertyDiff>();
+ 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<String, PropertyDiff> 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<String, PropertyDiff> diffProperties(Node reference,
+ Node observed, List<String> properties) {
+ Map<String, PropertyDiff> diffs = new TreeMap<String, PropertyDiff>();
+ try {
+ Iterator<String> 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 <code>false</code>, 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, <a
+ * href="https://issues.apache.org/jira/browse/JCR-2233">these properties
+ * are not automatically updated</a>, 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 <a
+ * href="http://jackrabbit.apache.org/node-type-notation.html"> Node type
+ * Notation </a> 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<Privilege> privileges = new ArrayList<Privilege>();
+ 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<Privilege> 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);
+ }
+ }
+
+}
--- /dev/null
+package org.argeo.jcr;
+
+import javax.jcr.Repository;
+
+/** Abstracts maintenance operations on a {@link Repository} */
+public interface MaintainedRepository extends Repository {
+
+}
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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);
+}
--- /dev/null
+/*
+ * 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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<String, Repository> getRepositories();
+}
--- /dev/null
+/*
+ * 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<Repository> repositories;
+
+ private ThreadLocal<Session> session = new ThreadLocal<Session>();
+ 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<Thread> threads = Collections
+ .synchronizedList(new ArrayList<Thread>());
+ private final Map<Long, Session> activeSessions = Collections
+ .synchronizedMap(new HashMap<Long, Session>());
+ 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<Thread> 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<? extends Session> 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<Repository> 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<Repository> 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();
+ }
+ }
+
+ }
+}
--- /dev/null
+/*
+ * 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() {
+ }
+}
--- /dev/null
+/*
+ * 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<String, PropertyDiff> diffs;
+ private Calendar updateTime;
+
+ public VersionDiff(String userId, Calendar updateTime,
+ Map<String, PropertyDiff> diffs) {
+ this.userId = userId;
+ this.updateTime = updateTime;
+ this.diffs = diffs;
+ }
+
+ public String getUserId() {
+ return userId;
+ }
+
+ public Map<String, PropertyDiff> getDiffs() {
+ return diffs;
+ }
+
+ public Calendar getUpdateTime() {
+ return updateTime;
+ }
+}
--- /dev/null
+<argeo = 'http://www.argeo.org/ns/argeo'>
+
+// 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)
+
--- /dev/null
+/*
+ * 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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, <code>null</code> if the resource was not found
+ * (e.g. HTTP 404)
+ */
+ public Node proxy(String relativePath);
+}
--- /dev/null
+/*
+ * 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<br/>
+ * value := group1,group2,user1
+ */
+ private Map<String, String> principalPrivileges = new HashMap<String, String>();
+
+ 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<Privilege> privs = new ArrayList<Privilege>();
+ 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<Privilege> 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<String, String> groupPrivileges) {
+ this.principalPrivileges = groupPrivileges;
+ }
+
+ public void setPrincipalPrivileges(Map<String, String> 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;
+ }
+
+}
--- /dev/null
+/*
+ * 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<Object> lst = new ArrayList<Object>();
+ 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<Object> lst;
+ try {
+ lst = (List<Object>) loadClass(lstClass).newInstance();
+ } catch (Exception e) {
+ lst = new ArrayList<Object>();
+ }
+
+ 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<Object, Object> map;
+ try {
+ map = (Map<Object, Object>) loadClass(mapClass)
+ .newInstance();
+ } catch (Exception e) {
+ map = new HashMap<Object, Object>();
+ }
+
+ // 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;
+ }
+}
--- /dev/null
+/*
+ * 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();
+ }
+
+}
--- /dev/null
+/*
+ * 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<TabularColumn> header = new ArrayList<TabularColumn>();
+
+ /** referenced so that we can close it */
+ private Binary binary;
+ private InputStream in;
+
+ private CsvParser csvParser;
+ private ArrayBlockingQueue<List<String>> 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<List<String>>(1000);
+ csvParser = new CsvParser() {
+ protected void processLine(Integer lineNumber,
+ List<String> header, List<String> 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<String> tokens = textLines.take();
+ List<Object> objs = new ArrayList<Object>(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<TabularColumn> getHeader() {
+ return header;
+ }
+
+}
--- /dev/null
+/*
+ * 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<TabularColumn> columns;
+
+ /** Creates a table node */
+ public JcrTabularWriter(Node tableNode, List<TabularColumn> 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);
+ }
+ }
+}
--- /dev/null
+/*
+ * 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;
+ }
+}