@Override
 public void constraintRenditionStreamDownload(Content content, String streamId) {
   List<String> renditions = content.getRenditionIds();
   if (CollectionUtils.isEmpty(renditions) || !renditions.contains(streamId)) {
     constraint(content.getId(), "This document has no rendition specified with " + streamId);
   }
 }
  /** Filtering check to a list of contents based on the permission */
  @Override
  public <T> List<T> getFiltered(CallContext callContext, String repositoryId, List<T> contents) {
    List<T> result = new ArrayList<T>();

    // Validation
    // TODO refine the logic
    if (CollectionUtils.isEmpty(contents)) {
      return null;
    }

    // Filtering
    for (T _content : contents) {
      Content content = (Content) _content;
      Acl acl = contentService.calculateAcl(repositoryId, content);

      Boolean filtered =
          checkPermission(
              callContext,
              repositoryId,
              PermissionMapping.CAN_GET_PROPERTIES_OBJECT,
              acl,
              content.getType(),
              content);
      if (filtered) {
        result.add(_content);
      }
    }
    return result;
  }
  private Content checkExceptionBeforeUpdateProperties(
      CallContext callContext,
      String repositoryId,
      Holder<String> objectId,
      Properties properties,
      Holder<String> changeToken) {
    // //////////////////
    // General Exception
    // //////////////////
    exceptionService.invalidArgumentRequiredCollection("properties", properties.getPropertyList());
    Content content = contentService.getContent(repositoryId, objectId.getValue());
    exceptionService.objectNotFound(DomainType.OBJECT, content, objectId.getValue());
    if (content.isDocument()) {
      Document d = (Document) content;
      exceptionService.versioning(d);
      exceptionService.constraintUpdateWhenCheckedOut(repositoryId, callContext.getUsername(), d);
      TypeDefinition typeDef = typeManager.getTypeDefinition(repositoryId, d);
      exceptionService.constraintImmutable(repositoryId, d, typeDef);
    }
    exceptionService.permissionDenied(
        callContext, repositoryId, PermissionMapping.CAN_UPDATE_PROPERTIES_OBJECT, content);
    exceptionService.updateConflict(content, changeToken);

    TypeDefinition tdf = typeManager.getTypeDefinition(repositoryId, content);
    exceptionService.constraintPropertyValue(repositoryId, tdf, properties, objectId.getValue());

    return content;
  }
    @Override
    public BulkUpdateObjectIdAndChangeToken call() throws Exception {
      exceptionService.invalidArgumentRequiredString("objectId", objectIdAndChangeToken.getId());

      Lock lock = threadLockService.getWriteLock(repositoryId, objectIdAndChangeToken.getId());
      try {
        lock.lock();

        Content content =
            checkExceptionBeforeUpdateProperties(
                callContext,
                repositoryId,
                new Holder<String>(objectIdAndChangeToken.getId()),
                properties,
                new Holder<String>(objectIdAndChangeToken.getChangeToken()));
        contentService.updateProperties(callContext, repositoryId, properties, content);
        nemakiCachePool.get(repositoryId).removeCmisCache(content.getId());

        BulkUpdateObjectIdAndChangeToken result =
            new BulkUpdateObjectIdAndChangeTokenImpl(
                objectIdAndChangeToken.getId(),
                content.getId(),
                String.valueOf(content.getChangeToken()));
        return result;
      } catch (Exception e) {
        // Don't throw an error
        // Don't return any BulkUpdateObjectIdAndChangetoken
      } finally {
        lock.unlock();
      }

      // TODO Auto-generated method stub
      return null;
    }
 @Override
 public void constraintAllowedTargetTypes(
     RelationshipTypeDefinition relationshipTypeDefinition, Content target) {
   List<String> allowed = relationshipTypeDefinition.getAllowedTargetTypeIds();
   if (CollectionUtils.isNotEmpty(allowed)) {
     if (!allowed.contains(target.getObjectType()))
       constraint(target.getId(), "The target object's type is not allowed for the relationship");
   }
 }
 private void permissionTopLevelFolder(
     CallContext context, String repositoryId, String key, Content content) {
   boolean result =
       permissionService.checkPermissionAtTopLevel(context, repositoryId, key, content);
   if (!result) {
     String msg =
         String.format(
             "Permission Denied to top level folders for non-admin user! repositoryId=%s key=%s userId=%s content={id:%s, name:%s} ",
             repositoryId, key, context.getUsername(), content.getId(), content.getName());
     throw new CmisPermissionDeniedException(msg, HTTP_STATUS_CODE_403);
   }
 }
  private void permissionDeniedInternal(
      CallContext callContext,
      String repositoryId,
      String key,
      Acl acl,
      String baseTypeId,
      Content content) {

    if (!permissionService.checkPermission(
        callContext, repositoryId, key, acl, baseTypeId, content)) {
      String msg =
          String.format(
              "Permission Denied! repositoryId=%s key=%s acl=%s  content={id:%s, name:%s} ",
              repositoryId, key, acl, content.getId(), content.getName());
      throw new CmisPermissionDeniedException(msg, HTTP_STATUS_CODE_403);
    }
  }
  @Override
  public ObjectData getObjectByPath(
      CallContext callContext,
      String repositoryId,
      String path,
      String filter,
      Boolean includeAllowableActions,
      IncludeRelationships includeRelationships,
      String renditionFilter,
      Boolean includePolicyIds,
      Boolean includeAcl,
      ExtensionsData extension) {
    // //////////////////
    // General Exception
    // //////////////////
    exceptionService.invalidArgumentRequired("objectId", path);
    // FIXME path is not preserved in db.
    Content content = contentService.getContentByPath(repositoryId, path);

    // TODO create objectNotFoundByPath method
    exceptionService.objectNotFoundByPath(DomainType.OBJECT, content, path);

    Lock lock = threadLockService.getReadLock(repositoryId, content.getId());
    try {
      lock.lock();

      exceptionService.permissionDenied(
          callContext, repositoryId, PermissionMapping.CAN_GET_PROPERTIES_OBJECT, content);

      // //////////////////
      // Body of the method
      // //////////////////
      return compileService.compileObjectData(
          callContext,
          repositoryId,
          content,
          filter,
          includeAllowableActions,
          includeRelationships,
          renditionFilter,
          includeAcl);
    } finally {
      lock.unlock();
    }
  }
  private ContentStream getRenditionStream(String repositoryId, Content content, String streamId) {
    if (!content.isDocument() && !content.isFolder()) {
      exceptionService.constraint(
          content.getId(),
          "getRenditionStream cannnot be invoked to other than document or folder type.");
    }

    exceptionService.constraintRenditionStreamDownload(content, streamId);

    Rendition rendition = contentService.getRendition(repositoryId, streamId);

    BigInteger length = BigInteger.valueOf(rendition.getLength());
    String mimeType = rendition.getMimetype();
    InputStream is = rendition.getInputStream();
    ContentStream cs = new ContentStreamImpl("preview_" + streamId, length, mimeType, is);

    return cs;
  }
 @Override
 public void updateConflict(Content content, Holder<String> changeToken) {
   if ((changeToken == null || changeToken.getValue() == null)) {
     throw new CmisUpdateConflictException(
         "Change token is required to update", HTTP_STATUS_CODE_409);
   } else if (!changeToken.getValue().equals(content.getChangeToken())) {
     throw new CmisUpdateConflictException(
         "Cannot update because the changeToken conflicts", HTTP_STATUS_CODE_409);
   }
 }
      public void execute() {
        if (content.isDocument()) {
          Future<Boolean> result =
              parentService
                  .getService()
                  .submit(new DeleteTask(callContext, repositoryId, content, allVersions));
          failureIds.put(content.getId(), result);
        } else if (content.isFolder()) {
          WrappedExecutorService childrenService =
              new WrappedExecutorService(Executors.newFixedThreadPool(threadMax), (Folder) content);

          List<Content> children = contentService.getChildren(repositoryId, content.getId());
          if (CollectionUtils.isNotEmpty(children)) {
            for (Content child : children) {
              DeleteService deleteService =
                  new DeleteService(
                      this.failureIds,
                      childrenService,
                      callContext,
                      repositoryId,
                      child,
                      allVersions);
              deleteService.execute();
            }
          }

          // wait til newService ends
          childrenService.getService().shutdown();
          try {
            childrenService.getService().awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
          } catch (InterruptedException e) {
            log.error(e, e);
          }

          // Lastly, delete self
          Future<Boolean> result =
              parentService
                  .getService()
                  .submit(new DeleteTask(callContext, repositoryId, content, allVersions));
          failureIds.put(content.getId(), result);
        }
      }
  @Override
  public void moveObject(
      CallContext callContext,
      String repositoryId,
      Holder<String> objectId,
      String sourceFolderId,
      String targetFolderId) {

    exceptionService.invalidArgumentRequiredHolderString("objectId", objectId);

    Lock lock = threadLockService.getWriteLock(repositoryId, objectId.getValue());
    try {
      lock.lock();
      // //////////////////
      // General Exception
      // //////////////////
      exceptionService.invalidArgumentRequiredString("sourceFolderId", sourceFolderId);
      exceptionService.invalidArgumentRequiredString("targetFolderId", targetFolderId);
      Content content = contentService.getContent(repositoryId, objectId.getValue());
      exceptionService.objectNotFound(DomainType.OBJECT, content, objectId.getValue());
      Folder source = contentService.getFolder(repositoryId, sourceFolderId);
      exceptionService.objectNotFound(DomainType.OBJECT, source, sourceFolderId);
      Folder target = contentService.getFolder(repositoryId, targetFolderId);
      exceptionService.objectNotFound(DomainType.OBJECT, target, targetFolderId);
      exceptionService.permissionDenied(
          callContext, repositoryId, PermissionMapping.CAN_MOVE_OBJECT, content);
      exceptionService.permissionDenied(
          callContext, repositoryId, PermissionMapping.CAN_MOVE_SOURCE, source);
      exceptionService.permissionDenied(
          callContext, repositoryId, PermissionMapping.CAN_MOVE_TARGET, target);

      // //////////////////
      // Body of the method
      // //////////////////
      contentService.move(callContext, repositoryId, content, target);

      nemakiCachePool.get(repositoryId).removeCmisCache(content.getId());
    } finally {
      lock.unlock();
    }
  }
  // TODO Implement HTTP range(offset and length of stream), though it is not
  // obligatory.
  private ContentStream getContentStreamInternal(
      String repositoryId, Content content, BigInteger rangeOffset, BigInteger rangeLength) {
    if (!content.isDocument()) {
      exceptionService.constraint(
          content.getId(), "getContentStream cannnot be invoked to other than document type.");
    }
    Document document = (Document) content;
    exceptionService.constraintContentStreamDownload(repositoryId, document);
    AttachmentNode attachment =
        contentService.getAttachment(repositoryId, document.getAttachmentNodeId());
    attachment.setRangeOffset(rangeOffset);
    attachment.setRangeLength(rangeLength);

    // Set content stream
    BigInteger length = BigInteger.valueOf(attachment.getLength());
    String name = attachment.getName();
    String mimeType = attachment.getMimeType();
    InputStream is = attachment.getInputStream();
    ContentStream cs = new ContentStreamImpl(name, length, mimeType, is);

    return cs;
  }
  // TODO Show also stack errors
  @Override
  public void permissionDenied(
      CallContext context, String repositoryId, String key, Content content) {
    if (content == null) {
      System.out.println();
    }

    String baseTypeId = content.getType();
    Acl acl = contentService.calculateAcl(repositoryId, content);
    permissionDeniedInternal(context, repositoryId, key, acl, baseTypeId, content);

    permissionTopLevelFolder(context, repositoryId, key, content);
  }
  @Override
  public void contentAlreadyExists(Content content, Boolean overwriteFlag) {
    if (!overwriteFlag) {
      Document document = (Document) content; // FIXME
      String attachmentNodeId = document.getAttachmentNodeId(); // FIXME
      // getAttachmentNodes

      if (attachmentNodeId != null) {
        String msg = "Can't overwrite the content stream when overwriteFlag is false";
        throw new CmisContentAlreadyExistsException(
            buildMsgWithId(msg, content.getId()), HTTP_STATUS_CODE_409);
      }
    }
  }
  /**
   * TODO In the future, enable different configuration for Read/Update/Delete.
   *
   * @param callContext
   * @param repositoryId TODO
   * @param key
   * @param relationship
   * @return
   */
  private Boolean checkRelationshipPermission(
      CallContext callContext, String repositoryId, String key, Relationship relationship) {
    Content source = contentService.getRelationship(repositoryId, relationship.getSourceId());
    Content target = contentService.getRelationship(repositoryId, relationship.getTargetId());

    if (source == null || target == null) {
      log.warn(
          "[objectId="
              + relationship.getId()
              + "]Source or target of this relationship is missing");
      return false;
    }

    // Read action when a relationship is specified directly
    if (PermissionMapping.CAN_GET_PROPERTIES_OBJECT.equals(key)) {
      boolean readSource =
          checkPermission(
              callContext,
              repositoryId,
              PermissionMapping.CAN_GET_OBJECT_RELATIONSHIPS_OBJECT,
              contentService.calculateAcl(repositoryId, source),
              source.getType(),
              source);
      boolean readTarget =
          checkPermission(
              callContext,
              repositoryId,
              PermissionMapping.CAN_GET_OBJECT_RELATIONSHIPS_OBJECT,
              contentService.calculateAcl(repositoryId, target),
              target.getType(),
              target);
      return readSource | readTarget;
    }

    // Update action
    if (PermissionMapping.CAN_UPDATE_PROPERTIES_OBJECT.equals(key)) {
      boolean updateSource =
          checkPermission(
              callContext,
              repositoryId,
              PermissionMapping.CAN_GET_OBJECT_RELATIONSHIPS_OBJECT,
              contentService.calculateAcl(repositoryId, source),
              source.getType(),
              source);
      boolean updateTarget =
          checkPermission(
              callContext,
              repositoryId,
              PermissionMapping.CAN_GET_OBJECT_RELATIONSHIPS_OBJECT,
              contentService.calculateAcl(repositoryId, target),
              target.getType(),
              target);
      return updateSource | updateTarget;
    }

    // Delete action
    if (PermissionMapping.CAN_DELETE_OBJECT.equals(key)) {
      boolean deleteSource =
          checkPermission(
              callContext,
              repositoryId,
              PermissionMapping.CAN_GET_OBJECT_RELATIONSHIPS_OBJECT,
              contentService.calculateAcl(repositoryId, source),
              source.getType(),
              source);
      boolean deleteTarget =
          checkPermission(
              callContext,
              repositoryId,
              PermissionMapping.CAN_GET_OBJECT_RELATIONSHIPS_OBJECT,
              contentService.calculateAcl(repositoryId, target),
              target.getType(),
              target);
      return deleteSource | deleteTarget;
    }

    return false;
  }
  // TODO Merge arguments(acl, content)
  // FIXME Refactor duplicate isAllowableBaseType
  @Override
  public Boolean checkPermission(
      CallContext callContext,
      String repositoryId,
      String key,
      Acl acl,
      String baseType,
      Content content) {

    // All permission checks must go through baseType check
    if (!isAllowableBaseType(key, baseType, content, repositoryId)) return false;

    // Admin always pass a permission check
    CallContextImpl cci = (CallContextImpl) callContext;
    Boolean _isAdmin = (Boolean) cci.get(CallContextKey.IS_ADMIN);
    boolean isAdmin = (_isAdmin == null) ? false : _isAdmin;
    if (isAdmin) return true;

    // PWC doesn't accept any actions from a non-owner user
    // TODO admin can manipulate PWC even when it is checked out ?
    if (content.isDocument()) {
      Document document = (Document) content;
      if (document.isPrivateWorkingCopy()) {
        VersionSeries vs = contentService.getVersionSeries(repositoryId, document);
        if (!callContext.getUsername().equals(vs.getVersionSeriesCheckedOutBy())) {
          return false;
        }
      }
    }

    // Relation has no ACL stored in DB.
    // Though some actions are defined in the specs,
    // Some other direct actions is needed to be set here.
    if (content.isRelationship()) {
      Relationship relationship = (Relationship) content;
      return checkRelationshipPermission(callContext, repositoryId, key, relationship);
    }

    // Void Acl fails(but Admin can do an action)
    if (acl == null) return false;

    // Even if a user has multiple ACEs, the permissions is pushed into
    // Set<String> and remain unique.
    // Get ACL for the current user
    String userName = callContext.getUsername();
    List<Ace> aces = acl.getAllAces();
    Set<String> userPermissions = new HashSet<String>();
    Set<String> groups = principalService.getGroupIdsContainingUser(repositoryId, userName);
    for (Ace ace : aces) {
      // Filter ace which has not permissions
      if (ace.getPermissions() == null) continue;

      // Add user permissions
      if (ace.getPrincipalId().equals(userName)) {
        userPermissions.addAll(ace.getPermissions());
      }
      // Add inherited permissions which user inherits
      if (CollectionUtils.isNotEmpty(groups) && groups.contains(ace.getPrincipalId())) {
        userPermissions.addAll(ace.getPermissions());
      }
    }

    // Check mapping between the user and the content
    return checkCalculatedPermissions(repositoryId, key, userPermissions);
  }