001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (C) Alkacon Software (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.cmis; 029 030import static org.opencms.cmis.CmsCmisUtil.addAction; 031import static org.opencms.cmis.CmsCmisUtil.addPropertyDateTime; 032import static org.opencms.cmis.CmsCmisUtil.addPropertyId; 033import static org.opencms.cmis.CmsCmisUtil.addPropertyString; 034import static org.opencms.cmis.CmsCmisUtil.handleCmsException; 035import static org.opencms.cmis.CmsCmisUtil.millisToCalendar; 036 037import org.opencms.file.CmsObject; 038import org.opencms.file.CmsResource; 039import org.opencms.file.CmsResourceFilter; 040import org.opencms.file.CmsUser; 041import org.opencms.lock.CmsLock; 042import org.opencms.main.CmsException; 043import org.opencms.relations.CmsRelation; 044import org.opencms.relations.CmsRelationFilter; 045import org.opencms.relations.CmsRelationType; 046import org.opencms.security.CmsPermissionSet; 047import org.opencms.util.CmsUUID; 048 049import java.util.ArrayList; 050import java.util.GregorianCalendar; 051import java.util.LinkedHashSet; 052import java.util.List; 053import java.util.Set; 054import java.util.regex.Matcher; 055import java.util.regex.Pattern; 056 057import org.apache.chemistry.opencmis.commons.PropertyIds; 058import org.apache.chemistry.opencmis.commons.data.Ace; 059import org.apache.chemistry.opencmis.commons.data.Acl; 060import org.apache.chemistry.opencmis.commons.data.AllowableActions; 061import org.apache.chemistry.opencmis.commons.data.ObjectData; 062import org.apache.chemistry.opencmis.commons.data.Properties; 063import org.apache.chemistry.opencmis.commons.enums.Action; 064import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; 065import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships; 066import org.apache.chemistry.opencmis.commons.exceptions.CmisBaseException; 067import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException; 068import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException; 069import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlListImpl; 070import org.apache.chemistry.opencmis.commons.impl.dataobjects.AllowableActionsImpl; 071import org.apache.chemistry.opencmis.commons.impl.dataobjects.ObjectDataImpl; 072import org.apache.chemistry.opencmis.commons.impl.dataobjects.PropertiesImpl; 073import org.apache.chemistry.opencmis.commons.impl.server.ObjectInfoImpl; 074 075/** 076 * Helper class for CMIS CRUD operations on relation objects.<p> 077 * 078 * Since CMIS requires any object to have an ID by which it is accessed, but OpenCms relations 079 * are not addressable by ids, we invent an artificial relation id string of the form 080 * REL_(SOURCE_ID)_(TARGET_ID)_(TYPE).<p> 081 * 082 */ 083public class CmsCmisRelationHelper implements I_CmsCmisObjectHelper { 084 085 /** 086 * A class which contains the necessary information to identify a relation object.<p> 087 */ 088 public static class RelationKey { 089 090 /** The internal OpenCms relation object (optional). */ 091 private CmsRelation m_relation; 092 093 /** The relation type string. */ 094 private String m_relType; 095 096 /** The internal OpenCms resource which is the relation source (optional). */ 097 private CmsResource m_source; 098 099 /** The source id of the relation. */ 100 private CmsUUID m_sourceId; 101 102 /** The target id of the relation. */ 103 private CmsUUID m_targetId; 104 105 /** 106 * Creates a new relation key.<p> 107 * 108 * @param sourceId the source id 109 * @param targetId the target id 110 * @param relType the relation type 111 */ 112 public RelationKey(CmsUUID sourceId, CmsUUID targetId, String relType) { 113 114 m_sourceId = sourceId; 115 m_targetId = targetId; 116 m_relType = relType; 117 } 118 119 /** 120 * Reads the actual resource and relation data from the OpenCms VFS.<p> 121 * 122 * @param cms the CMS context to use for reading the data 123 */ 124 public void fillRelation(CmsObject cms) { 125 126 try { 127 m_source = cms.readResource(m_sourceId); 128 List<CmsRelation> relations = cms.getRelationsForResource( 129 m_source, 130 CmsRelationFilter.TARGETS.filterStructureId(m_targetId).filterType(getRelationType(m_relType))); 131 if (relations.isEmpty()) { 132 throw new CmisObjectNotFoundException(toString()); 133 } 134 m_relation = relations.get(0); 135 } catch (CmsException e) { 136 CmsCmisUtil.handleCmsException(e); 137 } 138 } 139 140 /** 141 * Gets the relation object.<p> 142 * 143 * @return the relation object 144 */ 145 public CmsRelation getRelation() { 146 147 return m_relation; 148 } 149 150 /** 151 * Gets the relation type.<p> 152 * 153 * @return the relation type 154 */ 155 public String getRelType() { 156 157 return m_relType; 158 } 159 160 /** 161 * Gets the source resource of the relation.<p> 162 * 163 * @return the source of the relation 164 */ 165 public CmsResource getSource() { 166 167 return m_source; 168 } 169 170 /** 171 * Gets the source id.<p> 172 * 173 * @return the source id 174 */ 175 public CmsUUID getSourceId() { 176 177 return m_sourceId; 178 } 179 180 /** 181 * Gets the target id of the relation.<p> 182 * 183 * @return the target id 184 */ 185 public CmsUUID getTargetId() { 186 187 return m_targetId; 188 } 189 190 /** 191 * Sets the relation type.<p> 192 * 193 * @param relType the relation type 194 */ 195 public void setRelType(String relType) { 196 197 m_relType = relType; 198 } 199 200 /** 201 * Sets the source id.<p> 202 * 203 * @param sourceId the source id 204 */ 205 public void setSourceId(CmsUUID sourceId) { 206 207 m_sourceId = sourceId; 208 } 209 210 /** 211 * Sets the target id.<p> 212 * 213 * @param targetId the target id 214 */ 215 public void setTargetId(CmsUUID targetId) { 216 217 m_targetId = targetId; 218 } 219 220 /** 221 * @see java.lang.Object#toString() 222 */ 223 @Override 224 public String toString() { 225 226 return createKey(m_sourceId, m_targetId, m_relType); 227 } 228 } 229 230 /** The prefix used to identify relation ids. */ 231 public static final String RELATION_ID_PREFIX = "REL_"; 232 233 /** The pattern which relation ids should match. */ 234 public static final Pattern RELATION_PATTERN = Pattern.compile( 235 "^REL_(" + CmsUUID.UUID_REGEX + ")_(" + CmsUUID.UUID_REGEX + ")_(.*)$"); 236 237 /** The underlying CMIS repository. */ 238 private CmsCmisRepository m_repository; 239 240 /** 241 * Creates a new relation helper for the given repository.<p> 242 * 243 * @param repository the repository 244 */ 245 public CmsCmisRelationHelper(CmsCmisRepository repository) { 246 247 m_repository = repository; 248 } 249 250 /** 251 * Creates a relation id string from the source and target ids and a relation type.<p> 252 * 253 * @param source the source id 254 * @param target the target id 255 * @param relType the relation type 256 * 257 * @return the relation id 258 */ 259 protected static String createKey(CmsUUID source, CmsUUID target, String relType) { 260 261 return RELATION_ID_PREFIX + source + "_" + target + "_" + relType; 262 } 263 264 /** 265 * Gets a relation type by name.<p> 266 * 267 * @param typeName the relation type name 268 * 269 * @return the relation type with the matching name 270 */ 271 protected static CmsRelationType getRelationType(String typeName) { 272 273 for (CmsRelationType relType : CmsRelationType.getAll()) { 274 if (relType.getName().equalsIgnoreCase(typeName)) { 275 return relType; 276 } 277 } 278 return null; 279 } 280 281 /** 282 * @see org.opencms.cmis.I_CmsCmisObjectHelper#deleteObject(org.opencms.cmis.CmsCmisCallContext, java.lang.String, boolean) 283 */ 284 public void deleteObject(CmsCmisCallContext context, String objectId, boolean allVersions) { 285 286 try { 287 288 RelationKey rk = parseRelationKey(objectId); 289 CmsUUID sourceId = rk.getSourceId(); 290 CmsObject cms = m_repository.getCmsObject(context); 291 CmsResource sourceResource = cms.readResource(sourceId); 292 boolean wasLocked = CmsCmisUtil.ensureLock(cms, sourceResource); 293 try { 294 CmsRelationFilter relFilter = CmsRelationFilter.ALL.filterType( 295 getRelationType(rk.getRelType())).filterStructureId(rk.getTargetId()); 296 cms.deleteRelationsFromResource(sourceResource.getRootPath(), relFilter); 297 } finally { 298 if (wasLocked) { 299 cms.unlockResource(sourceResource); 300 } 301 } 302 } catch (CmsException e) { 303 CmsCmisUtil.handleCmsException(e); 304 } 305 } 306 307 /** 308 * @see org.opencms.cmis.I_CmsCmisObjectHelper#getAcl(org.opencms.cmis.CmsCmisCallContext, java.lang.String, boolean) 309 */ 310 public Acl getAcl(CmsCmisCallContext context, String objectId, boolean onlyBasicPermissions) { 311 312 CmsObject cms = m_repository.getCmsObject(context); 313 RelationKey rk = parseRelationKey(objectId); 314 rk.fillRelation(cms); 315 return collectAcl(cms, rk.getSource(), onlyBasicPermissions); 316 } 317 318 /** 319 * @see org.opencms.cmis.I_CmsCmisObjectHelper#getAllowableActions(org.opencms.cmis.CmsCmisCallContext, java.lang.String) 320 */ 321 public AllowableActions getAllowableActions(CmsCmisCallContext context, String objectId) { 322 323 CmsObject cms = m_repository.getCmsObject(context); 324 RelationKey rk = parseRelationKey(objectId); 325 rk.fillRelation(cms); 326 return collectAllowableActions(cms, rk.getSource(), rk.getRelation()); 327 } 328 329 /** 330 * @see org.opencms.cmis.I_CmsCmisObjectHelper#getObject(org.opencms.cmis.CmsCmisCallContext, java.lang.String, java.lang.String, boolean, org.apache.chemistry.opencmis.commons.enums.IncludeRelationships, java.lang.String, boolean, boolean) 331 */ 332 public ObjectData getObject( 333 CmsCmisCallContext context, 334 String objectId, 335 String filter, 336 boolean includeAllowableActions, 337 IncludeRelationships includeRelationships, 338 String renditionFilter, 339 boolean includePolicyIds, 340 boolean includeAcl) { 341 342 CmsObject cms = m_repository.getCmsObject(context); 343 RelationKey rk = parseRelationKey(objectId); 344 rk.fillRelation(cms); 345 Set<String> filterSet = CmsCmisUtil.splitFilter(filter); 346 ObjectData result = collectObjectData( 347 context, 348 cms, 349 rk.getSource(), 350 rk.getRelation(), 351 filterSet, 352 includeAllowableActions, 353 includeAcl); 354 return result; 355 } 356 357 /** 358 * Compiles the ACL for a relation.<p> 359 * 360 * @param cms the CMS context 361 * @param resource the resource for which to collect the ACLs 362 * @param onlyBasic flag to only include basic ACEs 363 * 364 * @return the ACL for the resource 365 */ 366 protected Acl collectAcl(CmsObject cms, CmsResource resource, boolean onlyBasic) { 367 368 AccessControlListImpl cmisAcl = new AccessControlListImpl(); 369 List<Ace> cmisAces = new ArrayList<Ace>(); 370 cmisAcl.setAces(cmisAces); 371 cmisAcl.setExact(Boolean.FALSE); 372 return cmisAcl; 373 } 374 375 /** 376 * Collects the allowable actions for a relation.<p> 377 * 378 * @param cms the current CMS context 379 * @param file the source of the relation 380 * @param relation the relation object 381 * 382 * @return the allowable actions for the given resource 383 */ 384 protected AllowableActions collectAllowableActions(CmsObject cms, CmsResource file, CmsRelation relation) { 385 386 try { 387 Set<Action> aas = new LinkedHashSet<Action>(); 388 AllowableActionsImpl result = new AllowableActionsImpl(); 389 390 CmsLock lock = cms.getLock(file); 391 CmsUser user = cms.getRequestContext().getCurrentUser(); 392 boolean canWrite = !cms.getRequestContext().getCurrentProject().isOnlineProject() 393 && (lock.isOwnedBy(user) || lock.isLockableBy(user)) 394 && cms.hasPermissions(file, CmsPermissionSet.ACCESS_WRITE, false, CmsResourceFilter.DEFAULT); 395 addAction(aas, Action.CAN_GET_PROPERTIES, true); 396 addAction(aas, Action.CAN_DELETE_OBJECT, canWrite && !relation.getType().isDefinedInContent()); 397 result.setAllowableActions(aas); 398 return result; 399 } catch (CmsException e) { 400 handleCmsException(e); 401 return null; 402 } 403 } 404 405 /** 406 * Fills in an ObjectData record.<p> 407 * 408 * @param context the call context 409 * @param cms the CMS context 410 * @param resource the resource for which we want the ObjectData 411 * @param relation the relation object 412 * @param filter the property filter string 413 * @param includeAllowableActions true if the allowable actions should be included 414 * @param includeAcl true if the ACL entries should be included 415 * 416 * @return the object data 417 */ 418 protected ObjectData collectObjectData( 419 CmsCmisCallContext context, 420 CmsObject cms, 421 CmsResource resource, 422 CmsRelation relation, 423 Set<String> filter, 424 boolean includeAllowableActions, 425 boolean includeAcl) { 426 427 ObjectDataImpl result = new ObjectDataImpl(); 428 ObjectInfoImpl objectInfo = new ObjectInfoImpl(); 429 430 result.setProperties(collectProperties(cms, resource, relation, filter, objectInfo)); 431 432 if (includeAllowableActions) { 433 result.setAllowableActions(collectAllowableActions(cms, resource, relation)); 434 } 435 436 if (includeAcl) { 437 result.setAcl(collectAcl(cms, resource, true)); 438 result.setIsExactAcl(Boolean.FALSE); 439 } 440 441 if (context.isObjectInfoRequired()) { 442 objectInfo.setObject(result); 443 context.getObjectInfoHandler().addObjectInfo(objectInfo); 444 } 445 return result; 446 } 447 448 /** 449 * Gathers all base properties of a file or folder. 450 * 451 * @param cms the current CMS context 452 * @param resource the file for which we want the properties 453 * @param relation the relation object 454 * @param orgfilter the property filter 455 * @param objectInfo the object info handler 456 * 457 * @return the properties for the given resource 458 */ 459 protected Properties collectProperties( 460 CmsObject cms, 461 CmsResource resource, 462 CmsRelation relation, 463 Set<String> orgfilter, 464 ObjectInfoImpl objectInfo) { 465 466 CmsCmisTypeManager tm = m_repository.getTypeManager(); 467 468 if (resource == null) { 469 throw new IllegalArgumentException("Resource may not be null."); 470 } 471 472 // copy filter 473 Set<String> filter = (orgfilter == null ? null : new LinkedHashSet<String>(orgfilter)); 474 475 // find base type 476 String typeId = "opencms:" + relation.getType().getName(); 477 objectInfo.setBaseType(BaseTypeId.CMIS_RELATIONSHIP); 478 objectInfo.setTypeId(typeId); 479 objectInfo.setContentType(null); 480 objectInfo.setFileName(null); 481 objectInfo.setHasAcl(false); 482 objectInfo.setHasContent(false); 483 objectInfo.setVersionSeriesId(null); 484 objectInfo.setIsCurrentVersion(true); 485 objectInfo.setRelationshipSourceIds(null); 486 objectInfo.setRelationshipTargetIds(null); 487 objectInfo.setRenditionInfos(null); 488 objectInfo.setSupportsDescendants(false); 489 objectInfo.setSupportsFolderTree(false); 490 objectInfo.setSupportsPolicies(false); 491 objectInfo.setSupportsRelationships(false); 492 objectInfo.setWorkingCopyId(null); 493 objectInfo.setWorkingCopyOriginalId(null); 494 495 // let's do it 496 try { 497 PropertiesImpl result = new PropertiesImpl(); 498 499 // id 500 String id = createKey(relation); 501 addPropertyId(tm, result, typeId, filter, PropertyIds.OBJECT_ID, id); 502 objectInfo.setId(id); 503 504 // name 505 String name = createReadableName(relation); 506 addPropertyString(tm, result, typeId, filter, PropertyIds.NAME, name); 507 objectInfo.setName(name); 508 509 // created and modified by 510 CmsUUID creatorId = resource.getUserCreated(); 511 CmsUUID modifierId = resource.getUserLastModified(); 512 String creatorName = creatorId.toString(); 513 String modifierName = modifierId.toString(); 514 try { 515 CmsUser user = cms.readUser(creatorId); 516 creatorName = user.getName(); 517 } catch (CmsException e) { 518 // ignore, use id as name 519 } 520 try { 521 CmsUser user = cms.readUser(modifierId); 522 modifierName = user.getName(); 523 } catch (CmsException e) { 524 // ignore, use id as name 525 } 526 527 addPropertyString(tm, result, typeId, filter, PropertyIds.CREATED_BY, creatorName); 528 addPropertyString(tm, result, typeId, filter, PropertyIds.LAST_MODIFIED_BY, modifierName); 529 objectInfo.setCreatedBy(creatorName); 530 531 addPropertyId(tm, result, typeId, filter, PropertyIds.SOURCE_ID, relation.getSourceId().toString()); 532 addPropertyId(tm, result, typeId, filter, PropertyIds.TARGET_ID, relation.getTargetId().toString()); 533 534 // creation and modification date 535 GregorianCalendar lastModified = millisToCalendar(resource.getDateLastModified()); 536 GregorianCalendar created = millisToCalendar(resource.getDateCreated()); 537 538 addPropertyDateTime(tm, result, typeId, filter, PropertyIds.CREATION_DATE, created); 539 addPropertyDateTime(tm, result, typeId, filter, PropertyIds.LAST_MODIFICATION_DATE, lastModified); 540 objectInfo.setCreationDate(created); 541 objectInfo.setLastModificationDate(lastModified); 542 543 // change token - always null 544 addPropertyString(tm, result, typeId, filter, PropertyIds.CHANGE_TOKEN, null); 545 546 // base type and type name 547 addPropertyId(tm, result, typeId, filter, PropertyIds.BASE_TYPE_ID, BaseTypeId.CMIS_RELATIONSHIP.value()); 548 addPropertyId(tm, result, typeId, filter, PropertyIds.OBJECT_TYPE_ID, typeId); 549 objectInfo.setHasParent(false); 550 return result; 551 } catch (Exception e) { 552 if (e instanceof CmisBaseException) { 553 throw (CmisBaseException)e; 554 } 555 throw new CmisRuntimeException(e.getMessage(), e); 556 } 557 } 558 559 /** 560 * Creates a user-readable name from the given relation object.<p> 561 * 562 * @param relation the relation object 563 * 564 * @return the readable name 565 */ 566 protected String createReadableName(CmsRelation relation) { 567 568 return relation.getType().getName() 569 + "[ " 570 + relation.getSourcePath() 571 + " -> " 572 + relation.getTargetPath() 573 + " ]"; 574 } 575 576 /** 577 * Extracts the source/target ids and the type from a relation id.<p> 578 * 579 * @param id the relation id 580 * 581 * @return the relation key object 582 */ 583 protected RelationKey parseRelationKey(String id) { 584 585 Matcher matcher = RELATION_PATTERN.matcher(id); 586 matcher.find(); 587 CmsUUID src = new CmsUUID(matcher.group(1)); 588 CmsUUID tgt = new CmsUUID(matcher.group(2)); 589 String tp = matcher.group(3); 590 return new RelationKey(src, tgt, tp); 591 } 592 593 /** 594 * Creates a relation id from the given OpenCms relation object.<p> 595 * 596 * @param relation the OpenCms relation object 597 * 598 * @return the relation id 599 */ 600 String createKey(CmsRelation relation) { 601 602 return createKey(relation.getSourceId(), relation.getTargetId(), relation.getType().getName()); 603 } 604 605}