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