content: /*
   * Licensed to the Apache Software Foundation (ASF) under one
   * or more contributor license agreements.  See the NOTICE file
   * distributed with this work for additional information
   * regarding copyright ownership.  The ASF licenses this file
   * to you under the Apache License, Version 2.0 (the
   * "License"); you may not use this file except in compliance
   * with the License.  You may obtain a copy of the License at
   *
   * http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing,
   * software distributed under the License is distributed on an
   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
   * KIND, either express or implied.  See the License for the
   * specific language governing permissions and limitations
   * under the License.
   *
   * Contributors:
   *     Florian Mueller
   *     Florent Guillaume, Nuxeo
   */
  package org.apache.chemistry.opencmis.server.impl.atompub;
  
  import static org.apache.chemistry.opencmis.commons.impl.CollectionsHelper.isNotEmpty;
  
  import java.math.BigInteger;
  import java.util.GregorianCalendar;
  import java.util.List;
  import java.util.Map;
  
  import javax.servlet.http.HttpServletRequest;
  import javax.xml.stream.XMLStreamException;
  
  import org.apache.chemistry.opencmis.commons.data.ObjectData;
  import org.apache.chemistry.opencmis.commons.data.ObjectInFolderContainer;
  import org.apache.chemistry.opencmis.commons.definitions.TypeDefinition;
  import org.apache.chemistry.opencmis.commons.definitions.TypeDefinitionContainer;
  import org.apache.chemistry.opencmis.commons.enums.BaseTypeId;
  import org.apache.chemistry.opencmis.commons.enums.CmisVersion;
  import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
  import org.apache.chemistry.opencmis.commons.impl.Constants;
  import org.apache.chemistry.opencmis.commons.impl.ReturnVersion;
  import org.apache.chemistry.opencmis.commons.impl.UrlBuilder;
  import org.apache.chemistry.opencmis.commons.server.CmisService;
  import org.apache.chemistry.opencmis.commons.server.LinkInfo;
  import org.apache.chemistry.opencmis.commons.server.NamespaceDefinitions;
  import org.apache.chemistry.opencmis.commons.server.ObjectInfo;
  import org.apache.chemistry.opencmis.commons.server.RenditionInfo;
  import org.apache.chemistry.opencmis.server.shared.AbstractServiceCall;
  import org.apache.chemistry.opencmis.server.shared.Dispatcher;
  
  public abstract class AbstractAtomPubServiceCall extends AbstractServiceCall {
  
      public static final String RESOURCE_CHILDREN = "children";
      public static final String RESOURCE_DESCENDANTS = "descendants";
      public static final String RESOURCE_FOLDERTREE = "foldertree";
      public static final String RESOURCE_TYPE = "type";
      public static final String RESOURCE_TYPES = "types";
      public static final String RESOURCE_TYPESDESC = "typedesc";
      public static final String RESOURCE_ENTRY = "entry";
      public static final String RESOURCE_PARENT = "parent";
      public static final String RESOURCE_PARENTS = "parents";
      public static final String RESOURCE_VERSIONS = "versions";
      public static final String RESOURCE_ALLOWABLEACIONS = "allowableactions";
      public static final String RESOURCE_ACL = "acl";
      public static final String RESOURCE_POLICIES = "policies";
      public static final String RESOURCE_RELATIONSHIPS = "relationships";
      public static final String RESOURCE_OBJECTBYID = "id";
      public static final String RESOURCE_OBJECTBYPATH = "path";
      public static final String RESOURCE_QUERY = "query";
      public static final String RESOURCE_CHECKEDOUT = "checkedout";
      public static final String RESOURCE_UNFILED = "unfiled";
      public static final String RESOURCE_CHANGES = "changes";
      public static final String RESOURCE_CONTENT = "content";
      public static final String RESOURCE_BULK_UPDATE = "update";
  
      public static final String REPOSITORY_PLACEHOLDER = "{repositoryId}";
  
      public static final BigInteger PAGE_SIZE = BigInteger.valueOf(100);
  
      public static final String TYPE_AUTHOR = "unknown";
  
      /**
       * Compiles the base URL for links, collections and templates.
       */
      public UrlBuilder compileBaseUrl(HttpServletRequest request, String repositoryId) {
          String baseUrl = (String) request.getAttribute(Dispatcher.BASE_URL_ATTRIBUTE);
          if (baseUrl != null) {
              int repIdPos = baseUrl.indexOf(REPOSITORY_PLACEHOLDER);
              if (repIdPos < 0) {
                  return new UrlBuilder(baseUrl);
              } else {
                  return new UrlBuilder(baseUrl.substring(0, repIdPos) + repositoryId
                          + baseUrl.substring(repIdPos + REPOSITORY_PLACEHOLDER.length()));
              }
          }
  
          UrlBuilder url = new UrlBuilder(request.getScheme(), request.getServerName(), request.getServerPort(), null);
  
          url.addPath(request.getContextPath());
          url.addPath(request.getServletPath());
  
          if (repositoryId != null) {
              url.addPathSegment(repositoryId);
          }
  
          return url;
      }
  
      /**
       * Compiles a URL for links, collections and templates.
       */
      public String compileUrl(UrlBuilder baseUrl, String resource, String id) {
          return compileUrlBuilder(baseUrl, resource, id).toString();
      }
  
      /**
       * Compiles a URL for links, collections and templates.
       */
      public UrlBuilder compileUrlBuilder(UrlBuilder baseUrl, String resource, String id) {
          UrlBuilder url = new UrlBuilder(baseUrl);
          url.addPathSegment(resource);
  
          if (id != null) {
              url.addParameter("id", id);
          }
  
          return url;
      }
  
      // -------------------------------------------------------------------------
      // --- namespaces ---
      // -------------------------------------------------------------------------
  
      public Map<String, String> getNamespaces(Object obj) {
          if (obj instanceof NamespaceDefinitions) {
              return ((NamespaceDefinitions) obj).getNamespaces();
          }
  
          return null;
      }
  
      // -------------------------------------------------------------------------
      // --- entry builder ---
      // -------------------------------------------------------------------------
  
      /**
       * Writes the a object entry.
       */
      public void writeObjectEntry(CmisService service, AtomEntry entry, ObjectData object,
              List<ObjectInFolderContainer> children, String repositoryId, String pathSegment,
              String relativePathSegment, UrlBuilder baseUrl, boolean isRoot, CmisVersion cmisVersion)
              throws XMLStreamException {
          if (object == null) {
              throw new CmisRuntimeException("Object not set!");
          }
  
          ObjectInfo info = service.getObjectInfo(repositoryId, object.getId());
          if (info == null) {
              throw new CmisRuntimeException("Object Info not found for: " + object.getId());
          }
  
          // start
          entry.startEntry(isRoot);
  
          // write object
          String contentSrc = null;
  
          if (info.hasContent()) {
              UrlBuilder contentSrcBuilder = compileUrlBuilder(baseUrl, RESOURCE_CONTENT, info.getId());
              if (info.getFileName() != null) {
                  contentSrcBuilder.addPathSegment(info.getFileName());
              }
  
              contentSrc = contentSrcBuilder.toString();
          }
  
          entry.writeObject(object, info, contentSrc, info.getContentType(), pathSegment, relativePathSegment,
                  cmisVersion);
  
          // write links
          entry.writeServiceLink(baseUrl.toString(), repositoryId);
  
          entry.writeSelfLink(compileUrl(baseUrl, RESOURCE_ENTRY, info.getId()), info.getId());
          entry.writeEnclosureLink(compileUrl(baseUrl, RESOURCE_ENTRY, info.getId()));
          entry.writeEditLink(compileUrl(baseUrl, RESOURCE_ENTRY, info.getId()));
          entry.writeDescribedByLink(compileUrl(baseUrl, RESOURCE_TYPE, info.getTypeId()));
          entry.writeAllowableActionsLink(compileUrl(baseUrl, RESOURCE_ALLOWABLEACIONS, info.getId()));
  
          if (info.hasParent()) {
              if (info.getBaseType() == BaseTypeId.CMIS_FOLDER) {
                  entry.writeUpLink(compileUrl(baseUrl, RESOURCE_PARENT, info.getId()), Constants.MEDIATYPE_ENTRY);
              } else {
                  entry.writeUpLink(compileUrl(baseUrl, RESOURCE_PARENTS, info.getId()), Constants.MEDIATYPE_FEED);
              }
          }
  
          if (info.getBaseType() == BaseTypeId.CMIS_FOLDER) {
              entry.writeDownLink(compileUrl(baseUrl, RESOURCE_CHILDREN, info.getId()), Constants.MEDIATYPE_FEED);
  
              if (info.supportsDescendants()) {
                  entry.writeDownLink(compileUrl(baseUrl, RESOURCE_DESCENDANTS, info.getId()),
                          Constants.MEDIATYPE_DESCENDANTS);
              }
  
              if (info.supportsFolderTree()) {
                  entry.writeFolderTreeLink(compileUrl(baseUrl, RESOURCE_FOLDERTREE, info.getId()));
              }
          }
  
          if (info.getVersionSeriesId() != null) {
              UrlBuilder vsUrl = compileUrlBuilder(baseUrl, RESOURCE_VERSIONS, info.getId());
              vsUrl.addParameter(Constants.PARAM_VERSION_SERIES_ID, info.getVersionSeriesId());
              entry.writeVersionHistoryLink(vsUrl.toString());
          }
  
          if (!info.isCurrentVersion()) {
              UrlBuilder cvUrl = compileUrlBuilder(baseUrl, RESOURCE_ENTRY, info.getId());
              cvUrl.addParameter(Constants.PARAM_RETURN_VERSION, ReturnVersion.LATEST);
              entry.writeEditLink(cvUrl.toString());
          }
  
          if (info.getBaseType() == BaseTypeId.CMIS_DOCUMENT) {
              entry.writeEditMediaLink(compileUrl(baseUrl, RESOURCE_CONTENT, info.getId()), info.getContentType());
          }
  
          if (info.getWorkingCopyId() != null) {
              entry.writeWorkingCopyLink(compileUrl(baseUrl, RESOURCE_ENTRY, info.getWorkingCopyId()));
          }
  
          if (info.getWorkingCopyOriginalId() != null) {
              entry.writeViaLink(compileUrl(baseUrl, RESOURCE_ENTRY, info.getWorkingCopyOriginalId()));
          }
  
          if (info.getRenditionInfos() != null) {
              for (RenditionInfo ri : info.getRenditionInfos()) {
                  UrlBuilder rurl = compileUrlBuilder(baseUrl, RESOURCE_CONTENT, info.getId());
                  rurl.addParameter(Constants.PARAM_STREAM_ID, ri.getId());
                  entry.writeAlternateLink(rurl.toString(), ri.getContenType(), ri.getKind(), ri.getTitle(),
                          ri.getLength());
              }
          }
  
          if (info.hasAcl()) {
              entry.writeAclLink(compileUrl(baseUrl, RESOURCE_ACL, info.getId()));
          }
  
          if (info.supportsPolicies()) {
              entry.writePoliciesLink(compileUrl(baseUrl, RESOURCE_POLICIES, info.getId()));
          }
  
          if (info.supportsRelationships()) {
              entry.writeRelationshipsLink(compileUrl(baseUrl, RESOURCE_RELATIONSHIPS, info.getId()));
          }
  
          if (info.getRelationshipSourceIds() != null) {
              for (String id : info.getRelationshipSourceIds()) {
                  entry.writeRelationshipSourceLink(compileUrl(baseUrl, RESOURCE_ENTRY, id));
              }
          }
  
          if (info.getRelationshipTargetIds() != null) {
              for (String id : info.getRelationshipTargetIds()) {
                  entry.writeRelationshipTargetLink(compileUrl(baseUrl, RESOURCE_ENTRY, id));
              }
          }
  
          if (info.getAdditionalLinks() != null) {
              for (LinkInfo linkInfo : info.getAdditionalLinks()) {
                  entry.writeLink(linkInfo.getRel(), linkInfo.getHref(), linkInfo.getType(), linkInfo.getId());
              }
          }
  
          // write children
          if (isNotEmpty(children)) {
              writeObjectChildren(service, entry, info, children, repositoryId, baseUrl, cmisVersion);
          }
  
          // we are done
          entry.endEntry();
      }
  
      /**
       * Writes the a object entry in a content changes list.
       * 
       * Content changes objects need special treatment because some of them could
       * have been deleted and an object info cannot be generated.
       */
      public void writeContentChangesObjectEntry(CmisService service, AtomEntry entry, ObjectData object,
              List<ObjectInFolderContainer> children, String repositoryId, String pathSegment,
              String relativePathSegment, UrlBuilder baseUrl, boolean isRoot, CmisVersion cmisVersion)
              throws XMLStreamException {
          if (object == null) {
              throw new CmisRuntimeException("Object not set!");
          }
  
          ObjectInfo info = null;
          try {
              info = service.getObjectInfo(repositoryId, object.getId());
          } catch (Exception e) {
              // ignore all exceptions
          }
  
          if (info != null) {
              writeObjectEntry(service, entry, object, children, repositoryId, pathSegment, relativePathSegment, baseUrl,
                      isRoot, cmisVersion);
              return;
          }
  
          // start delete object entry
          entry.startEntry(isRoot);
  
          // write object
          entry.writeDeletedObject(object, cmisVersion);
  
          // write links
          entry.writeServiceLink(baseUrl.toString(), repositoryId);
  
          // we are done
          entry.endEntry();
      }
  
      /**
       * Writes an objects entry children feed.
       */
      public void writeObjectChildren(CmisService service, AtomEntry entry, ObjectInfo folderInfo,
              List<ObjectInFolderContainer> children, String repositoryId, UrlBuilder baseUrl, CmisVersion cmisVersion)
              throws XMLStreamException {
  
          // start
          AtomFeed feed = new AtomFeed(entry.getWriter());
          feed.startChildren();
          feed.startFeed(false);
  
          // write basic Atom feed elements
          feed.writeFeedElements(folderInfo.getId(), folderInfo.getAtomId(), folderInfo.getCreatedBy(),
                  folderInfo.getName(), folderInfo.getLastModificationDate(), null, null);
  
          // write links
          feed.writeServiceLink(baseUrl.toString(), repositoryId);
  
          feed.writeSelfLink(compileUrl(baseUrl, RESOURCE_DESCENDANTS, folderInfo.getId()), null);
  
          feed.writeViaLink(compileUrl(baseUrl, RESOURCE_ENTRY, folderInfo.getId()));
  
          feed.writeDownLink(compileUrl(baseUrl, RESOURCE_CHILDREN, folderInfo.getId()), Constants.MEDIATYPE_FEED);
  
          feed.writeDownLink(compileUrl(baseUrl, RESOURCE_FOLDERTREE, folderInfo.getId()),
                  Constants.MEDIATYPE_DESCENDANTS);
  
          feed.writeUpLink(compileUrl(baseUrl, RESOURCE_PARENTS, folderInfo.getId()), Constants.MEDIATYPE_FEED);
  
          for (ObjectInFolderContainer container : children) {
              if ((container != null) && (container.getObject() != null)) {
                  writeObjectEntry(service, entry, container.getObject().getObject(), container.getChildren(),
                          repositoryId, container.getObject().getPathSegment(), null, baseUrl, false, cmisVersion);
              }
          }
  
          // we are done
          feed.endFeed();
          feed.endChildren();
      }
  
      /**
       * Writes the a type entry.
       */
      public void writeTypeEntry(AtomEntry entry, TypeDefinition type, List<TypeDefinitionContainer> children,
              String repositoryId, UrlBuilder baseUrl, boolean isRoot, CmisVersion cmisVersion) throws XMLStreamException {
  
          // start
          entry.startEntry(isRoot);
  
          // write type
          entry.writeType(type, cmisVersion);
  
          // write links
          entry.writeServiceLink(baseUrl.toString(), repositoryId);
  
          entry.writeSelfLink(compileUrl(baseUrl, RESOURCE_TYPE, type.getId()), type.getId());
          entry.writeEnclosureLink(compileUrl(baseUrl, RESOURCE_TYPE, type.getId()));
          if (type.getParentTypeId() != null) {
              entry.writeUpLink(compileUrl(baseUrl, RESOURCE_TYPE, type.getParentTypeId()), Constants.MEDIATYPE_ENTRY);
          }
          UrlBuilder downLink = compileUrlBuilder(baseUrl, RESOURCE_TYPES, null);
          downLink.addParameter(Constants.PARAM_TYPE_ID, type.getId());
          entry.writeDownLink(downLink.toString(), Constants.MEDIATYPE_CHILDREN);
          UrlBuilder downLink2 = compileUrlBuilder(baseUrl, RESOURCE_TYPESDESC, null);
          downLink2.addParameter(Constants.PARAM_TYPE_ID, type.getId());
          entry.writeDownLink(downLink2.toString(), Constants.MEDIATYPE_DESCENDANTS);
          entry.writeDescribedByLink(compileUrl(baseUrl, RESOURCE_TYPE, type.getBaseTypeId().value()));
  
          // write children
          if (isNotEmpty(children)) {
              writeTypeChildren(entry, type, children, repositoryId, baseUrl, cmisVersion);
          }
  
          // we are done
          entry.endEntry();
      }
  
      /**
       * Writes the a type entry children feed.
       */
      private void writeTypeChildren(AtomEntry entry, TypeDefinition type, List<TypeDefinitionContainer> children,
              String repositoryId, UrlBuilder baseUrl, CmisVersion cmisVersion) throws XMLStreamException {
  
          // start
          AtomFeed feed = new AtomFeed(entry.getWriter());
          feed.startChildren();
          feed.startFeed(false);
  
          // write basic Atom feed elements
          feed.writeFeedElements(type.getId(), null, TYPE_AUTHOR, type.getDisplayName(), new GregorianCalendar(), null,
                  null);
  
          feed.writeServiceLink(baseUrl.toString(), repositoryId);
  
          UrlBuilder selfLink = compileUrlBuilder(baseUrl, RESOURCE_TYPESDESC, null);
          selfLink.addParameter(Constants.PARAM_TYPE_ID, type.getId());
          feed.writeSelfLink(selfLink.toString(), type.getId());
  
          feed.writeViaLink(compileUrl(baseUrl, RESOURCE_TYPE, type.getId()));
  
          UrlBuilder downLink = compileUrlBuilder(baseUrl, RESOURCE_TYPES, null);
          downLink.addParameter(Constants.PARAM_TYPE_ID, type.getId());
          feed.writeDownLink(downLink.toString(), Constants.MEDIATYPE_FEED);
  
          if (type.getParentTypeId() != null) {
              feed.writeUpLink(compileUrl(baseUrl, RESOURCE_TYPE, type.getParentTypeId()), Constants.MEDIATYPE_ENTRY);
          }
  
          // write tree
          for (TypeDefinitionContainer container : children) {
              if ((container != null) && (container.getTypeDefinition() != null)) {
                  writeTypeEntry(entry, container.getTypeDefinition(), container.getChildren(), repositoryId, baseUrl,
                          false, cmisVersion);
              }
          }
  
          // write extensions
          feed.writeExtensions(type);
  
          // we are done
          feed.endFeed();
          feed.endChildren();
      }
  }
  