/**
 * Copyright (C) 2010 eXo Platform SAS.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */

package org.xcmis.restatom;

import org.apache.abdera.factory.Factory;
import org.apache.abdera.model.Document;
import org.apache.abdera.model.Link;
import org.apache.abdera.model.Service;
import org.apache.abdera.model.Workspace;
import org.apache.abdera.parser.stax.FOMExtensibleElement;
import org.apache.abdera.protocol.server.CollectionInfo;
import org.apache.abdera.protocol.server.RequestContext;
import org.apache.abdera.protocol.server.ResponseContext;
import org.apache.abdera.protocol.server.WorkspaceInfo;
import org.apache.abdera.protocol.server.context.BaseResponseContext;
import org.apache.abdera.protocol.server.context.ResponseContextException;
import org.apache.abdera.protocol.server.servlet.ServletRequestContext;
import org.apache.commons.fileupload.FileItem;
import org.xcmis.restatom.abdera.AccessControlEntryTypeElement;
import org.xcmis.restatom.abdera.AllowableActionsElement;
import org.xcmis.restatom.abdera.RepositoryInfoTypeElement;
import org.xcmis.restatom.abdera.UriTemplateTypeElement;
import org.xcmis.restatom.collections.AbstractCmisCollection;
import org.xcmis.restatom.types.CmisUriTemplateType;
import org.xcmis.spi.CmisRegistry;
import org.xcmis.spi.Connection;
import org.xcmis.spi.InvalidArgumentException;
import org.xcmis.spi.ObjectNotFoundException;
import org.xcmis.spi.UpdateConflictException;
import org.xcmis.spi.model.AccessControlEntry;
import org.xcmis.spi.model.AccessControlPropagation;
import org.xcmis.spi.model.AllowableActions;
import org.xcmis.spi.model.CapabilityChanges;
import org.xcmis.spi.model.RepositoryCapabilities;
import org.xcmis.spi.model.RepositoryInfo;
import org.xcmis.spi.model.RepositoryShortInfo;
import org.xcmis.spi.model.UnfileObject;

import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.activation.MimeTypeParseException;
import javax.annotation.security.RolesAllowed;
import javax.servlet.http.HttpServletRequest;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import javax.ws.rs.core.Response.ResponseBuilder;

/**
 * @author <a href="mailto:andrey.parfonov@exoplatform.com">Andrey Parfonov</a>
 * @version $Id: AtomCmisService.java 216 2010-02-12 17:19:50Z andrew00x $
 */
@Path(AtomCMIS.CMIS_REST_RESOURCE_PATH)
public class AtomCmisService
{

   /** The provider. */
   protected ProviderImpl provider;

   /**
    * Instantiates a new atom cmis service.
    */
   public AtomCmisService(ProviderImpl provider)
   {
      this.provider = provider;
   }

   @PUT
   @Path("{repositoryId}/objacl/{objectId}")
   @Produces("application/cmisacl+xml")
   public Response addACL(@Context HttpServletRequest httpRequest, @PathParam("repositoryId") String repositoryId,
      @PathParam("objectId") String objectId)
   {
      Connection connection = null;
      try
      {
         connection = CmisRegistry.getInstance().getConnection(repositoryId);
         RequestContext request = initRequestContext(httpRequest);
         Document<?> doc = request.getDocument();
         List<AccessControlEntryTypeElement> listEl = doc.getRoot().getElements();
         List<AccessControlEntry> listACE = new ArrayList<AccessControlEntry>();
         for (AccessControlEntryTypeElement el : listEl)
         {
            listACE.add(el.getACE());
         }
         List<AccessControlEntry> removeACL = new ArrayList<AccessControlEntry>();
         connection.applyACL(objectId, listACE, removeACL, AccessControlPropagation.REPOSITORYDETERMINED);
         List<AccessControlEntry> list = connection.getACL(objectId, false);
         FOMExtensibleElement accessControlListTypeElement =
            AbderaFactory.getInstance().getFactory().newElement(AtomCMIS.ACL);
         for (AccessControlEntry accessControlEntry : list)
         {
            AccessControlEntryTypeElement ace = accessControlListTypeElement.addExtension(AtomCMIS.PERMISSION);
            ace.build(accessControlEntry);
         }
         //return Response.status(201).build();
         return Response.ok(accessControlListTypeElement).header(HttpHeaders.CACHE_CONTROL, "no-cache").build();
      }
      catch (IOException io)
      {
         throw new WebApplicationException(io, createErrorResponse(io, 500));
      }
      catch (Throwable t)
      {
         throw new WebApplicationException(t, createErrorResponse(t, 500));
      }
      finally
      {
         if (connection != null)
         {
            connection.close();
         }
      }

   }

   @POST
   @Path("{repositoryId}/types")
   @RolesAllowed({"administrators"})
   public Response addType(@Context HttpServletRequest httpRequest, @PathParam("repositoryId") String repositoryId)
   {
      return createItem(repositoryId, httpRequest);
   }

   @POST
   @Path("{repositoryId}/policies/{objectId}")
   @Produces("application/atom+xml;type=entry")
   public Response applyPolicy(@Context HttpServletRequest httpRequest, @PathParam("repositoryId") String repositoryId)
   {
      return createItem(repositoryId, httpRequest);
   }

   @POST
   @Path("{repositoryId}/checkedout")
   public Response checkOut(@Context HttpServletRequest httpRequest, @PathParam("repositoryId") String repositoryId)
      throws Exception
   {
      return createItem(repositoryId, httpRequest);
   }

   @POST
   @Path("{repositoryId}/children/{folderId}")
   @Produces("application/atom+xml;type=entry")
   public Response createChild(@Context HttpServletRequest httpRequest, @PathParam("repositoryId") String repositoryId)
   {
      return createItem(repositoryId, httpRequest);
   }

   @POST
   @Path("{repositoryId}/object/{folderId}")
   @Produces("application/atom+xml;type=entry")
   public Response createChildObj(@Context HttpServletRequest httpRequest,
      @PathParam("repositoryId") String repositoryId)
   {
      // Found some clients those use direct object (folder) link for adding child.
      return createItem(repositoryId, httpRequest);
   }

   @POST
   @Path("{repositoryId}/relationships/{objectId}")
   @Produces("application/atom+xml;type=entry")
   public Response createRelationship(@Context HttpServletRequest httpRequest,
      @PathParam("repositoryId") String repositoryId)
   {
      return createItem(repositoryId, httpRequest);
   }

   @DELETE
   @Path("{repositoryId}/file/{objectId}")
   public Response deleteContentStream(@Context HttpServletRequest httpRequest,
      @PathParam("repositoryId") String repositoryId)
   {
      RequestContext request = initRequestContext(httpRequest);
      Connection connection = null;
      try
      {
         connection = CmisRegistry.getInstance().getConnection(repositoryId);
         AbstractCmisCollection<?> collection = getCollection(request, connection);
         ResponseContext abderaResponse = collection.deleteMedia(request);
         return Response.status(abderaResponse.getStatus()).entity(abderaResponse).build();
      }
      finally
      {
         if (connection != null)
         {
            connection.close();
         }
      }
   }

   @DELETE
   @Path("{repositoryId}/object/{objectId}")
   public Response deleteObject(@Context HttpServletRequest httpRequest, @PathParam("repositoryId") String repositoryId)
   {
      return deleteItem(repositoryId, httpRequest);
   }

   @DELETE
   @Path("{repositoryId}/foldertree/{folderId}")
   public Response deleteFolderTree(@PathParam("repositoryId") String repositoryId,
      @PathParam("folderId") String folderId, @QueryParam("unfileObject") String unfileNonfolderObjects,
      @DefaultValue("false") @QueryParam("continueOnFailure") boolean continueOnFailure)
   {
      return deleteDescendants(repositoryId, folderId, unfileNonfolderObjects, continueOnFailure);
   }

   @DELETE
   @Path("{repositoryId}/descendants/{folderId}")
   public Response deleteDescendants(@PathParam("repositoryId") String repositoryId,
      @PathParam("folderId") String folderId, @QueryParam("unfileObject") String unfileNonfolderObjects,
      @DefaultValue("false") @QueryParam("continueOnFailure") boolean continueOnFailure)
   {
      UnfileObject unfileObject;
      try
      {
         unfileObject =
            unfileNonfolderObjects == null ? UnfileObject.DELETE : UnfileObject.fromValue(unfileNonfolderObjects);
      }
      catch (IllegalArgumentException e)
      {
         throw new IllegalArgumentException("Unsupported 'unfileObject' attribute: " + unfileNonfolderObjects);
      }
      Connection connection = null;
      try
      {
         connection = CmisRegistry.getInstance().getConnection(repositoryId);
         Boolean deleteAllVersions = true; // TODO
         connection.deleteTree(folderId, deleteAllVersions, unfileObject, continueOnFailure);
         return Response.noContent().build();
      }
      catch (UpdateConflictException uce)
      {
         throw new WebApplicationException(uce, createErrorResponse(uce, 409));
      }
      catch (ObjectNotFoundException onfe)
      {
         throw new WebApplicationException(onfe, createErrorResponse(onfe, 404));
      }
      catch (InvalidArgumentException iae)
      {
         throw new WebApplicationException(iae, createErrorResponse(iae, 400));
      }
      catch (Throwable t)
      {
         throw new WebApplicationException(t, createErrorResponse(t, 500));
      }
      finally
      {
         if (connection != null)
         {
            connection.close();
         }
      }
   }

   @DELETE
   @Path("{repositoryId}/types/{typeId}")
   @RolesAllowed({"administrators"})
   public Response deleteType(@Context HttpServletRequest httpRequest, @PathParam("repositoryId") String repositoryId)
   {
      return deleteItem(repositoryId, httpRequest);
   }

   @GET
   @Path("{repositoryId}/objacl/{objectId}")
   @Produces("application/cmisacl+xml")
   public Response getACL(@PathParam("repositoryId") String repositoryId, @PathParam("objectId") String objectId,
      @DefaultValue("true") @QueryParam("onlyBasicPermissions") boolean onlyBasicPermissions)
   {
      Connection connection = null;
      try
      {
         connection = CmisRegistry.getInstance().getConnection(repositoryId);
         List<AccessControlEntry> list = connection.getACL(objectId, onlyBasicPermissions);
         FOMExtensibleElement accessControlListTypeElement =
            AbderaFactory.getInstance().getFactory().newElement(AtomCMIS.ACL);
         for (AccessControlEntry accessControlEntry : list)
         {
            AccessControlEntryTypeElement ace = accessControlListTypeElement.addExtension(AtomCMIS.PERMISSION);
            ace.build(accessControlEntry);
         }
         return Response.ok(accessControlListTypeElement).header(HttpHeaders.CACHE_CONTROL, "no-cache").build();
      }
      catch (InvalidArgumentException iae)
      {
         throw new WebApplicationException(iae, createErrorResponse(iae, 400));
      }
      catch (Throwable others)
      {
         throw new WebApplicationException(others, createErrorResponse(others, 500));
      }
      finally
      {
         if (connection != null)
         {
            connection.close();
         }
      }
   }

   @GET
   @Path("{repositoryId}/allowableactions/{objectId}")
   @Produces("application/atom+xml;type=allowableActions")
   public Response getAllowableActions(@PathParam("repositoryId") String repositoryId,
      @PathParam("objectId") String objectId)
   {
      Connection connection = null;
      try
      {
         connection = CmisRegistry.getInstance().getConnection(repositoryId);
         AllowableActions result = connection.getAllowableActions(objectId);
         AllowableActionsElement el = AbderaFactory.getInstance().getFactory().newElement(AtomCMIS.ALLOWABLE_ACTIONS);
         el.build(result);
         return Response.ok(el).header(HttpHeaders.CACHE_CONTROL, "no-cache").build();
      }
      catch (ObjectNotFoundException onfe)
      {
         throw new WebApplicationException(onfe, createErrorResponse(onfe, 404));
      }
      catch (InvalidArgumentException iae)
      {
         throw new WebApplicationException(iae, createErrorResponse(iae, 400));
      }
      catch (Throwable others)
      {
         throw new WebApplicationException(others, createErrorResponse(others, 500));
      }
      finally
      {
         if (connection != null)
         {
            connection.close();
         }
      }
   }

   @GET
   @Path("{repositoryId}/typedescendants")
   @Produces("application/cmistree+xml")
   public Response getAllTypeDescendants(@Context HttpServletRequest httpRequest,
      @PathParam("repositoryId") String repositoryId)
   {
      // The same jobs as 'getTypeDescendants()' but respect URL pattern.
      return getFeed(repositoryId, httpRequest);
   }

   @GET
   @Path("{repositoryId}/types")
   @Produces("application/atom+xml;type=entry")
   public Response getAllTypes(@Context HttpServletRequest httpRequest, @PathParam("repositoryId") String repositoryId)
   {
      // The same jobs as 'getTypes()' but respect URL pattern.
      return getTypes(httpRequest, repositoryId);
   }

   @GET
   // @Path("{repositoryId}/versions/{versionSeriesId}")
   @Path("{repositoryId}/versions/{versionSeriesId:.+}")
   @Produces("application/atom+xml;type=entry")
   public Response getAllVersions(@Context HttpServletRequest httpRequest,
      @PathParam("repositoryId") String repositoryId)
   {
      return getFeed(repositoryId, httpRequest);
   }

   @GET
   @Path("{repositoryId}/policies/{objectId}")
   public Response getAppliedPolicies(@Context HttpServletRequest httpRequest,
      @PathParam("repositoryId") String repositoryId)
   {
      return getFeed(repositoryId, httpRequest);
   }

   @GET
   @Path("{repositoryId}/changes")
   public Response getContentChanges(@Context HttpServletRequest httpRequest,
      @PathParam("repositoryId") String repositoryId)
   {
      return getFeed(repositoryId, httpRequest);
   }

   @GET
   @Path("{repositoryId}/checkedout")
   public Response getCheckedOut(@Context HttpServletRequest httpRequest, @PathParam("repositoryId") String repositoryId)
   {
      return getFeed(repositoryId, httpRequest);
   }

   @GET
   @Path("{repositoryId}/children/{folderId}")
   @Produces("application/atom+xml;type=feed")
   public Response getChildren(@Context HttpServletRequest httpRequest, @PathParam("repositoryId") String repositoryId)
   {
      return getFeed(repositoryId, httpRequest);
   }

   @GET
   @Path("{repositoryId}/file/{documentId}")
   public Response getContentStream(@Context HttpServletRequest httpRequest,
      @PathParam("repositoryId") String repositoryId)
   {
      RequestContext request = initRequestContext(httpRequest);
      Connection connection = null;
      try
      {
         connection = CmisRegistry.getInstance().getConnection(repositoryId);
         AbstractCmisCollection<?> collection = getCollection(request, connection);
         ResponseContext abderaResponse = collection.getMedia(request);
         ResponseBuilder builder = Response.status(abderaResponse.getStatus());
         copyAbderaHeaders(builder, abderaResponse);
         builder.entity(abderaResponse);
         // Cache-Control headers ?
         return builder.build();
      }
      finally
      {
         if (connection != null)
         {
            connection.close();
         }
      }

   }

   @GET
   @Path("{repositoryId}/descendants/{folderId}")
   @Produces("application/cmistree+xml")
   public Response getDescendants(@Context HttpServletRequest httpRequest,
      @PathParam("repositoryId") String repositoryId)
   {
      return getFeed(repositoryId, httpRequest);
   }

   @GET
   @Path("{repositoryId}/foldertree/{folderId}")
   @Produces("application/atom+xml;type=feed")
   public Response getFolderTree(@Context HttpServletRequest httpRequest, @PathParam("repositoryId") String repositoryId)
   {
      return getFeed(repositoryId, httpRequest);
   }

   @GET
   @Path("{repositoryId}/object/{objectId}")
   @Produces("application/atom+xml;type=entry")
   public Response getObjectById(@Context HttpServletRequest httpRequest, @PathParam("repositoryId") String repositoryId)
   {
      return getEntry(repositoryId, httpRequest);
   }

   @GET
   @Path("{repositoryId}/objectbypath")
   @Produces("application/atom+xml;type=entry")
   public Response getObjectByPath(@Context HttpServletRequest httpRequest,
      @PathParam("repositoryId") String repositoryId)
   {
      return getEntry(repositoryId, httpRequest);
   }

   @GET
   @Path("{repositoryId}/parents/{objectId}")
   public Response getObjectParents(@Context HttpServletRequest httpRequest,
      @PathParam("repositoryId") String repositoryId)
   {
      return getFeed(repositoryId, httpRequest);
   }

   @GET
   @Path("{repositoryId}/relationships/{objectId}")
   @Produces("application/atom+xml;type=entry")
   public Response getRelationships(@Context HttpServletRequest httpRequest,
      @PathParam("repositoryId") String repositoryId)
   {
      return getFeed(repositoryId, httpRequest);
   }

   @GET
   @Path("{repositoryId}/alternate/{documentId}/{streamId}")
   public Response getRendition(@Context HttpServletRequest httpRequest,
      @PathParam("repositoryId") String repositoryId, @PathParam("documentId") String documentId)
   {
      return getContentStream(httpRequest, repositoryId);
   }

   @GET
   public Response getRepositories(@Context HttpServletRequest httpRequest, @Context UriInfo uriInfo)
   {
      Service service = AbderaFactory.getInstance().getFactory().newService();
      service.declareNS(AtomCMIS.CMISRA_NS_URI, AtomCMIS.CMISRA_PREFIX);
      Set<RepositoryShortInfo> shortInfos = CmisRegistry.getInstance().getStorageInfos();
      RequestContext request = initRequestContext(httpRequest);
      if (shortInfos != null && !shortInfos.isEmpty())
      {
         for (RepositoryShortInfo info : shortInfos)
         {
            Workspace ws = service.addWorkspace(info.getRepositoryId());
            ws.setTitle(info.getRepositoryName());
            RepositoryInfoTypeElement repoInfoElement = ws.addExtension(AtomCMIS.REPOSITORY_INFO);
            repoInfoElement.addSimpleExtension(AtomCMIS.REPOSITORY_ID, info.getRepositoryId());
            repoInfoElement.addSimpleExtension(AtomCMIS.REPOSITORY_NAME, info.getRepositoryName());
            repoInfoElement.addSimpleExtension(AtomCMIS.ROOT_FOLDER_ID, info.getRootFolderId());

            String repoPath =
               UriBuilder.fromUri(uriInfo.getBaseUri()).path(getClass()).path(info.getRepositoryId()).build()
                  .toString();

            Collection<CollectionInfo> collectionsInfo = getCollectionsInfo(request);
            for (CollectionInfo collectionInfo : collectionsInfo)
            {
               CmisCollectionInfo cmisCollectionInfo = (CmisCollectionInfo)collectionInfo;
               String collectionType = cmisCollectionInfo.getCollectionType();
               if (AtomCMIS.COLLECTION_TYPE_ROOT.equals(collectionType))
                  ws.addCollection(((CmisCollectionInfo)collectionInfo).asCollectionElement(request, repoPath, info
                     .getRootFolderId()));
               else
                  ws.addCollection(((CmisCollectionInfo)collectionInfo).asCollectionElement(request, repoPath));
            }

            includeURITemplates(ws, repoPath);
         }
      }
      return Response.ok().entity(service).header(HttpHeaders.CACHE_CONTROL, "no-cache").type(
         MediaType.APPLICATION_ATOM_XML).build();
   }

   @GET
   @Path("{repositoryId}")
   public Response getRepositoryInfo(@Context HttpServletRequest httpRequest, @Context UriInfo uriInfo,
      @PathParam("repositoryId") String repositoryId)
   {
      Service service = AbderaFactory.getInstance().getFactory().newService();
      service.declareNS(AtomCMIS.CMIS_NS_URI, AtomCMIS.CMIS_PREFIX);
      service.declareNS(AtomCMIS.CMISRA_NS_URI, AtomCMIS.CMISRA_PREFIX);
      addCmisRepository(httpRequest, service, repositoryId, uriInfo.getBaseUri());
      Document<Service> serviceDocument = service.getDocument();
      serviceDocument.setCharset("utf-8");
      ResponseContext abderaResponse = new BaseResponseContext<Document<Service>>(serviceDocument);
      abderaResponse.setStatus(200);
      return Response.ok(abderaResponse).header(HttpHeaders.CACHE_CONTROL, "no-cache").type(
         MediaType.APPLICATION_ATOM_XML).build();
   }

   @GET
   @Path("{repositoryId}/typebyid/{typeId}")
   @Produces("application/atom+xml;type=entry")
   public Response getTypeById(@Context HttpServletRequest httpRequest, @PathParam("repositoryId") String repositoryId)
   {
      return getEntry(repositoryId, httpRequest);
   }

   @GET
   @Path("{repositoryId}/typedescendants/{typeId}")
   @Produces("application/cmistree+xml")
   public Response getTypeDescendants(@Context HttpServletRequest httpRequest,
      @PathParam("repositoryId") String repositoryId)
   {
      return getFeed(repositoryId, httpRequest);
   }

   @GET
   @Path("{repositoryId}/types/{typeId}")
   @Produces("application/atom+xml;type=entry")
   public Response getTypes(@Context HttpServletRequest httpRequest, @PathParam("repositoryId") String repositoryId)
   {
      return getFeed(repositoryId, httpRequest);
   }

   @POST
   @Path("{repositoryId}/query")
   public Response query(@Context HttpServletRequest httpRequest, @PathParam("repositoryId") String repositoryId)
   {
      RequestContext request = initRequestContext(httpRequest);
      Connection connection = null;
      try
      {
         connection = CmisRegistry.getInstance().getConnection(repositoryId);
         AbstractCmisCollection<?> collection = getCollection(request, connection);
         ResponseContext abderaResponse = collection.getFeed(request);
         ResponseBuilder builder =
            Response.status(abderaResponse.getStatus() == 200 ? 201 : abderaResponse.getStatus());
         copyAbderaHeaders(builder, abderaResponse);
         builder.header(HttpHeaders.CACHE_CONTROL, "no-cache");
         return builder.entity(abderaResponse).build();
      }
      finally
      {
         if (connection != null)
         {
            connection.close();
         }
      }
   }

   @GET
   @Path("{repositoryId}/query")
   public Response queryGET(@Context HttpServletRequest httpRequest, @PathParam("repositoryId") String repositoryId)
   {
      return getFeed(repositoryId, httpRequest);
   }

   @DELETE
   @Path("{repositoryId}/policies/{objectId}")
   public Response removePolicy(@Context HttpServletRequest httpRequest, @PathParam("repositoryId") String repositoryId)
   {
      return deleteItem(repositoryId, httpRequest);
   }

   @PUT
   @Path("{repositoryId}/file/{objectId}")
   public Response setContentStream(@Context HttpServletRequest httpRequest,
      @PathParam("repositoryId") String repositoryId)
   {
      RequestContext request = initRequestContext(httpRequest);
      Connection connection = null;
      try
      {
         connection = CmisRegistry.getInstance().getConnection(repositoryId);
         AbstractCmisCollection<?> collection = getCollection(request, connection);
         ResponseContext abderaResponse = collection.putMedia(request);
         ResponseBuilder builder = Response.status(abderaResponse.getStatus());
         copyAbderaHeaders(builder, abderaResponse);
         return builder.entity(abderaResponse).build();
      }
      finally
      {
         if (connection != null)
         {
            connection.close();
         }
      }
   }

   @POST
   @Path("{repositoryId}/file/{objectId}")
   @Consumes("multipart/form-data")
   public Response setContentStream(@Context HttpServletRequest httpRequest,
      @PathParam("repositoryId") String repositoryId, @QueryParam(HttpHeaders.CONTENT_TYPE) String contentType,
      Iterator<FileItem> files)
   {
      if (files.hasNext())
      {
         FileItem file = files.next();
         // In fact expected to get just one file. Other items
         // in iterator may be simple form fields, and we are not
         // care about it. Just skip all of them.
         if (!file.isFormField())
         {
            try
            {
               // Content-Type passed as query parameter, we do not
               // wont to use passed by browser. But if parameter does
               // not exists then try to get media type passed by browser.
               if (contentType == null)
               {
                  contentType = file.getContentType();
               }

               RequestContext request = initRequestContext(httpRequest);
               Connection connection = null;
               try
               {
                  connection = CmisRegistry.getInstance().getConnection(repositoryId);
                  AbstractCmisCollection<?> collection = getCollection(request, connection);
                  collection.putMedia(null, new javax.activation.MimeType(contentType), null, file.getInputStream(),
                     request);
                  return Response.status(201).build();
               }
               finally
               {
                  if (connection != null)
                  {
                     connection.close();
                  }
               }
            }
            catch (IOException ioe)
            {
               throw new WebApplicationException(ioe, createErrorResponse(ioe, 500));
            }
            catch (ResponseContextException rce)
            {
               throw new WebApplicationException(rce, createErrorResponse(rce, rce.getResponseContext().getStatus()));
            }
            catch (MimeTypeParseException mte)
            {
               throw new WebApplicationException(mte, createErrorResponse(mte, 400));
            }
         }
      }
      throw new WebApplicationException(new InvalidArgumentException("Content of document is missing."), 400);
   }

   @PUT
   @Path("{repositoryId}/object/{objectId}")
   @Produces("application/atom+xml;type=entry")
   public Response updateProperties(@Context HttpServletRequest httpRequest,
      @PathParam("repositoryId") String repositoryId)
   {
      RequestContext request = initRequestContext(httpRequest);
      Connection connection = null;
      try
      {
         connection = CmisRegistry.getInstance().getConnection(repositoryId);
         AbstractCmisCollection<?> collection = getCollection(request, connection);
         ResponseContext abderaResponse = collection.putEntry(request);
         ResponseBuilder builder = Response.status(abderaResponse.getStatus());
         copyAbderaHeaders(builder, abderaResponse);
         return builder.entity(abderaResponse).build();
      }
      finally
      {
         if (connection != null)
         {
            connection.close();
         }
      }
   }

   private void copyAbderaHeaders(ResponseBuilder responseBuilder, ResponseContext abderaResponse)
   {
      for (String headerName : abderaResponse.getHeaderNames())
      {
         for (Object v : abderaResponse.getHeaders(headerName))
         {
            // TODO : need avoid direct casting to String.
            // For now just be sure not get errors if RESTful framework.
            responseBuilder.header(headerName, v.toString());
         }
      }
   }

   @POST
   @Path("{repositoryId}/unfiled")
   public Response unfile(@Context HttpServletRequest httpRequest, @PathParam("repositoryId") String repositoryId)
      throws Exception
   {
      return createItem(repositoryId, httpRequest);
   }

   @GET
   @Path("{repositoryId}/unfiled")
   public Response getUnfiled(@Context HttpServletRequest httpRequest, @PathParam("repositoryId") String repositoryId)
   {
      return getFeed(repositoryId, httpRequest);
   }

   private Workspace addCmisRepository(HttpServletRequest httpRequest, Service service, String repositoryId, URI baseUri)
   {
      RepositoryInfo repoInfo;
      Connection connection = null;
      try
      {
         connection = CmisRegistry.getInstance().getConnection(repositoryId);
         repoInfo = connection.getStorage().getRepositoryInfo();
      }
      catch (InvalidArgumentException iae)
      {
         throw new WebApplicationException(iae, createErrorResponse(iae, 400));
      }
      catch (Throwable others)
      {
         throw new WebApplicationException(others, createErrorResponse(others, 500));
      }
      finally
      {
         if (connection != null)
         {
            connection.close();
         }
      }

      Workspace ws = service.addWorkspace(repositoryId);
      ws.setTitle(repoInfo.getRepositoryName());
      RepositoryInfoTypeElement repoInfoElement = ws.addExtension(AtomCMIS.REPOSITORY_INFO);
      repoInfoElement.build(repoInfo);

      String repoPath = UriBuilder.fromUri(baseUri).path(getClass()).path(repositoryId).build().toString();

      RequestContext request = initRequestContext(httpRequest);

      Collection<CollectionInfo> collectionsInfo = getCollectionsInfo(request);
      for (CollectionInfo collectionInfo : collectionsInfo)
      {
         CmisCollectionInfo cmisCollectionInfo = (CmisCollectionInfo)collectionInfo;
         String collectionType = cmisCollectionInfo.getCollectionType();
         if (AtomCMIS.COLLECTION_TYPE_ROOT.equals(collectionType))
            ws.addCollection(((CmisCollectionInfo)collectionInfo).asCollectionElement(request, repoPath, repoInfo
               .getRootFolderId()));
         else
            ws.addCollection(((CmisCollectionInfo)collectionInfo).asCollectionElement(request, repoPath));
      }

      includeURITemplates(ws, repoPath);

      // Add links
      Factory factory = AbderaFactory.getInstance().getFactory();
      Link typeTypeDescendantsLink = factory.newLink(ws);
      typeTypeDescendantsLink.setTitle("Type descendants");
      typeTypeDescendantsLink.setRel(AtomCMIS.LINK_CMIS_TYPEDESCENDANTS);
      typeTypeDescendantsLink.setMimeType(AtomCMIS.MEDIATYPE_CMISTREE);
      typeTypeDescendantsLink.setHref(repoPath + "/typedescendants");

      RepositoryCapabilities capabilities = repoInfo.getCapabilities();
      if (capabilities.isCapabilityGetDescendants())
      {
         Link descendantsLink = factory.newLink(ws);
         descendantsLink.setTitle("Root folder descendants");
         descendantsLink.setRel(AtomCMIS.LINK_CMIS_ROOTDESCENDANTS);
         descendantsLink.setMimeType(AtomCMIS.MEDIATYPE_CMISTREE);
         descendantsLink.setHref(repoPath + "/descendants/" + repoInfo.getRootFolderId());
      }
      if (capabilities.isCapabilityGetFolderTree())
      {
         Link treeLink = factory.newLink(ws);
         treeLink.setTitle("Root folder tree");
         treeLink.setRel(AtomCMIS.LINK_CMIS_FOLDERTREE);
         treeLink.setMimeType(AtomCMIS.MEDIATYPE_CMISTREE);
         treeLink.setHref(repoPath + "/foldertree/" + repoInfo.getRootFolderId());
      }
      if (CapabilityChanges.NONE != capabilities.getCapabilityChanges())
      {
         Link treeLink = factory.newLink(ws);
         treeLink.setTitle("Changes log");
         treeLink.setRel(AtomCMIS.LINK_CMIS_CHANGES);
         treeLink.setMimeType(AtomCMIS.MEDIATYPE_ATOM_FEED);
         treeLink.setHref(repoPath + "/changes");
      }

      return ws;
   }

   private void includeURITemplates(Workspace ws, String repoPath)
   {
      // objectbyid template
      CmisUriTemplateType objectById = new CmisUriTemplateType();
      objectById.setMediatype(AtomCMIS.MEDIATYPE_ATOM_ENTRY);
      objectById.setTemplate(new StringBuilder() //
         .append(repoPath) //
         .append("/object/{id}?") //
         .append("filter={filter}&") //
         .append("includeAllowableActions={includeAllowableActions}&") //
         .append("includePolicyIds={includePolicyIds}&") //
         .append("includeRelationships={includeRelationships}&") //
         .append("includeACL={includeACL}&") //
         .append("renditionFilter={renditionFilter}").toString());
      objectById.setType(AtomCMIS.URITEMPLATE_OBJECTBYID);
      UriTemplateTypeElement objectByIdElement = ws.addExtension(AtomCMIS.URITEMPLATE);
      objectByIdElement.build(objectById);

      // objectbypath template
      CmisUriTemplateType objectByPath = new CmisUriTemplateType();
      objectByPath.setMediatype(AtomCMIS.MEDIATYPE_ATOM_ENTRY);
      objectByPath.setTemplate(new StringBuilder() //
         .append(repoPath) //
         .append("/objectbypath?")//
         .append("path={path}&")//
         .append("filter={filter}&")//
         .append("includeAllowableActions={includeAllowableActions}&")//
         .append("includePolicyIds={includePolicyIds}&")//
         .append("includeRelationships={includeRelationships}&")//
         .append("includeACL={includeACL}&") //
         .append("renditionFilter={renditionFilter}").toString());
      objectByPath.setType(AtomCMIS.URITEMPLATE_OBJECTBYPATH);
      UriTemplateTypeElement folderByPathElement = ws.addExtension(AtomCMIS.URITEMPLATE);
      folderByPathElement.build(objectByPath);

      // query template
      CmisUriTemplateType query = new CmisUriTemplateType();
      query.setMediatype(AtomCMIS.MEDIATYPE_ATOM_FEED);
      query.setTemplate(new StringBuilder() //
         .append(repoPath) //
         .append("/query?")//
         .append("q={q}&")//
         .append("searchAllVersions={searchAllVersions}&") //
         .append("maxItems={maxItems}&") //
         .append("skipCount={skipCount}&") //
         .append("includeAllowableActions={includeAllowableActions}&")//
         .append("includeRelationships={includeRelationships}").toString());
      query.setType(AtomCMIS.URITEMPLATE_QUERY);
      UriTemplateTypeElement queryElement = ws.addExtension(AtomCMIS.URITEMPLATE);
      queryElement.build(query);

      // typebyid template
      CmisUriTemplateType typeById = new CmisUriTemplateType();
      typeById.setMediatype(AtomCMIS.MEDIATYPE_ATOM_ENTRY);
      typeById.setTemplate(repoPath + "/typebyid/{id}");
      typeById.setType(AtomCMIS.URITEMPLATE_TYPEBYID);
      UriTemplateTypeElement typeByIdElement = ws.addExtension(AtomCMIS.URITEMPLATE);
      typeByIdElement.build(typeById);

      // changes
      CmisUriTemplateType changes = new CmisUriTemplateType();
      changes.setMediatype(AtomCMIS.MEDIATYPE_ATOM_FEED);
      changes.setTemplate(new StringBuilder() //
         .append(repoPath) //
         .append("/changes?")//
         .append("changeLogToken={changeLogToken}&")//
         .append("includeProperties={includeProperties}&") //
         .append("includePolicyIds={includePolicyIds}&") //
         .append("includeACL={includeACL}&") //
         .append("filter={filter}&")//
         .append("maxItems={maxItems}").toString());
      changes.setType(AtomCMIS.URITEMPLATE_CHANGES);
      UriTemplateTypeElement changesElement = ws.addExtension(AtomCMIS.URITEMPLATE);
      changesElement.build(changes);
   }

   protected Response createErrorResponse(Throwable t, int status)
   {
      return Response.status(status).entity(t.getMessage()).type("text/plain").build();
   }

   protected Response createItem(String repositoryId, HttpServletRequest httpRequest)
   {
      RequestContext request = initRequestContext(httpRequest);
      Connection connection = null;
      try
      {
         connection = CmisRegistry.getInstance().getConnection(repositoryId);
         AbstractCmisCollection<?> collection = getCollection(request, connection);
         ResponseContext abderaResponse = collection.postEntry(request);
         ResponseBuilder builder = Response.status(abderaResponse.getStatus());
         copyAbderaHeaders(builder, abderaResponse);
         return builder.entity(abderaResponse).build();
      }
      finally
      {
         if (connection != null)
         {
            connection.close();
         }
      }
   }

   protected Response deleteItem(String repositoryId, HttpServletRequest httpRequest)
   {
      RequestContext request = initRequestContext(httpRequest);
      Connection connection = null;
      try
      {
         connection = CmisRegistry.getInstance().getConnection(repositoryId);
         AbstractCmisCollection<?> collection = getCollection(request, connection);
         ResponseContext abderaResponse = collection.deleteEntry(request);
         ResponseBuilder builder = Response.status(abderaResponse.getStatus());
         copyAbderaHeaders(builder, abderaResponse);
         return builder.entity(abderaResponse).build();
      }
      finally
      {
         if (connection != null)
         {
            connection.close();
         }
      }
   }

   protected AbstractCmisCollection<?> getCollection(RequestContext request, Connection connection)
   {
      WorkspaceManagerImpl wm = (WorkspaceManagerImpl)provider.getWorkspaceManager(request);
      AbstractCmisCollection<?> collection = wm.getCollectionAdapter(request, connection);
      return collection;
   }

   protected Collection<CollectionInfo> getCollectionsInfo(RequestContext request)
   {
      Collection<WorkspaceInfo> workspaces = (provider).getWorkspaceManager(request).getWorkspaces(request);
      Collection<CollectionInfo> collections = workspaces.iterator().next().getCollections(request);
      return collections;
   }

   protected Response getEntry(String repositoryId, HttpServletRequest httpRequest)
   {
      RequestContext request = initRequestContext(httpRequest);
      Connection connection = null;
      try
      {
         connection = CmisRegistry.getInstance().getConnection(repositoryId);
         AbstractCmisCollection<?> collection = getCollection(request, connection);
         ResponseContext abderaResponse = collection.getEntry(request);
         ResponseBuilder builder = Response.status(abderaResponse.getStatus());
         copyAbderaHeaders(builder, abderaResponse);
         builder.header(HttpHeaders.CACHE_CONTROL, "no-cache");
         return builder.entity(abderaResponse).build();
      }
      finally
      {
         if (connection != null)
         {
            connection.close();
         }
      }
   }

   protected Response getFeed(String repositoryId, HttpServletRequest httpRequest)
   {
      RequestContext request = initRequestContext(httpRequest);
      Connection connection = null;
      try
      {
         connection = CmisRegistry.getInstance().getConnection(repositoryId);
         AbstractCmisCollection<?> collection = getCollection(request, connection);
         ResponseContext abderaResponse = collection.getFeed(request);
         ResponseBuilder builder = Response.status(abderaResponse.getStatus());
         copyAbderaHeaders(builder, abderaResponse);
         builder.header(HttpHeaders.CACHE_CONTROL, "no-cache");
         return builder.entity(abderaResponse).build();
      }
      finally
      {
         if (connection != null)
         {
            connection.close();
         }
      }
   }

   private RequestContext initRequestContext(HttpServletRequest httpRequest)
   {
      return new ServletRequestContext(provider, httpRequest)
      {
         @Override
         public String getTargetPath()
         {
            String uri = getUri().toString();
            String bpath = getTargetBasePath();
            return uri.substring(bpath.length());
         }
      };
   }

}
