/*
* ModeShape (http://www.modeshape.org)
*
* 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.modeshape.cmis;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.jcr.Credentials;
import javax.jcr.Session;
import javax.jcr.SimpleCredentials;
import javax.servlet.http.HttpServletRequest;
import org.apache.chemistry.opencmis.commons.data.Acl;
import org.apache.chemistry.opencmis.commons.data.AllowableActions;
import org.apache.chemistry.opencmis.commons.data.ContentStream;
import org.apache.chemistry.opencmis.commons.data.ExtensionsData;
import org.apache.chemistry.opencmis.commons.data.FailedToDeleteData;
import org.apache.chemistry.opencmis.commons.data.ObjectData;
import org.apache.chemistry.opencmis.commons.data.ObjectInFolderContainer;
import org.apache.chemistry.opencmis.commons.data.ObjectInFolderList;
import org.apache.chemistry.opencmis.commons.data.ObjectList;
import org.apache.chemistry.opencmis.commons.data.ObjectParentData;
import org.apache.chemistry.opencmis.commons.data.Properties;
import org.apache.chemistry.opencmis.commons.data.RepositoryInfo;
import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionContainer;
import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionList;
import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships;
import org.apache.chemistry.opencmis.commons.enums.UnfileObject;
import org.apache.chemistry.opencmis.commons.enums.VersioningState;
import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisPermissionDeniedException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
import org.apache.chemistry.opencmis.commons.impl.server.AbstractCmisService;
import org.apache.chemistry.opencmis.commons.server.CallContext;
import org.apache.chemistry.opencmis.commons.spi.Holder;
import org.apache.chemistry.opencmis.jcr.JcrRepository;
import org.apache.chemistry.opencmis.server.support.wrapper.CallContextAwareCmisService;
import org.modeshape.common.util.CheckArg;
import org.modeshape.jcr.api.ServletCredentials;
import org.modeshape.web.jcr.WebLogger;
/**
* JCR service implementation.
*
* @author kulikov
*/
public class JcrService extends AbstractCmisService implements CallContextAwareCmisService {
private final static org.modeshape.jcr.api.Logger LOGGER = WebLogger.getLogger(JcrService.class);
private final Map<String, JcrRepository> jcrRepositories;
private final Map<String, Session> sessions = new HashMap<String, Session>();
private CallContext context;
public JcrService(Map<String, JcrRepository> jcrRepositories) {
this.jcrRepositories = jcrRepositories;
}
@Override
public void close() {
for (Session session : sessions.values()) {
session.logout();
}
super.close();
}
@Override
public void setCallContext(CallContext context) {
this.context = context;
}
@Override
public CallContext getCallContext() {
return this.context;
}
//------------------------------------------< repository service >---
@Override
public RepositoryInfo getRepositoryInfo(String repositoryId, ExtensionsData extension) {
LOGGER.debug("-- getting repository info");
RepositoryInfo info = jcrRepository(repositoryId).getRepositoryInfo(login(repositoryId));
return new RepositoryInfoLocal(repositoryId, info);
}
@Override
public List<RepositoryInfo> getRepositoryInfos(ExtensionsData extension) {
ArrayList<RepositoryInfo> info = new ArrayList<>();
Set<String> IDs = jcrRepositories.keySet();
for (String Id : IDs) {
JcrRepository repo = jcrRepositories.get(Id);
List<RepositoryInfo> infos = repo.getRepositoryInfos(login(Id));
for (RepositoryInfo i : infos) {
info.add(new RepositoryInfoLocal(Id, i));
}
}
return info;
}
@Override
public TypeDefinitionList getTypeChildren(String repositoryId, String typeId, Boolean includePropertyDefinitions,
BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) {
return jcrRepository(repositoryId).getTypeChildren(login(repositoryId), typeId, includePropertyDefinitions, maxItems, skipCount);
}
@Override
public TypeDefinition getTypeDefinition(String repositoryId, String typeId, ExtensionsData extension) {
return jcrRepository(repositoryId).getTypeDefinition(login(repositoryId), typeId);
}
@Override
public List<TypeDefinitionContainer> getTypeDescendants(String repositoryId, String typeId, BigInteger depth,
Boolean includePropertyDefinitions, ExtensionsData extension) {
return jcrRepository(repositoryId).getTypesDescendants(login(repositoryId), typeId, depth, includePropertyDefinitions);
}
//------------------------------------------< navigation service >---
@Override
public ObjectInFolderList getChildren(String repositoryId, String folderId, String filter, String orderBy,
Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter,
Boolean includePathSegment, BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) {
return jcrRepository(repositoryId).getChildren(login(repositoryId), folderId, filter, includeAllowableActions,
includePathSegment, maxItems, skipCount, this, context.isObjectInfoRequired());
}
@Override
public List<ObjectInFolderContainer> getDescendants(String repositoryId, String folderId, BigInteger depth,
String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships,
String renditionFilter, Boolean includePathSegment, ExtensionsData extension) {
return jcrRepository(repositoryId).getDescendants(login(repositoryId), folderId, depth, filter, includeAllowableActions,
includePathSegment, this, context.isObjectInfoRequired(), false);
}
@Override
public ObjectData getFolderParent(String repositoryId, String folderId, String filter, ExtensionsData extension) {
return jcrRepository(repositoryId).getFolderParent(login(repositoryId), folderId, filter, this, context.isObjectInfoRequired());
}
@Override
public List<ObjectInFolderContainer> getFolderTree(String repositoryId, String folderId, BigInteger depth,
String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships,
String renditionFilter, Boolean includePathSegment, ExtensionsData extension) {
return jcrRepository(repositoryId).getDescendants(login(repositoryId), folderId, depth, filter, includeAllowableActions,
includePathSegment, this, context.isObjectInfoRequired(), true);
}
@Override
public List<ObjectParentData> getObjectParents(String repositoryId, String objectId, String filter,
Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter,
Boolean includeRelativePathSegment, ExtensionsData extension) {
return jcrRepository(repositoryId).getObjectParents(login(repositoryId), objectId, filter, includeAllowableActions,
includeRelativePathSegment, this, context.isObjectInfoRequired());
}
@Override
public ObjectList getCheckedOutDocs(String repositoryId, String folderId, String filter, String orderBy,
Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter,
BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) {
return jcrRepository(repositoryId).getCheckedOutDocs(login(repositoryId), folderId, filter, orderBy, includeAllowableActions,
maxItems, skipCount);
}
//------------------------------------------< object service >---
@Override
public String createDocument(String repositoryId, Properties properties, String folderId,
ContentStream contentStream, VersioningState versioningState, List<String> policies, Acl addAces,
Acl removeAces, ExtensionsData extension) {
return jcrRepository(repositoryId).createDocument(login(repositoryId), properties, folderId, contentStream, versioningState);
}
@Override
public String createDocumentFromSource(String repositoryId, String sourceId, Properties properties,
String folderId, VersioningState versioningState, List<String> policies, Acl addAces, Acl removeAces,
ExtensionsData extension) {
return jcrRepository(repositoryId).createDocumentFromSource(login(repositoryId), sourceId, properties, folderId, versioningState);
}
@Override
public void setContentStream(String repositoryId, Holder<String> objectId, Boolean overwriteFlag,
Holder<String> changeToken, ContentStream contentStream, ExtensionsData extension) {
jcrRepository(repositoryId).setContentStream(login(repositoryId), objectId, overwriteFlag, contentStream);
}
@Override
public void deleteContentStream(String repositoryId, Holder<String> objectId, Holder<String> changeToken,
ExtensionsData extension) {
jcrRepository(repositoryId).setContentStream(login(repositoryId), objectId, true, null);
}
@Override
public String createFolder(String repositoryId, Properties properties, String folderId, List<String> policies,
Acl addAces, Acl removeAces, ExtensionsData extension) {
return jcrRepository(repositoryId).createFolder(login(repositoryId), properties, folderId);
}
@Override
public void deleteObjectOrCancelCheckOut(String repositoryId, String objectId, Boolean allVersions,
ExtensionsData extension) {
jcrRepository(repositoryId).deleteObject(login(repositoryId), objectId, allVersions);
}
@Override
public FailedToDeleteData deleteTree(String repositoryId, String folderId, Boolean allVersions,
UnfileObject unfileObjects, Boolean continueOnFailure, ExtensionsData extension) {
return jcrRepository(repositoryId).deleteTree(login(repositoryId), folderId);
}
@Override
public AllowableActions getAllowableActions(String repositoryId, String objectId, ExtensionsData extension) {
return jcrRepository(repositoryId).getAllowableActions(login(repositoryId), objectId);
}
@Override
public ContentStream getContentStream(String repositoryId, String objectId, String streamId, BigInteger offset,
BigInteger length, ExtensionsData extension) {
return jcrRepository(repositoryId).getContentStream(login(repositoryId), objectId, offset, length);
}
@Override
public ObjectData getObject(String repositoryId, String objectId, String filter, Boolean includeAllowableActions,
IncludeRelationships includeRelationships, String renditionFilter, Boolean includePolicyIds,
Boolean includeAcl, ExtensionsData extension) {
return jcrRepository(repositoryId).getObject(login(repositoryId), objectId, filter, includeAllowableActions, this,
context.isObjectInfoRequired());
}
@Override
public ObjectData getObjectByPath(String repositoryId, String path, String filter, Boolean includeAllowableActions,
IncludeRelationships includeRelationships, String renditionFilter, Boolean includePolicyIds,
Boolean includeAcl, ExtensionsData extension) {
return jcrRepository(repositoryId).getObjectByPath(login(repositoryId), path, filter, includeAllowableActions, includeAcl,
this, context.isObjectInfoRequired());
}
@Override
public Properties getProperties(String repositoryId, String objectId, String filter, ExtensionsData extension) {
return jcrRepository(repositoryId).getProperties(login(repositoryId), objectId, filter, false, this,
context.isObjectInfoRequired());
}
@Override
public void moveObject(String repositoryId, Holder<String> objectId, String targetFolderId, String sourceFolderId,
ExtensionsData extension) {
jcrRepository(repositoryId).moveObject(login(repositoryId), objectId, targetFolderId, this, context.isObjectInfoRequired());
}
@Override
public void updateProperties(String repositoryId, Holder<String> objectId, Holder<String> changeToken,
Properties properties, ExtensionsData extension) {
jcrRepository(repositoryId).updateProperties(login(repositoryId), objectId, properties, this, context.isObjectInfoRequired());
}
//------------------------------------------< versioning service >---
@Override
public void checkOut(String repositoryId, Holder<String> objectId, ExtensionsData extension,
Holder<Boolean> contentCopied) {
jcrRepository(repositoryId).checkOut(login(repositoryId), objectId, contentCopied);
}
@Override
public void cancelCheckOut(String repositoryId, String objectId, ExtensionsData extension) {
jcrRepository(repositoryId).cancelCheckout(login(repositoryId), objectId);
}
@Override
public void checkIn(String repositoryId, Holder<String> objectId, Boolean major, Properties properties,
ContentStream contentStream, String checkinComment, List<String> policies, Acl addAces, Acl removeAces,
ExtensionsData extension) {
jcrRepository(repositoryId).checkIn(login(repositoryId), objectId, major, properties, contentStream, checkinComment);
}
@Override
public List<ObjectData> getAllVersions(String repositoryId, String objectId, String versionSeriesId, String filter,
Boolean includeAllowableActions, ExtensionsData extension) {
return jcrRepository(repositoryId).getAllVersions(login(repositoryId), versionSeriesId == null ? objectId : versionSeriesId,
filter, includeAllowableActions, this, context.isObjectInfoRequired());
}
@Override
public ObjectData getObjectOfLatestVersion(String repositoryId, String objectId, String versionSeriesId,
Boolean major, String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships,
String renditionFilter, Boolean includePolicyIds, Boolean includeAcl, ExtensionsData extension) {
return jcrRepository(repositoryId).getObject(login(repositoryId), versionSeriesId == null ? objectId : versionSeriesId,
filter, includeAllowableActions, this, context.isObjectInfoRequired());
}
@Override
public Properties getPropertiesOfLatestVersion(String repositoryId, String objectId, String versionSeriesId,
Boolean major, String filter, ExtensionsData extension) {
ObjectData object = getObjectOfLatestVersion(repositoryId, objectId, versionSeriesId, major, filter, false,
null, null, false, false, extension);
return object.getProperties();
}
// --- discovery service ---
@Override
public ObjectList query(String repositoryId, String statement, Boolean searchAllVersions,
Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter,
BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) {
return jcrRepository(repositoryId).query(login(repositoryId), statement, searchAllVersions, includeAllowableActions,
maxItems, skipCount);
}
//------------------------------------------< protected >---
protected Session login(String repositoryId) {
LOGGER.debug("--- login: " + repositoryId);
if (context == null) {
throw new CmisRuntimeException("No user context!");
}
Session session = sessions.get(repositoryId);
if (session == null) {
HttpServletRequest request = (HttpServletRequest) context.get(CallContext.HTTP_SERVLET_REQUEST);
if (request != null) {
//try via http authentication
try {
session = jcrRepository(repositoryId).login(new ServletCredentials(request), workspace(repositoryId));
sessions.put(repositoryId, session);
return session;
} catch (CmisPermissionDeniedException e) {
LOGGER.debug(e, "Cannot authenticate using http authentication");
}
}
//http authentication didn't work, try std authentication
String userName = context.getUsername();
String password = context.getPassword();
Credentials credentials = (userName == null)
? null : new SimpleCredentials(userName, password == null ? "".toCharArray() : password.toCharArray());
try {
session = jcrRepository(repositoryId).login(credentials, workspace(repositoryId));
sessions.put(repositoryId, session);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
return session;
}
private JcrRepository jcrRepository(String repositoryId) {
String name = name(repositoryId);
JcrRepository repo = jcrRepositories.get(name);
if (repo == null) {
throw new CmisInvalidArgumentException("Repository lookup failed for \"" + repositoryId + "\" (using name \"" + name + "\")");
}
return repo;
}
private String name(String repositoryId) {
CheckArg.isNotNull(repositoryId, "repositoryId"); // if can be user-supplied, or 'assert repositoryId != null' if not user supplied
repositoryId = repositoryId.trim();
String[] parts = repositoryId.split(":", 2);
int numParts = parts.length;
if (numParts > 1) {
return parts[0].trim(); // may be blank
}
return repositoryId;
}
/**
*
* @param repositoryId The repositoryId
* @return The workspace. If {@code repositoryId} is a single word (i.e. not
* colon-separated), then it is assumed that the {@code repositoryId}
* argument only represents the repository name and this method will return
* {@code null} for the workspace.
*/
private String workspace(String repositoryId) {
CheckArg.isNotNull(repositoryId, "repositoryId"); // if can be user-supplied, or 'assert repositoryId != null' if not user supplied
repositoryId = repositoryId.trim();
String[] parts = repositoryId.split(":");
int numParts = parts.length;
if (numParts > 1) {
// Just take the second part
return parts[1].trim(); // may be blank
}
return null;
}
}