001/* 002 * Copyright (c) 2006-2011 Nuxeo SA (http://nuxeo.com/) and others. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the Eclipse Public License v1.0 006 * which accompanies this distribution, and is available at 007 * http://www.eclipse.org/legal/epl-v10.html 008 * 009 * Contributors: 010 * Florent Guillaume 011 */ 012package org.nuxeo.ecm.core.opencmis.impl.server; 013 014import static org.apache.chemistry.opencmis.commons.impl.Constants.RENDITION_NONE; 015 016import java.io.IOException; 017import java.io.InputStream; 018import java.io.Serializable; 019import java.math.BigInteger; 020import java.util.ArrayList; 021import java.util.Arrays; 022import java.util.Collections; 023import java.util.EnumSet; 024import java.util.HashMap; 025import java.util.Iterator; 026import java.util.LinkedHashMap; 027import java.util.LinkedHashSet; 028import java.util.List; 029import java.util.Map; 030import java.util.Map.Entry; 031import java.util.Set; 032 033import javax.servlet.ServletContext; 034 035import org.apache.chemistry.opencmis.client.api.OperationContext; 036import org.apache.chemistry.opencmis.commons.BasicPermissions; 037import org.apache.chemistry.opencmis.commons.PropertyIds; 038import org.apache.chemistry.opencmis.commons.data.Ace; 039import org.apache.chemistry.opencmis.commons.data.Acl; 040import org.apache.chemistry.opencmis.commons.data.AllowableActions; 041import org.apache.chemistry.opencmis.commons.data.ChangeEventInfo; 042import org.apache.chemistry.opencmis.commons.data.CmisExtensionElement; 043import org.apache.chemistry.opencmis.commons.data.ExtensionsData; 044import org.apache.chemistry.opencmis.commons.data.MutableAce; 045import org.apache.chemistry.opencmis.commons.data.MutableAcl; 046import org.apache.chemistry.opencmis.commons.data.ObjectData; 047import org.apache.chemistry.opencmis.commons.data.PolicyIdList; 048import org.apache.chemistry.opencmis.commons.data.Properties; 049import org.apache.chemistry.opencmis.commons.data.PropertyData; 050import org.apache.chemistry.opencmis.commons.data.RenditionData; 051import org.apache.chemistry.opencmis.commons.definitions.PropertyDefinition; 052import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition; 053import org.apache.chemistry.opencmis.commons.enums.Action; 054import org.apache.chemistry.opencmis.commons.enums.BaseTypeId; 055import org.apache.chemistry.opencmis.commons.enums.IncludeRelationships; 056import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException; 057import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlEntryImpl; 058import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlListImpl; 059import org.apache.chemistry.opencmis.commons.impl.dataobjects.AccessControlPrincipalDataImpl; 060import org.apache.chemistry.opencmis.commons.impl.dataobjects.AllowableActionsImpl; 061import org.apache.chemistry.opencmis.commons.impl.dataobjects.BindingsObjectFactoryImpl; 062import org.apache.chemistry.opencmis.commons.impl.dataobjects.PolicyIdListImpl; 063import org.apache.chemistry.opencmis.commons.impl.dataobjects.RenditionDataImpl; 064import org.apache.chemistry.opencmis.commons.server.CallContext; 065import org.apache.chemistry.opencmis.commons.server.CmisService; 066import org.apache.chemistry.opencmis.commons.spi.BindingsObjectFactory; 067import org.apache.commons.lang.StringUtils; 068import org.nuxeo.ecm.core.api.Blob; 069import org.nuxeo.ecm.core.api.DocumentModel; 070import org.nuxeo.ecm.core.api.IterableQueryResult; 071import org.nuxeo.ecm.core.api.PropertyException; 072import org.nuxeo.ecm.core.api.security.ACE; 073import org.nuxeo.ecm.core.api.security.ACL; 074import org.nuxeo.ecm.core.api.security.ACP; 075import org.nuxeo.ecm.core.api.security.SecurityConstants; 076import org.nuxeo.ecm.core.api.security.impl.ACPImpl; 077import org.nuxeo.ecm.core.opencmis.impl.util.ListUtils; 078import org.nuxeo.ecm.core.opencmis.impl.util.SimpleImageInfo; 079import org.nuxeo.ecm.platform.rendition.Rendition; 080import org.nuxeo.ecm.platform.rendition.service.RenditionDefinition; 081import org.nuxeo.ecm.platform.rendition.service.RenditionService; 082import org.nuxeo.runtime.api.Framework; 083 084/** 085 * Nuxeo implementation of a CMIS {@link ObjectData}, backed by a {@link DocumentModel}. 086 */ 087public class NuxeoObjectData implements ObjectData { 088 089 public static final String REND_STREAM_ICON = "nuxeo:icon"; 090 091 public static final String REND_KIND_CMIS_THUMBNAIL = "cmis:thumbnail"; 092 093 public static final String REND_STREAM_RENDITION_PREFIX = "nuxeo:rendition:"; 094 095 public static final String REND_KIND_NUXEO_RENDITION = "nuxeo:rendition"; 096 097 /** 098 * Property to determine whether all renditions provide a computed size and length. 099 * 100 * @since 7.4 101 */ 102 public static final String RENDITION_COMPUTE_INFO_PROP = "org.nuxeo.cmis.computeRenditionInfo"; 103 104 /** 105 * Default for {@value #RENDITION_COMPUTE_INFO_PROP}. 106 * 107 * @since 7.4 108 */ 109 public static final String RENDITION_COMPUTE_INFO_DEFAULT = "false"; 110 111 public CmisService service; 112 113 public DocumentModel doc; 114 115 public boolean creation = false; // TODO 116 117 private List<String> propertyIds; 118 119 private Boolean includeAllowableActions; 120 121 private IncludeRelationships includeRelationships; 122 123 private String renditionFilter; 124 125 private Boolean includePolicyIds; 126 127 private Boolean includeAcl; 128 129 private static final BindingsObjectFactory objectFactory = new BindingsObjectFactoryImpl(); 130 131 private TypeDefinition type; 132 133 private List<TypeDefinition> secondaryTypes; 134 135 /** type + secondaryTypes */ 136 private List<TypeDefinition> allTypes; 137 138 private static final int CACHE_MAX_SIZE = 10; 139 140 private static final int DEFAULT_MAX_RENDITIONS = 20; 141 142 /** Cache for Properties objects, which are expensive to create. */ 143 private Map<String, Properties> propertiesCache = new HashMap<String, Properties>(); 144 145 private CallContext callContext; 146 147 private NuxeoCmisService nuxeoCmisService; 148 149 public NuxeoObjectData(CmisService service, DocumentModel doc, String filter, Boolean includeAllowableActions, 150 IncludeRelationships includeRelationships, String renditionFilter, Boolean includePolicyIds, 151 Boolean includeAcl, ExtensionsData extension) { 152 this.service = service; 153 this.doc = doc; 154 propertyIds = getPropertyIdsFromFilter(filter); 155 this.includeAllowableActions = includeAllowableActions; 156 this.includeRelationships = includeRelationships; 157 this.renditionFilter = renditionFilter; 158 this.includePolicyIds = includePolicyIds; 159 this.includeAcl = includeAcl; 160 nuxeoCmisService = NuxeoCmisService.extractFromCmisService(service); 161 type = nuxeoCmisService.repository.getTypeDefinition(NuxeoTypeHelper.mappedId(doc.getType())); 162 secondaryTypes = new ArrayList<>(); 163 for (String secondaryTypeId : NuxeoPropertyData.getSecondaryTypeIds(doc)) { 164 TypeDefinition td = nuxeoCmisService.repository.getTypeDefinition(secondaryTypeId); 165 if (td != null) { 166 secondaryTypes.add(td); 167 } // else doc has old facet not declared in types anymore, ignore 168 } 169 allTypes = new ArrayList<>(1 + secondaryTypes.size()); 170 allTypes.add(type); 171 allTypes.addAll(secondaryTypes); 172 callContext = nuxeoCmisService.callContext; 173 } 174 175 protected NuxeoObjectData(CmisService service, DocumentModel doc) { 176 this(service, doc, null, null, null, null, null, null, null); 177 } 178 179 public NuxeoObjectData(CmisService service, DocumentModel doc, OperationContext context) { 180 this(service, doc, context.getFilterString(), Boolean.valueOf(context.isIncludeAllowableActions()), 181 context.getIncludeRelationships(), context.getRenditionFilterString(), 182 Boolean.valueOf(context.isIncludePolicies()), Boolean.valueOf(context.isIncludeAcls()), null); 183 } 184 185 private static final String STAR = "*"; 186 187 protected static final List<String> STAR_FILTER = Collections.singletonList(STAR); 188 189 protected static List<String> getPropertyIdsFromFilter(String filter) { 190 if (filter == null || filter.length() == 0) 191 return STAR_FILTER; 192 else { 193 List<String> ids = Arrays.asList(filter.split(",\\s*")); 194 if (ids.contains(STAR)) { 195 ids = STAR_FILTER; 196 } 197 return ids; 198 } 199 } 200 201 @Override 202 public String getId() { 203 return doc.getId(); 204 } 205 206 @Override 207 public BaseTypeId getBaseTypeId() { 208 return NuxeoTypeHelper.getBaseTypeId(doc); 209 } 210 211 public List<TypeDefinition> getTypeDefinitions() { 212 return allTypes; 213 } 214 215 @Override 216 public Properties getProperties() { 217 return getProperties(propertyIds); 218 } 219 220 protected Properties getProperties(List<String> propertyIds) { 221 // for STAR_FILTER the key is equal to STAR (see limitCacheSize) 222 String key = StringUtils.join(propertyIds, ','); 223 Properties properties = propertiesCache.get(key); 224 if (properties == null) { 225 List<PropertyData<?>> props = new ArrayList<PropertyData<?>>(); 226 for (TypeDefinition t : allTypes) { 227 Map<String, PropertyDefinition<?>> propertyDefinitions = t.getPropertyDefinitions(); 228 for (PropertyDefinition<?> pd : propertyDefinitions.values()) { 229 if (propertyIds == STAR_FILTER || propertyIds.contains(pd.getId())) { 230 props.add((PropertyData<?>) NuxeoPropertyData.construct(this, pd, callContext)); 231 } 232 } 233 } 234 properties = objectFactory.createPropertiesData(props); 235 limitCacheSize(); 236 propertiesCache.put(key, properties); 237 } 238 return properties; 239 } 240 241 /** Limits cache size, always keeps STAR filter. */ 242 protected void limitCacheSize() { 243 if (propertiesCache.size() >= CACHE_MAX_SIZE) { 244 Properties sf = propertiesCache.get(STAR); 245 propertiesCache.clear(); 246 if (sf != null) { 247 propertiesCache.put(STAR, sf); 248 } 249 } 250 } 251 252 public NuxeoPropertyDataBase<?> getProperty(String id) { 253 // make use of cache 254 return (NuxeoPropertyDataBase<?>) getProperties(STAR_FILTER).getProperties().get(id); 255 } 256 257 @Override 258 public AllowableActions getAllowableActions() { 259 if (!Boolean.TRUE.equals(includeAllowableActions)) { 260 return null; 261 } 262 return getAllowableActions(doc, creation); 263 } 264 265 public static AllowableActions getAllowableActions(DocumentModel doc, boolean creation) { 266 BaseTypeId baseType = NuxeoTypeHelper.getBaseTypeId(doc); 267 boolean isDocument = baseType == BaseTypeId.CMIS_DOCUMENT; 268 boolean isFolder = baseType == BaseTypeId.CMIS_FOLDER; 269 boolean isRoot = "/".equals(doc.getPathAsString()); 270 boolean canWrite = creation || doc.getCoreSession().hasPermission(doc.getRef(), SecurityConstants.WRITE); 271 272 Set<Action> set = EnumSet.noneOf(Action.class); 273 set.add(Action.CAN_GET_OBJECT_PARENTS); 274 set.add(Action.CAN_GET_PROPERTIES); 275 if (isFolder) { 276 set.add(Action.CAN_GET_DESCENDANTS); 277 set.add(Action.CAN_GET_FOLDER_TREE); 278 set.add(Action.CAN_GET_CHILDREN); 279 if (!isRoot) { 280 set.add(Action.CAN_GET_FOLDER_PARENT); 281 } 282 } else if (isDocument) { 283 set.add(Action.CAN_GET_CONTENT_STREAM); 284 set.add(Action.CAN_GET_ALL_VERSIONS); 285 set.add(Action.CAN_ADD_OBJECT_TO_FOLDER); 286 set.add(Action.CAN_REMOVE_OBJECT_FROM_FOLDER); 287 if (doc.isCheckedOut()) { 288 set.add(Action.CAN_CHECK_IN); 289 set.add(Action.CAN_CANCEL_CHECK_OUT); 290 } else { 291 set.add(Action.CAN_CHECK_OUT); 292 } 293 } 294 if (isFolder || isDocument) { 295 set.add(Action.CAN_GET_RENDITIONS); 296 } 297 if (canWrite) { 298 if (isFolder) { 299 set.add(Action.CAN_CREATE_DOCUMENT); 300 set.add(Action.CAN_CREATE_FOLDER); 301 set.add(Action.CAN_CREATE_RELATIONSHIP); 302 set.add(Action.CAN_DELETE_TREE); 303 } else if (isDocument) { 304 set.add(Action.CAN_SET_CONTENT_STREAM); 305 set.add(Action.CAN_DELETE_CONTENT_STREAM); 306 } 307 set.add(Action.CAN_UPDATE_PROPERTIES); 308 if (isFolder && !isRoot || isDocument) { 309 // Relationships are not fileable 310 set.add(Action.CAN_MOVE_OBJECT); 311 } 312 if (!isRoot) { 313 set.add(Action.CAN_DELETE_OBJECT); 314 } 315 } 316 if (Boolean.FALSE.booleanValue()) { 317 // TODO 318 set.add(Action.CAN_GET_OBJECT_RELATIONSHIPS); 319 set.add(Action.CAN_APPLY_POLICY); 320 set.add(Action.CAN_REMOVE_POLICY); 321 set.add(Action.CAN_GET_APPLIED_POLICIES); 322 set.add(Action.CAN_GET_ACL); 323 set.add(Action.CAN_APPLY_ACL); 324 set.add(Action.CAN_CREATE_ITEM); 325 } 326 327 AllowableActionsImpl aa = new AllowableActionsImpl(); 328 aa.setAllowableActions(set); 329 return aa; 330 } 331 332 @Override 333 public List<RenditionData> getRenditions() { 334 if (!needsRenditions(renditionFilter)) { 335 return Collections.emptyList(); 336 } 337 return getRenditions(doc, renditionFilter, null, null, callContext); 338 } 339 340 public static boolean needsRenditions(String renditionFilter) { 341 return !StringUtils.isBlank(renditionFilter) && !RENDITION_NONE.equals(renditionFilter); 342 } 343 344 public static List<RenditionData> getRenditions(DocumentModel doc, String renditionFilter, BigInteger maxItems, 345 BigInteger skipCount, CallContext callContext) { 346 try { 347 List<RenditionData> list = new ArrayList<RenditionData>(); 348 list.addAll(getRenditionServiceRenditions(doc, callContext)); 349 // rendition filter 350 if (!STAR.equals(renditionFilter)) { 351 String[] filters = renditionFilter.split(","); 352 for (Iterator<RenditionData> it = list.iterator(); it.hasNext();) { 353 RenditionData ren = it.next(); 354 boolean keep = false; 355 for (String filter : filters) { 356 if (filter.contains("/")) { 357 // mimetype 358 if (filter.endsWith("/*")) { 359 String typeSlash = filter.substring(0, filter.indexOf('/') + 1); 360 if (ren.getMimeType().startsWith(typeSlash)) { 361 keep = true; 362 break; 363 } 364 } else { 365 if (ren.getMimeType().equals(filter)) { 366 keep = true; 367 break; 368 } 369 } 370 } else { 371 // kind 372 if (ren.getKind().equals(filter)) { 373 keep = true; 374 break; 375 } 376 } 377 } 378 if (!keep) { 379 it.remove(); 380 } 381 } 382 } 383 list = ListUtils.batchList(list, maxItems, skipCount, DEFAULT_MAX_RENDITIONS); 384 return list; 385 } catch (IOException e) { 386 throw new CmisRuntimeException(e.toString(), e); 387 } 388 } 389 390 /** 391 * @deprecated since 7.3. The thumbnail is now a default rendition, see NXP-16662. 392 */ 393 @Deprecated 394 protected static List<RenditionData> getIconRendition(DocumentModel doc, CallContext callContext) 395 throws IOException { 396 String iconPath; 397 try { 398 iconPath = (String) doc.getPropertyValue(NuxeoTypeHelper.NX_ICON); 399 } catch (PropertyException e) { 400 iconPath = null; 401 } 402 InputStream is = getIconStream(iconPath, callContext); 403 if (is == null) { 404 return Collections.emptyList(); 405 } 406 RenditionDataImpl ren = new RenditionDataImpl(); 407 ren.setStreamId(REND_STREAM_ICON); 408 ren.setKind(REND_KIND_CMIS_THUMBNAIL); 409 int slash = iconPath.lastIndexOf('/'); 410 String filename = slash == -1 ? iconPath : iconPath.substring(slash + 1); 411 ren.setTitle(filename); 412 SimpleImageInfo info = new SimpleImageInfo(is); 413 ren.setBigLength(BigInteger.valueOf(info.getLength())); 414 ren.setBigWidth(BigInteger.valueOf(info.getWidth())); 415 ren.setBigHeight(BigInteger.valueOf(info.getHeight())); 416 ren.setMimeType(info.getMimeType()); 417 return Collections.<RenditionData> singletonList(ren); 418 } 419 420 /** 421 * @deprecated since 7.3. The thumbnail is now a default rendition, see NXP-16662. 422 */ 423 @Deprecated 424 public static InputStream getIconStream(String iconPath, CallContext context) { 425 if (iconPath == null || iconPath.length() == 0) { 426 return null; 427 } 428 if (!iconPath.startsWith("/")) { 429 iconPath = '/' + iconPath; 430 } 431 ServletContext servletContext = (ServletContext) context.get(CallContext.SERVLET_CONTEXT); 432 if (servletContext == null) { 433 throw new CmisRuntimeException("Cannot get servlet context"); 434 } 435 return servletContext.getResourceAsStream(iconPath); 436 } 437 438 protected static List<RenditionData> getRenditionServiceRenditions(DocumentModel doc, CallContext callContext) 439 throws IOException { 440 RenditionService renditionService = Framework.getLocalService(RenditionService.class); 441 List<RenditionDefinition> defs = renditionService.getAvailableRenditionDefinitions(doc); 442 List<RenditionData> list = new ArrayList<>(defs.size()); 443 for (RenditionDefinition def : defs) { 444 if (!def.isVisible()) { 445 continue; 446 } 447 RenditionDataImpl ren = new RenditionDataImpl(); 448 String cmisName = def.getCmisName(); 449 if (StringUtils.isBlank(cmisName)) { 450 cmisName = REND_STREAM_RENDITION_PREFIX + def.getName(); 451 } 452 ren.setStreamId(cmisName); 453 String kind = def.getKind(); 454 ren.setKind(StringUtils.isNotBlank(kind) ? kind : REND_KIND_NUXEO_RENDITION); 455 ren.setTitle(def.getLabel()); 456 ren.setMimeType(def.getContentType()); 457 458 boolean computeInfo = Boolean.parseBoolean( 459 Framework.getProperty(RENDITION_COMPUTE_INFO_PROP, RENDITION_COMPUTE_INFO_DEFAULT)); 460 if (REND_KIND_CMIS_THUMBNAIL.equals(ren.getKind()) || computeInfo) { 461 Rendition rendition = renditionService.getRendition(doc, def.getName()); 462 Blob blob = rendition.getBlob(); 463 if (blob != null) { 464 ren.setTitle(blob.getFilename()); 465 SimpleImageInfo info = new SimpleImageInfo(blob.getStream()); 466 ren.setBigLength(BigInteger.valueOf(info.getLength())); 467 ren.setBigWidth(BigInteger.valueOf(info.getWidth())); 468 ren.setBigHeight(BigInteger.valueOf(info.getHeight())); 469 ren.setMimeType(info.getMimeType()); 470 } 471 } 472 list.add(ren); 473 } 474 return list; 475 } 476 477 @Override 478 public List<ObjectData> getRelationships() { 479 return getRelationships(getId(), includeRelationships, nuxeoCmisService); 480 } 481 482 public static List<ObjectData> getRelationships(String id, IncludeRelationships includeRelationships, 483 NuxeoCmisService service) { 484 if (includeRelationships == null || includeRelationships == IncludeRelationships.NONE) { 485 return null; 486 } 487 String statement = "SELECT " + PropertyIds.OBJECT_ID + ", " + PropertyIds.BASE_TYPE_ID + ", " 488 + PropertyIds.SOURCE_ID + ", " + PropertyIds.TARGET_ID + " FROM " 489 + BaseTypeId.CMIS_RELATIONSHIP.value() + " WHERE "; 490 String qid = "'" + id.replace("'", "''") + "'"; 491 if (includeRelationships != IncludeRelationships.TARGET) { 492 statement += PropertyIds.SOURCE_ID + " = " + qid; 493 } 494 if (includeRelationships == IncludeRelationships.BOTH) { 495 statement += " OR "; 496 } 497 if (includeRelationships != IncludeRelationships.SOURCE) { 498 statement += PropertyIds.TARGET_ID + " = " + qid; 499 } 500 List<ObjectData> list = new ArrayList<ObjectData>(); 501 IterableQueryResult res = null; 502 try { 503 Map<String, PropertyDefinition<?>> typeInfo = new HashMap<String, PropertyDefinition<?>>(); 504 res = service.queryAndFetch(statement, false, typeInfo); 505 for (Map<String, Serializable> map : res) { 506 list.add(service.makeObjectData(map, typeInfo)); 507 } 508 } finally { 509 if (res != null) { 510 res.close(); 511 } 512 } 513 return list; 514 } 515 516 @Override 517 public Acl getAcl() { 518 if (!Boolean.TRUE.equals(includeAcl)) { 519 return null; 520 } 521 ACP acp = doc.getACP(); 522 return getAcl(acp, false, nuxeoCmisService); 523 } 524 525 protected static Acl getAcl(ACP acp, boolean onlyBasicPermissions, NuxeoCmisService service) { 526 if (acp == null) { 527 acp = new ACPImpl(); 528 } 529 Boolean exact = Boolean.TRUE; 530 List<Ace> aces = new ArrayList<Ace>(); 531 for (ACL acl : acp.getACLs()) { 532 // inherited and non-local ACLs are non-direct 533 boolean direct = ACL.LOCAL_ACL.equals(acl.getName()); 534 Map<String, Set<String>> permissionMap = new LinkedHashMap<>(); 535 for (ACE ace : acl.getACEs()) { 536 boolean denied = ace.isDenied(); 537 String username = ace.getUsername(); 538 String permission = ace.getPermission(); 539 if (denied) { 540 if (SecurityConstants.EVERYONE.equals(username) && SecurityConstants.EVERYTHING.equals(permission)) { 541 permission = NuxeoCmisService.PERMISSION_NOTHING; 542 } else { 543 // we cannot represent this blocking 544 exact = Boolean.FALSE; 545 continue; 546 } 547 } 548 Set<String> permissions = permissionMap.get(username); 549 if (permissions == null) { 550 permissionMap.put(username, permissions = new LinkedHashSet<String>()); 551 } 552 // derive CMIS permission from Nuxeo permissions 553 boolean isBasic = false; 554 if (service.readPermissions.contains(permission)) { // Read 555 isBasic = true; 556 permissions.add(BasicPermissions.READ); 557 } 558 if (service.writePermissions.contains(permission)) { // ReadWrite 559 isBasic = true; 560 permissions.add(BasicPermissions.WRITE); 561 } 562 if (SecurityConstants.EVERYTHING.equals(permission)) { 563 isBasic = true; 564 permissions.add(BasicPermissions.ALL); 565 } 566 if (!onlyBasicPermissions) { 567 permissions.add(permission); 568 } else if (!isBasic) { 569 exact = Boolean.FALSE; 570 } 571 if (NuxeoCmisService.PERMISSION_NOTHING.equals(permission)) { 572 break; 573 } 574 } 575 for (Entry<String, Set<String>> en : permissionMap.entrySet()) { 576 String username = en.getKey(); 577 Set<String> permissions = en.getValue(); 578 if (permissions.isEmpty()) { 579 continue; 580 } 581 MutableAce entry = new AccessControlEntryImpl(); 582 entry.setPrincipal(new AccessControlPrincipalDataImpl(username)); 583 entry.setPermissions(new ArrayList<String>(permissions)); 584 entry.setDirect(direct); 585 aces.add(entry); 586 } 587 } 588 MutableAcl result = new AccessControlListImpl(); 589 result.setAces(aces); 590 result.setExact(exact); 591 return result; 592 } 593 594 @Override 595 public Boolean isExactAcl() { 596 return Boolean.FALSE; // TODO 597 } 598 599 @Override 600 public PolicyIdList getPolicyIds() { 601 if (!Boolean.TRUE.equals(includePolicyIds)) { 602 return null; 603 } 604 return new PolicyIdListImpl(); // TODO 605 } 606 607 @Override 608 public ChangeEventInfo getChangeEventInfo() { 609 return null; 610 // throw new UnsupportedOperationException(); 611 } 612 613 @Override 614 public List<CmisExtensionElement> getExtensions() { 615 return Collections.emptyList(); 616 } 617 618 @Override 619 public void setExtensions(List<CmisExtensionElement> extensions) { 620 } 621 622}