001/* 002 * ModeShape (http://www.modeshape.org) 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.modeshape.cmis; 017 018import java.math.BigInteger; 019import java.util.ArrayList; 020import java.util.HashMap; 021import java.util.List; 022import java.util.Map; 023import java.util.Set; 024import javax.jcr.Credentials; 025import javax.jcr.Session; 026import javax.jcr.SimpleCredentials; 027import javax.servlet.http.HttpServletRequest; 028import org.apache.chemistry.opencmis.commons.data.Acl; 029import org.apache.chemistry.opencmis.commons.data.AllowableActions; 030import org.apache.chemistry.opencmis.commons.data.ContentStream; 031import org.apache.chemistry.opencmis.commons.data.ExtensionsData; 032import org.apache.chemistry.opencmis.commons.data.FailedToDeleteData; 033import org.apache.chemistry.opencmis.commons.data.ObjectData; 034import org.apache.chemistry.opencmis.commons.data.ObjectInFolderContainer; 035import org.apache.chemistry.opencmis.commons.data.ObjectInFolderList; 036import org.apache.chemistry.opencmis.commons.data.ObjectList; 037import org.apache.chemistry.opencmis.commons.data.ObjectParentData; 038import org.apache.chemistry.opencmis.commons.data.Properties; 039import org.apache.chemistry.opencmis.commons.data.RepositoryInfo; 040import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition; 041import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionContainer; 042import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionList; 043import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships; 044import org.apache.chemistry.opencmis.commons.enums.UnfileObject; 045import org.apache.chemistry.opencmis.commons.enums.VersioningState; 046import org.apache.chemistry.opencmis.commons.exceptions.CmisInvalidArgumentException; 047import org.apache.chemistry.opencmis.commons.exceptions.CmisPermissionDeniedException; 048import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException; 049import org.apache.chemistry.opencmis.commons.impl.server.AbstractCmisService; 050import org.apache.chemistry.opencmis.commons.server.CallContext; 051import org.apache.chemistry.opencmis.commons.spi.Holder; 052import org.apache.chemistry.opencmis.jcr.JcrRepository; 053import org.apache.chemistry.opencmis.server.support.wrapper.CallContextAwareCmisService; 054import org.modeshape.common.util.CheckArg; 055import org.modeshape.jcr.api.ServletCredentials; 056import org.modeshape.web.jcr.WebLogger; 057 058/** 059 * JCR service implementation. 060 * 061 * @author kulikov 062 */ 063public class JcrService extends AbstractCmisService implements CallContextAwareCmisService { 064 065 private final static org.modeshape.jcr.api.Logger LOGGER = WebLogger.getLogger(JcrService.class); 066 private final Map<String, JcrRepository> jcrRepositories; 067 private final Map<String, Session> sessions = new HashMap<String, Session>(); 068 private CallContext context; 069 070 public JcrService(Map<String, JcrRepository> jcrRepositories) { 071 this.jcrRepositories = jcrRepositories; 072 } 073 074 @Override 075 public void close() { 076 for (Session session : sessions.values()) { 077 session.logout(); 078 } 079 080 super.close(); 081 } 082 083 @Override 084 public void setCallContext(CallContext context) { 085 this.context = context; 086 } 087 088 @Override 089 public CallContext getCallContext() { 090 return this.context; 091 } 092 093 //------------------------------------------< repository service >--- 094 @Override 095 public RepositoryInfo getRepositoryInfo(String repositoryId, ExtensionsData extension) { 096 LOGGER.debug("-- getting repository info"); 097 RepositoryInfo info = jcrRepository(repositoryId).getRepositoryInfo(login(repositoryId)); 098 return new RepositoryInfoLocal(repositoryId, info); 099 } 100 101 @Override 102 public List<RepositoryInfo> getRepositoryInfos(ExtensionsData extension) { 103 ArrayList<RepositoryInfo> info = new ArrayList<>(); 104 Set<String> IDs = jcrRepositories.keySet(); 105 for (String Id : IDs) { 106 JcrRepository repo = jcrRepositories.get(Id); 107 List<RepositoryInfo> infos = repo.getRepositoryInfos(login(Id)); 108 for (RepositoryInfo i : infos) { 109 info.add(new RepositoryInfoLocal(Id, i)); 110 } 111 } 112 return info; 113 } 114 115 @Override 116 public TypeDefinitionList getTypeChildren(String repositoryId, String typeId, Boolean includePropertyDefinitions, 117 BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) { 118 119 return jcrRepository(repositoryId).getTypeChildren(login(repositoryId), typeId, includePropertyDefinitions, maxItems, skipCount); 120 } 121 122 @Override 123 public TypeDefinition getTypeDefinition(String repositoryId, String typeId, ExtensionsData extension) { 124 return jcrRepository(repositoryId).getTypeDefinition(login(repositoryId), typeId); 125 } 126 127 @Override 128 public List<TypeDefinitionContainer> getTypeDescendants(String repositoryId, String typeId, BigInteger depth, 129 Boolean includePropertyDefinitions, ExtensionsData extension) { 130 131 return jcrRepository(repositoryId).getTypesDescendants(login(repositoryId), typeId, depth, includePropertyDefinitions); 132 } 133 134 //------------------------------------------< navigation service >--- 135 @Override 136 public ObjectInFolderList getChildren(String repositoryId, String folderId, String filter, String orderBy, 137 Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, 138 Boolean includePathSegment, BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) { 139 140 return jcrRepository(repositoryId).getChildren(login(repositoryId), folderId, filter, includeAllowableActions, 141 includePathSegment, maxItems, skipCount, this, context.isObjectInfoRequired()); 142 } 143 144 @Override 145 public List<ObjectInFolderContainer> getDescendants(String repositoryId, String folderId, BigInteger depth, 146 String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships, 147 String renditionFilter, Boolean includePathSegment, ExtensionsData extension) { 148 149 return jcrRepository(repositoryId).getDescendants(login(repositoryId), folderId, depth, filter, includeAllowableActions, 150 includePathSegment, this, context.isObjectInfoRequired(), false); 151 } 152 153 @Override 154 public ObjectData getFolderParent(String repositoryId, String folderId, String filter, ExtensionsData extension) { 155 return jcrRepository(repositoryId).getFolderParent(login(repositoryId), folderId, filter, this, context.isObjectInfoRequired()); 156 } 157 158 @Override 159 public List<ObjectInFolderContainer> getFolderTree(String repositoryId, String folderId, BigInteger depth, 160 String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships, 161 String renditionFilter, Boolean includePathSegment, ExtensionsData extension) { 162 163 return jcrRepository(repositoryId).getDescendants(login(repositoryId), folderId, depth, filter, includeAllowableActions, 164 includePathSegment, this, context.isObjectInfoRequired(), true); 165 } 166 167 @Override 168 public List<ObjectParentData> getObjectParents(String repositoryId, String objectId, String filter, 169 Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, 170 Boolean includeRelativePathSegment, ExtensionsData extension) { 171 172 return jcrRepository(repositoryId).getObjectParents(login(repositoryId), objectId, filter, includeAllowableActions, 173 includeRelativePathSegment, this, context.isObjectInfoRequired()); 174 } 175 176 @Override 177 public ObjectList getCheckedOutDocs(String repositoryId, String folderId, String filter, String orderBy, 178 Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, 179 BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) { 180 181 return jcrRepository(repositoryId).getCheckedOutDocs(login(repositoryId), folderId, filter, orderBy, includeAllowableActions, 182 maxItems, skipCount); 183 } 184 185 //------------------------------------------< object service >--- 186 @Override 187 public String createDocument(String repositoryId, Properties properties, String folderId, 188 ContentStream contentStream, VersioningState versioningState, List<String> policies, Acl addAces, 189 Acl removeAces, ExtensionsData extension) { 190 191 return jcrRepository(repositoryId).createDocument(login(repositoryId), properties, folderId, contentStream, versioningState); 192 } 193 194 @Override 195 public String createDocumentFromSource(String repositoryId, String sourceId, Properties properties, 196 String folderId, VersioningState versioningState, List<String> policies, Acl addAces, Acl removeAces, 197 ExtensionsData extension) { 198 199 return jcrRepository(repositoryId).createDocumentFromSource(login(repositoryId), sourceId, properties, folderId, versioningState); 200 } 201 202 @Override 203 public void setContentStream(String repositoryId, Holder<String> objectId, Boolean overwriteFlag, 204 Holder<String> changeToken, ContentStream contentStream, ExtensionsData extension) { 205 206 jcrRepository(repositoryId).setContentStream(login(repositoryId), objectId, overwriteFlag, contentStream); 207 } 208 209 @Override 210 public void deleteContentStream(String repositoryId, Holder<String> objectId, Holder<String> changeToken, 211 ExtensionsData extension) { 212 213 jcrRepository(repositoryId).setContentStream(login(repositoryId), objectId, true, null); 214 } 215 216 @Override 217 public String createFolder(String repositoryId, Properties properties, String folderId, List<String> policies, 218 Acl addAces, Acl removeAces, ExtensionsData extension) { 219 220 return jcrRepository(repositoryId).createFolder(login(repositoryId), properties, folderId); 221 } 222 223 @Override 224 public void deleteObjectOrCancelCheckOut(String repositoryId, String objectId, Boolean allVersions, 225 ExtensionsData extension) { 226 227 jcrRepository(repositoryId).deleteObject(login(repositoryId), objectId, allVersions); 228 } 229 230 @Override 231 public FailedToDeleteData deleteTree(String repositoryId, String folderId, Boolean allVersions, 232 UnfileObject unfileObjects, Boolean continueOnFailure, ExtensionsData extension) { 233 234 return jcrRepository(repositoryId).deleteTree(login(repositoryId), folderId); 235 } 236 237 @Override 238 public AllowableActions getAllowableActions(String repositoryId, String objectId, ExtensionsData extension) { 239 return jcrRepository(repositoryId).getAllowableActions(login(repositoryId), objectId); 240 } 241 242 @Override 243 public ContentStream getContentStream(String repositoryId, String objectId, String streamId, BigInteger offset, 244 BigInteger length, ExtensionsData extension) { 245 246 return jcrRepository(repositoryId).getContentStream(login(repositoryId), objectId, offset, length); 247 } 248 249 @Override 250 public ObjectData getObject(String repositoryId, String objectId, String filter, Boolean includeAllowableActions, 251 IncludeRelationships includeRelationships, String renditionFilter, Boolean includePolicyIds, 252 Boolean includeAcl, ExtensionsData extension) { 253 254 return jcrRepository(repositoryId).getObject(login(repositoryId), objectId, filter, includeAllowableActions, this, 255 context.isObjectInfoRequired()); 256 } 257 258 @Override 259 public ObjectData getObjectByPath(String repositoryId, String path, String filter, Boolean includeAllowableActions, 260 IncludeRelationships includeRelationships, String renditionFilter, Boolean includePolicyIds, 261 Boolean includeAcl, ExtensionsData extension) { 262 263 return jcrRepository(repositoryId).getObjectByPath(login(repositoryId), path, filter, includeAllowableActions, includeAcl, 264 this, context.isObjectInfoRequired()); 265 } 266 267 @Override 268 public Properties getProperties(String repositoryId, String objectId, String filter, ExtensionsData extension) { 269 return jcrRepository(repositoryId).getProperties(login(repositoryId), objectId, filter, false, this, 270 context.isObjectInfoRequired()); 271 } 272 273 @Override 274 public void moveObject(String repositoryId, Holder<String> objectId, String targetFolderId, String sourceFolderId, 275 ExtensionsData extension) { 276 277 jcrRepository(repositoryId).moveObject(login(repositoryId), objectId, targetFolderId, this, context.isObjectInfoRequired()); 278 } 279 280 @Override 281 public void updateProperties(String repositoryId, Holder<String> objectId, Holder<String> changeToken, 282 Properties properties, ExtensionsData extension) { 283 284 jcrRepository(repositoryId).updateProperties(login(repositoryId), objectId, properties, this, context.isObjectInfoRequired()); 285 } 286 287 //------------------------------------------< versioning service >--- 288 @Override 289 public void checkOut(String repositoryId, Holder<String> objectId, ExtensionsData extension, 290 Holder<Boolean> contentCopied) { 291 292 jcrRepository(repositoryId).checkOut(login(repositoryId), objectId, contentCopied); 293 } 294 295 @Override 296 public void cancelCheckOut(String repositoryId, String objectId, ExtensionsData extension) { 297 jcrRepository(repositoryId).cancelCheckout(login(repositoryId), objectId); 298 } 299 300 @Override 301 public void checkIn(String repositoryId, Holder<String> objectId, Boolean major, Properties properties, 302 ContentStream contentStream, String checkinComment, List<String> policies, Acl addAces, Acl removeAces, 303 ExtensionsData extension) { 304 305 jcrRepository(repositoryId).checkIn(login(repositoryId), objectId, major, properties, contentStream, checkinComment); 306 } 307 308 @Override 309 public List<ObjectData> getAllVersions(String repositoryId, String objectId, String versionSeriesId, String filter, 310 Boolean includeAllowableActions, ExtensionsData extension) { 311 312 return jcrRepository(repositoryId).getAllVersions(login(repositoryId), versionSeriesId == null ? objectId : versionSeriesId, 313 filter, includeAllowableActions, this, context.isObjectInfoRequired()); 314 } 315 316 @Override 317 public ObjectData getObjectOfLatestVersion(String repositoryId, String objectId, String versionSeriesId, 318 Boolean major, String filter, Boolean includeAllowableActions, IncludeRelationships includeRelationships, 319 String renditionFilter, Boolean includePolicyIds, Boolean includeAcl, ExtensionsData extension) { 320 321 return jcrRepository(repositoryId).getObject(login(repositoryId), versionSeriesId == null ? objectId : versionSeriesId, 322 filter, includeAllowableActions, this, context.isObjectInfoRequired()); 323 } 324 325 @Override 326 public Properties getPropertiesOfLatestVersion(String repositoryId, String objectId, String versionSeriesId, 327 Boolean major, String filter, ExtensionsData extension) { 328 329 ObjectData object = getObjectOfLatestVersion(repositoryId, objectId, versionSeriesId, major, filter, false, 330 null, null, false, false, extension); 331 332 return object.getProperties(); 333 } 334 335 // --- discovery service --- 336 @Override 337 public ObjectList query(String repositoryId, String statement, Boolean searchAllVersions, 338 Boolean includeAllowableActions, IncludeRelationships includeRelationships, String renditionFilter, 339 BigInteger maxItems, BigInteger skipCount, ExtensionsData extension) { 340 341 return jcrRepository(repositoryId).query(login(repositoryId), statement, searchAllVersions, includeAllowableActions, 342 maxItems, skipCount); 343 } 344 345 //------------------------------------------< protected >--- 346 protected Session login(String repositoryId) { 347 LOGGER.debug("--- login: " + repositoryId); 348 349 if (context == null) { 350 throw new CmisRuntimeException("No user context!"); 351 } 352 353 Session session = sessions.get(repositoryId); 354 if (session == null) { 355 HttpServletRequest request = (HttpServletRequest) context.get(CallContext.HTTP_SERVLET_REQUEST); 356 if (request != null) { 357 //try via http authentication 358 try { 359 session = jcrRepository(repositoryId).login(new ServletCredentials(request), workspace(repositoryId)); 360 sessions.put(repositoryId, session); 361 return session; 362 } catch (CmisPermissionDeniedException e) { 363 LOGGER.debug(e, "Cannot authenticate using http authentication"); 364 } 365 } 366 //http authentication didn't work, try std authentication 367 String userName = context.getUsername(); 368 String password = context.getPassword(); 369 Credentials credentials = (userName == null) 370 ? null : new SimpleCredentials(userName, password == null ? "".toCharArray() : password.toCharArray()); 371 372 try { 373 session = jcrRepository(repositoryId).login(credentials, workspace(repositoryId)); 374 sessions.put(repositoryId, session); 375 } catch (Exception e) { 376 throw new RuntimeException(e); 377 } 378 } 379 return session; 380 } 381 382 private JcrRepository jcrRepository(String repositoryId) { 383 String name = name(repositoryId); 384 JcrRepository repo = jcrRepositories.get(name); 385 if (repo == null) { 386 throw new CmisInvalidArgumentException("Repository lookup failed for \"" + repositoryId + "\" (using name \"" + name + "\")"); 387 } 388 return repo; 389 } 390 391 private String name(String repositoryId) { 392 CheckArg.isNotNull(repositoryId, "repositoryId"); // if can be user-supplied, or 'assert repositoryId != null' if not user supplied 393 repositoryId = repositoryId.trim(); 394 String[] parts = repositoryId.split(":", 2); 395 int numParts = parts.length; 396 397 if (numParts > 1) { 398 return parts[0].trim(); // may be blank 399 } 400 return repositoryId; 401 } 402 403 /** 404 * 405 * @param repositoryId The repositoryId 406 * @return The workspace. If {@code repositoryId} is a single word (i.e. not 407 * colon-separated), then it is assumed that the {@code repositoryId} 408 * argument only represents the repository name and this method will return 409 * {@code null} for the workspace. 410 */ 411 private String workspace(String repositoryId) { 412 CheckArg.isNotNull(repositoryId, "repositoryId"); // if can be user-supplied, or 'assert repositoryId != null' if not user supplied 413 repositoryId = repositoryId.trim(); 414 String[] parts = repositoryId.split(":"); 415 int numParts = parts.length; 416 417 if (numParts > 1) { 418 // Just take the second part 419 return parts[1].trim(); // may be blank 420 } 421 return null; 422 } 423}