org.xcmis.restatom.collections.CmisObjectCollection.java Source code

Java tutorial

Introduction

Here is the source code for org.xcmis.restatom.collections.CmisObjectCollection.java

Source

/**
 * 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.collections;

import org.apache.abdera.factory.Factory;
import org.apache.abdera.i18n.iri.IRI;
import org.apache.abdera.model.Content;
import org.apache.abdera.model.Entry;
import org.apache.abdera.model.Feed;
import org.apache.abdera.model.Link;
import org.apache.abdera.model.Person;
import org.apache.abdera.model.Content.Type;
import org.apache.abdera.protocol.server.RequestContext;
import org.apache.abdera.protocol.server.ResponseContext;
import org.apache.abdera.protocol.server.TargetType;
import org.apache.abdera.protocol.server.context.EmptyResponseContext;
import org.apache.abdera.protocol.server.context.ResponseContextException;
import org.apache.commons.codec.binary.Base64;
import org.xcmis.restatom.AtomCMIS;
import org.xcmis.restatom.AtomUtils;
import org.xcmis.restatom.BinaryResponseContext;
import org.xcmis.restatom.abdera.ContentTypeElement;
import org.xcmis.restatom.abdera.ObjectTypeElement;
import org.xcmis.restatom.types.CmisContentType;
import org.xcmis.restatom.types.EnumReturnVersion;
import org.xcmis.spi.BaseContentStream;
import org.xcmis.spi.ChangeTokenHolder;
import org.xcmis.spi.CmisConstants;
import org.xcmis.spi.Connection;
import org.xcmis.spi.ConstraintException;
import org.xcmis.spi.ContentAlreadyExistsException;
import org.xcmis.spi.ContentStream;
import org.xcmis.spi.FilterNotValidException;
import org.xcmis.spi.InvalidArgumentException;
import org.xcmis.spi.NameConstraintViolationException;
import org.xcmis.spi.ObjectNotFoundException;
import org.xcmis.spi.StorageException;
import org.xcmis.spi.StreamNotSupportedException;
import org.xcmis.spi.UpdateConflictException;
import org.xcmis.spi.model.AccessControlEntry;
import org.xcmis.spi.model.BaseType;
import org.xcmis.spi.model.CmisObject;
import org.xcmis.spi.model.IncludeRelationships;
import org.xcmis.spi.model.Property;
import org.xcmis.spi.model.Rendition;
import org.xcmis.spi.model.RepositoryCapabilities;
import org.xcmis.spi.model.RepositoryInfo;
import org.xcmis.spi.model.impl.StringProperty;
import org.xcmis.spi.utils.Logger;

import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.security.Principal;
import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.activation.MimeType;
import javax.activation.MimeTypeParameterList;
import javax.ws.rs.core.HttpHeaders;

/**
 * @author <a href="mailto:andrey.parfonov@exoplatform.com">Andrey Parfonov</a>
 * @version $Id: CmisObjectCollection.java 218 2010-02-15 07:38:06Z andrew00x $
 */
public abstract class CmisObjectCollection extends AbstractCmisCollection<CmisObject> {

    private class HttpConnectionStream extends InputStream {
        private final HttpURLConnection httpConnection;

        private InputStream inStream;

        private boolean closed;

        public HttpConnectionStream(HttpURLConnection httpConnection) {
            this.httpConnection = httpConnection;
        }

        public int read() throws IOException {
            if (inStream == null) {
                inStream = httpConnection.getInputStream();
            }
            int i = inStream.read();
            if (i == -1) {
                if (!closed) {
                    try {
                        inStream.close();
                        httpConnection.disconnect();
                        closed = true;
                    } catch (IOException e) {
                        LOG.error(e.getMessage(), e);
                    }
                }
            }
            return i;
        }

        @Override
        protected void finalize() throws Throwable {
            if (!closed) {
                try {
                    inStream.close();
                    httpConnection.disconnect();
                } catch (IOException e) {
                    LOG.error(e.getMessage(), e);
                }
            }
            super.finalize();
        }
    }

    private static final Logger LOG = Logger.getLogger(CmisObjectCollection.class);

    /** The Constant SPACES_AIR_SPECIFIC_REFERER. */
    protected static final String SPACES_AIR_SPECIFIC_REFERER = "app:/CMISSpacesAir.swf";

    public CmisObjectCollection(Connection connection) {
        super(connection);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void deleteEntry(String objectId, RequestContext request) throws ResponseContextException {
        try {
            Connection connection = getConnection(request);
            connection.deleteObject(objectId, getBooleanParameter(request, AtomCMIS.PARAM_ALL_VERSIONS, true));
        } catch (ConstraintException cve) {
            throw new ResponseContextException(createErrorResponse(cve, 409));
        } catch (StorageException re) {
            throw new ResponseContextException(createErrorResponse(re, 500));
        } catch (UpdateConflictException uce) {
            throw new ResponseContextException(createErrorResponse(uce, 409));
        } catch (ObjectNotFoundException onfe) {
            throw new ResponseContextException(createErrorResponse(onfe, 404));
        } catch (InvalidArgumentException iae) {
            throw new ResponseContextException(createErrorResponse(iae, 400));
        } catch (Throwable t) {
            throw new ResponseContextException(createErrorResponse(t, 500));
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ResponseContext deleteMedia(RequestContext request) {
        try {
            Connection connection = getConnection(request);
            ChangeTokenHolder changeTokenHolder = new ChangeTokenHolder();
            changeTokenHolder.setValue(request.getHeader(HttpHeaders.IF_MATCH));
            String objectId = connection.deleteContentStream(getId(request), changeTokenHolder /*changeToken*/);
            CmisObject object = connection.getProperties(objectId, true, CmisConstants.CHANGE_TOKEN);
            @SuppressWarnings("unchecked")
            Property<String> changeToken = (Property<String>) getProperty(object, CmisConstants.CHANGE_TOKEN);
            ResponseContext response = new EmptyResponseContext(204);
            if (changeToken != null && changeToken.getValues().size() > 0) {
                response.setEntityTag(changeToken.getValues().get(0));
            }
            return response;
        } catch (ConstraintException cve) {
            return createErrorResponse(cve, 409);
        } catch (StorageException re) {
            return createErrorResponse(re, 500);
        } catch (UpdateConflictException uce) {
            return createErrorResponse(uce, 409);
        } catch (ObjectNotFoundException onfe) {
            return createErrorResponse(onfe, 404);
        } catch (InvalidArgumentException iae) {
            return createErrorResponse(iae, 400);
        } catch (Throwable t) {
            return createErrorResponse(t, 500);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void deleteMedia(String documentId, RequestContext request) throws ResponseContextException {
        try {
            Connection connection = getConnection(request);
            ChangeTokenHolder changeTokenHolder = new ChangeTokenHolder();
            changeTokenHolder.setValue(request.getHeader(HttpHeaders.IF_MATCH));
            connection.deleteContentStream(documentId, changeTokenHolder);
        } catch (ConstraintException cve) {
            throw new ResponseContextException(createErrorResponse(cve, 409));
        } catch (StorageException re) {
            throw new ResponseContextException(createErrorResponse(re, 500));
        } catch (UpdateConflictException uce) {
            throw new ResponseContextException(createErrorResponse(uce, 409));
        } catch (ObjectNotFoundException onfe) {
            throw new ResponseContextException(createErrorResponse(onfe, 404));
        } catch (InvalidArgumentException iae) {
            throw new ResponseContextException(createErrorResponse(iae, 400));
        } catch (Throwable t) {
            throw new ResponseContextException(createErrorResponse(t, 500));
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getAuthor(RequestContext request) throws ResponseContextException {
        Principal principal = request.getPrincipal();
        if (principal != null) {
            return principal.getName();
        }
        return ANONYMOUS;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<Person> getAuthors(CmisObject object, RequestContext request) throws ResponseContextException {
        String author = object.getObjectInfo().getCreatedBy();
        Person p = request.getAbdera().getFactory().newAuthor();
        if (author != null) {
            p.setName(author);
        } else {
            p.setName(SYSTEM);
        }
        return Collections.singletonList(p);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getContentType(CmisObject object) {
        String contentType = object.getObjectInfo().getContentStreamMimeType();
        if (contentType != null //
                && !"".equals(contentType)) {
            return contentType;
        }
        return null;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public CmisObject getEntry(String id, RequestContext request) throws ResponseContextException {
        try {
            boolean includeAllowableActions = getBooleanParameter(request, AtomCMIS.PARAM_INCLUDE_ALLOWABLE_ACTIONS,
                    false);
            String propertyFilter = request.getParameter(AtomCMIS.PARAM_FILTER);
            boolean includePolicies = getBooleanParameter(request, AtomCMIS.PARAM_INCLUDE_POLICY_IDS, false);
            boolean includeACL = getBooleanParameter(request, AtomCMIS.PARAM_INCLUDE_ACL, false);
            String renditionFilter = request.getParameter(AtomCMIS.PARAM_RENDITION_FILTER);
            IncludeRelationships includeRelationships;
            try {
                includeRelationships = request.getParameter(AtomCMIS.PARAM_INCLUDE_RELATIONSHIPS) == null
                        || request.getParameter(AtomCMIS.PARAM_INCLUDE_RELATIONSHIPS).length() == 0
                                ? IncludeRelationships.NONE
                                : IncludeRelationships
                                        .fromValue(request.getParameter(AtomCMIS.PARAM_INCLUDE_RELATIONSHIPS));
            } catch (IllegalArgumentException iae) {
                String msg = "Invalid parameter " + request.getParameter(AtomCMIS.PARAM_INCLUDE_RELATIONSHIPS);
                throw new ResponseContextException(createErrorResponse(msg, 400));
            }

            Connection connection = getConnection(request);

            CmisObject object;
            if (id.charAt(0) != '/') {
                // Get by id.
                object = connection.getObject(id, includeAllowableActions, includeRelationships, includePolicies,
                        includeACL, true, propertyFilter, renditionFilter);
            } else {
                // Get by path.
                object = connection.getObjectByPath(id, includeAllowableActions, includeRelationships,
                        includePolicies, includeACL, true, propertyFilter, renditionFilter);
            }
            BaseType type = getBaseObjectType(object);
            if (type == BaseType.DOCUMENT) {
                String returnVersion = request.getParameter(AtomCMIS.PARAM_RETURN_VERSION);
                if (returnVersion == null || returnVersion.length() == 0) {
                    return object;
                }
                EnumReturnVersion enumReturnVersion;
                try {
                    enumReturnVersion = EnumReturnVersion.fromValue(returnVersion);
                } catch (IllegalArgumentException iae) {
                    String msg = "Invalid parameter " + returnVersion;
                    throw new ResponseContextException(createErrorResponse(msg, 400));
                }
                if (enumReturnVersion == EnumReturnVersion.THIS) {
                    return object;
                }

                if (enumReturnVersion == EnumReturnVersion.LATEST && object.getObjectInfo().isLatestVersion()) {
                    return object;
                }

                if (enumReturnVersion == EnumReturnVersion.LATESTMAJOR
                        && object.getObjectInfo().isLatestMajorVersion()) {
                    return object;
                }

                // Find latest in Version series.
                String versionSeriesId = object.getObjectInfo().getVersionSeriesId();
                return connection.getObjectOfLatestVersion(//
                        versionSeriesId, //
                        enumReturnVersion == EnumReturnVersion.LATESTMAJOR, //
                        includeAllowableActions, //
                        includeRelationships, //
                        includePolicies, //
                        includeACL, //
                        true, //
                        propertyFilter, //
                        renditionFilter);
            }

            // Is not document.
            return object;
        } catch (FilterNotValidException fe) {
            throw new ResponseContextException(createErrorResponse(fe, 400));
        } catch (ObjectNotFoundException onfe) {
            throw new ResponseContextException(createErrorResponse(onfe, 404));
        } catch (InvalidArgumentException iae) {
            throw new ResponseContextException(createErrorResponse(iae, 400));
        } catch (Throwable t) {
            throw new ResponseContextException(createErrorResponse(t, 500));
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getId(CmisObject object) throws ResponseContextException {
        return object.getObjectInfo().getId();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getId(RequestContext request) {
        return request.getTarget().getParameter("objectid");
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ResponseContext getMedia(RequestContext request) {
        try {
            Connection connection = getConnection(request);
            // TODO : resolve (optional) offset, length
            ContentStream content = connection.getContentStream(getId(request), getStreamId(request));
            ResponseContext response = new BinaryResponseContext(content.getStream(), 200);
            response.setContentType(content.getMediaType().toString());
            response.setContentLength(content.length());
            response.setHeader(AtomCMIS.CONTENT_DISPOSITION_HEADER, //
                    "attachment; filename=\"" + content.getFileName() + "\"");
            return response;
        } catch (ObjectNotFoundException onfe) {
            return createErrorResponse(onfe, 404);
        } catch (ConstraintException cve) {
            return createErrorResponse(cve, 409);
        } catch (Throwable t) {
            return createErrorResponse(t, 500);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getName(CmisObject object) throws ResponseContextException {
        return object.getObjectInfo().getName();
    }

    /**
     * {@inheritDoc}
     */
    public String getStreamId(RequestContext request) {
        return request.getTarget().getParameter("streamid");
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getTitle(CmisObject object) throws ResponseContextException {
        return object.getObjectInfo().getName();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Date getUpdated(CmisObject object) throws ResponseContextException {
        return getLastModificationDate(object).getTime();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ResponseContext putEntry(RequestContext request) {
        Entry entry;
        try {
            entry = getEntryFromRequest(request);
        } catch (ResponseContextException rce) {
            //         rce.printStackTrace();
            return rce.getResponseContext();
        }

        try {
            Connection connection = getConnection(request);
            ObjectTypeElement objectElement = entry.getFirstChild(AtomCMIS.OBJECT);
            CmisObject object = objectElement != null ? objectElement.getObject() : new CmisObject();
            updatePropertiesFromEntry(object, entry);

            Map<String, Property<?>> properties = object.getProperties();
            Collection<String> policyIds = object.getPolicyIds();
            List<AccessControlEntry> acl = object.getACL();
            ContentStream contentStream = getContentStream(entry, request);

            boolean checkin = getBooleanParameter(request, AtomCMIS.PARAM_CHECKIN, false);
            String updatedId = null;
            if (checkin) {
                // If 'checkin' param is TRUE, execute checkin() service.
                boolean major = getBooleanParameter(request, AtomCMIS.PARAM_MAJOR, true);
                String checkinComment = request.getParameter(AtomCMIS.PARAM_CHECKIN_COMMENT);
                // TODO : ACEs for removing. Not clear from specification how to
                // pass (obtain) ACEs for adding and removing from one object.
                updatedId = connection.checkin(getId(request), major, properties, contentStream, checkinComment,
                        acl, null, policyIds);
            } else {
                // If 'checkin' param is FALSE, execute updateProperties() service.
                // Get 'if-match' header as is, according to HTTP specification :
                // http://www.w3.org/Protocols/rfc2616/rfc2616-sec13.html#sec13.3.3
                // ------------------------------------------
                // Clients MAY issue simple (non-subrange) GET requests with either
                // weak validators or strong validators. Clients MUST NOT use weak
                // validators in other forms of request.
                // ------------------------------------------
                // Method is PUT - use strong comparison.
                CmisObject cmisObject = connection.getProperties(getId(request), true, CmisConstants.BASE_TYPE_ID);
                BaseType baseType = cmisObject.getObjectInfo().getBaseType();

                ChangeTokenHolder changeTokenHolder = new ChangeTokenHolder();
                changeTokenHolder.setValue(request.getHeader(HttpHeaders.IF_MATCH));
                updatedId = connection.updateProperties(getId(request), changeTokenHolder, properties);
                if (baseType == BaseType.DOCUMENT && contentStream != null) {
                    updatedId = connection.setContentStream(getId(request), contentStream, changeTokenHolder, true);
                }

            }

            CmisObject updated = connection.getProperties(updatedId, true, CmisConstants.WILDCARD);
            entry = request.getAbdera().getFactory().newEntry();
            addEntryDetails(request, entry, request.getResolvedUri(), updated);
            return buildGetEntryResponse(request, entry);
        } catch (ConstraintException cve) {
            return createErrorResponse(cve, 409);
        } catch (NameConstraintViolationException nce) {
            return createErrorResponse(nce, 409);
        } catch (StorageException re) {
            return createErrorResponse(re, 500);
        } catch (UpdateConflictException uce) {
            return createErrorResponse(uce, 409);
        } catch (InvalidArgumentException iae) {
            return createErrorResponse(iae, 400);
        } catch (ResponseContextException rce) {
            return rce.getResponseContext();
        } catch (Throwable t) {
            return createErrorResponse(t, 500);
        }
    }

    /**
     * Put media.
     *
     * @param entryObj the entry obj
     * @param contentType the content type
     * @param slug the slug
     * @param inputStream the input stream
     * @param request the request
     *
     * @throws ResponseContextException the response context exception
     */
    @Override
    public void putMedia(CmisObject entryObj, MimeType contentType, String slug, InputStream inputStream,
            RequestContext request) throws ResponseContextException {
        try {
            Connection connection = getConnection(request);
            ContentStream content = new BaseContentStream(inputStream, null, convertMimeType(contentType));
            ChangeTokenHolder changeTokenHolder = new ChangeTokenHolder();
            changeTokenHolder.setValue(request.getHeader(HttpHeaders.IF_MATCH));
            boolean overwriteFlag = getBooleanParameter(request, AtomCMIS.PARAM_OVERWRITE_FLAG, true);
            connection.setContentStream(getId(request), content, changeTokenHolder, overwriteFlag);
        } catch (ConstraintException cve) {
            throw new ResponseContextException(createErrorResponse(cve, 409));
        } catch (StorageException re) {
            throw new ResponseContextException(createErrorResponse(re, 409));
        } catch (ContentAlreadyExistsException ce) {
            throw new ResponseContextException(createErrorResponse(ce, 409));
        } catch (StreamNotSupportedException se) {
            // XXX : specification says 403, is it correct ?
            throw new ResponseContextException(createErrorResponse(se, 400));
        } catch (UpdateConflictException uce) {
            throw new ResponseContextException(createErrorResponse(uce, 409));
        } catch (Throwable t) {
            throw new ResponseContextException(createErrorResponse(t, 500));
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public ResponseContext putMedia(RequestContext request) {
        try {
            Connection connection = getConnection(request);
            ContentStream content = new BaseContentStream(request.getInputStream(), null,
                    convertMimeType(request.getContentType()));
            ChangeTokenHolder changeTokenHolder = new ChangeTokenHolder();
            changeTokenHolder.setValue(request.getHeader(HttpHeaders.IF_MATCH));
            boolean overwriteFlag = getBooleanParameter(request, AtomCMIS.PARAM_OVERWRITE_FLAG, true);
            String updatedId = connection.setContentStream(getId(request), content, changeTokenHolder,
                    overwriteFlag);
            CmisObject updated = connection.getProperties(updatedId, true, CmisConstants.CHANGE_TOKEN);
            ResponseContext response = new EmptyResponseContext(201);
            String contentLink = getContentLink(getId(request), request);
            response.setHeader(HttpHeaders.CONTENT_LOCATION, contentLink);
            response.setHeader(HttpHeaders.LOCATION, contentLink);
            String changeToken = updated.getObjectInfo().getChangeToken();
            if (changeToken != null) {
                response.setEntityTag(changeToken);
            }
            return response;
        } catch (IOException ioe) {
            return createErrorResponse(ioe, 500);
        } catch (ConstraintException cve) {
            return createErrorResponse(cve, 409);
        } catch (StorageException re) {
            return createErrorResponse(re, 409);
        } catch (ContentAlreadyExistsException ce) {
            return createErrorResponse(ce, 409);
        } catch (StreamNotSupportedException se) {
            // XXX : specification says 403, is it correct ?
            return createErrorResponse(se, 400);
        } catch (UpdateConflictException uce) {
            return createErrorResponse(uce, 409);
        } catch (Throwable t) {
            return createErrorResponse(t, 500);
        }
    }

    /**
     * Process rendition links.
     *
     * @param entry the entry
     * @param object the object
     * @param request the request
     *
     * @throws ResponseContextException the response context exception
     */
    private void processRenditionLinks(Entry entry, CmisObject object, RequestContext request)
            throws ResponseContextException {
        String baseRenditionHref = getBaseRenditionHref(getId(object), request);

        List<Rendition> renditionList = object.getRenditions();
        for (Rendition rendition : renditionList) {
            Link link = entry.addLink(//
                    baseRenditionHref + "/" + rendition.getStreamId(), //
                    AtomCMIS.LINK_ALTERNATE, //
                    rendition.getMimeType(), //
                    rendition.getTitle(), //
                    null, //
                    rendition.getLength());
            link.setAttributeValue(AtomCMIS.RENDITION_KIND, rendition.getKind());
            entry.addLink(link);
        }
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected String addEntryDetails(RequestContext request, Entry entry, IRI feedIri, CmisObject object)
            throws ResponseContextException {
        String objectId = getId(object);
        entry.setId(objectId);
        // Updated and published is incorrect when pass Date.
        // Abdera uses Calendar.getInstance(TimeZone.getTimeZone("GMT"))
        // See org.apache.abdera.model.AtomDate .
        entry.setPublished(AtomUtils.getAtomDate(getCreationDate(object)));
        entry.setUpdated(AtomUtils.getAtomDate(getLastModificationDate(object)));
        entry.setSummary("");
        for (Person person : getAuthors(object, request)) {
            entry.addAuthor(person);
        }

        entry.setTitle(getTitle(object));

        // Service link.
        String service = getServiceLink(request);
        entry.addLink(service, AtomCMIS.LINK_SERVICE, AtomCMIS.MEDIATYPE_ATOM_SERVICE, null, null, -1);

        String self = getObjectLink(objectId, request);
        // Self link.
        entry.addLink(self, AtomCMIS.LINK_SELF);
        // Edit link.
        entry.addLink(self, AtomCMIS.LINK_EDIT);

        // Alternate links.
        processRenditionLinks(entry, object, request);

        // Object type link.
        String typeId = object.getObjectInfo().getTypeId();
        entry.addLink(getObjectTypeLink(typeId, request), AtomCMIS.LINK_DESCRIBEDBY, AtomCMIS.MEDIATYPE_ATOM_ENTRY,
                null, null, -1);

        // Allowable actions link.
        entry.addLink(getAllowableActionsLink(objectId, request), AtomCMIS.LINK_CMIS_ALLOWABLEACTIONS,
                AtomCMIS.MEDIATYPE_ALLOWABLE_ACTIONS, null, null, -1);

        BaseType baseType = getBaseObjectType(object);
        if (baseType == BaseType.FOLDER) {
            // Relationships link.
            String relationships = getRelationshipsLink(objectId, request);
            entry.addLink(relationships, AtomCMIS.LINK_CMIS_RELATIONSHIPS, AtomCMIS.MEDIATYPE_ATOM_FEED, null, null,
                    -1);

            // Policies link.
            String policies = getPoliciesLink(objectId, request);
            entry.addLink(policies, AtomCMIS.LINK_CMIS_POLICIES, AtomCMIS.MEDIATYPE_ATOM_FEED, null, null, -1);

            // ACL link.
            String acl = getACLLink(objectId, request);
            entry.addLink(acl, AtomCMIS.LINK_CMIS_ACL, AtomCMIS.MEDIATYPE_ACL, null, null, -1);

            // Children link.
            String children = getChildrenLink(objectId, request);
            entry.addLink(children, AtomCMIS.LINK_DOWN, AtomCMIS.MEDIATYPE_ATOM_FEED, null, null, -1);

            // Descendants link. Provided only if repository support descendants feature.
            String descendants = getDescendantsLink(objectId, request);
            if (descendants != null) {
                entry.addLink(descendants, AtomCMIS.LINK_DOWN, AtomCMIS.MEDIATYPE_CMISTREE, null, null, -1);
            }

            // Folder tree. link. Provided only if repository support folder tree feature.
            String folderTree = getFolderTreeLink(objectId, request);
            if (folderTree != null) {
                entry.addLink(folderTree, AtomCMIS.LINK_CMIS_FOLDERTREE, AtomCMIS.MEDIATYPE_ATOM_FEED, null, null,
                        -1);
            }

            // Parent link.
            String parentId = object.getObjectInfo().getParentId();
            if (parentId != null) {
                // Not provided for root folder.
                String parent = getObjectLink(parentId, request);
                entry.addLink(parent, AtomCMIS.LINK_UP, AtomCMIS.MEDIATYPE_ATOM_ENTRY, null, null, -1);
            }

            // Must have 'content' element to conform Atom specification.
            String name = object.getObjectInfo().getName();
            entry.setContent(name);
        } else if (baseType == BaseType.DOCUMENT) {
            // Relationship link.
            String relationships = getRelationshipsLink(objectId, request);
            entry.addLink(relationships, AtomCMIS.LINK_CMIS_RELATIONSHIPS, AtomCMIS.MEDIATYPE_ATOM_FEED, null, null,
                    -1);

            // Policies link
            String policies = getPoliciesLink(objectId, request);
            entry.addLink(policies, AtomCMIS.LINK_CMIS_POLICIES, AtomCMIS.MEDIATYPE_ATOM_FEED, null, null, -1);

            // ACL link
            String acl = getACLLink(objectId, request);
            entry.addLink(acl, AtomCMIS.LINK_CMIS_ACL, AtomCMIS.MEDIATYPE_ACL, null, null, -1);

            // All versions
            String versionSeriesId = object.getObjectInfo().getVersionSeriesId();
            String allVersions = getAllVersionsLink(versionSeriesId, request);
            entry.addLink(allVersions, AtomCMIS.LINK_VERSION_HISTORY, AtomCMIS.MEDIATYPE_ATOM_FEED, null, null, -1);

            // Latest version link
            StringBuilder sb = new StringBuilder();
            sb.append(self).append('?').append(AtomCMIS.PARAM_RETURN_VERSION).append('=')
                    .append(EnumReturnVersion.LATEST.value());
            entry.addLink(sb.toString(), AtomCMIS.LINK_CURRENT_VERSION, AtomCMIS.MEDIATYPE_ATOM_ENTRY, null, null,
                    -1);

            // PWC link if it exists.
            String checkedoutProperty = object.getObjectInfo().getVersionSeriesCheckedOutId();
            if (checkedoutProperty != null) {
                String pwcLink = getObjectLink(checkedoutProperty, request);
                entry.addLink(pwcLink, AtomCMIS.LINK_WORKING_COPY, AtomCMIS.MEDIATYPE_ATOM_ENTRY, null, null, -1);
            }

            // Parents link.
            String parent = getParentsLink(objectId, request);
            entry.addLink(parent, AtomCMIS.LINK_UP, AtomCMIS.MEDIATYPE_ATOM_FEED, null, null, -1);

            // Edit-media link.
            String contentLink = getContentLink(objectId, request);
            entry.addLink(contentLink, AtomCMIS.LINK_EDIT_MEDIA);

            // Content element.
            String contentType = getContentType(object);
            if (contentType != null) {
                entry.setContent(new IRI(contentLink), contentType);
            } else {
                Factory factory = request.getAbdera().getFactory();
                Content content = factory.newContent();
                content.setContentType(null);
                content.setSrc(contentLink);
                entry.setContentElement(content);
            }
        } else if (baseType == BaseType.POLICY) {
            // Relationships link.
            String relationships = getRelationshipsLink(objectId, request);
            entry.addLink(relationships, AtomCMIS.LINK_CMIS_RELATIONSHIPS, AtomCMIS.MEDIATYPE_ATOM_FEED, null, null,
                    -1);

            // Policy link.
            String policies = getPoliciesLink(objectId, request);
            entry.addLink(policies, AtomCMIS.LINK_CMIS_POLICIES, AtomCMIS.MEDIATYPE_ATOM_FEED, null, null, -1);

            // ACL link.
            String acl = getACLLink(objectId, request);
            entry.addLink(acl, AtomCMIS.LINK_CMIS_ACL, AtomCMIS.MEDIATYPE_ACL, null, null, -1);

            // Parents link.
            String parent = getParentsLink(objectId, request);
            entry.addLink(parent, AtomCMIS.LINK_UP, AtomCMIS.MEDIATYPE_ATOM_FEED, null, null, -1);

            // Must have 'content' element to conform Atom specification.
            String name = object.getObjectInfo().getName();
            entry.setContent(name);
        } else if (baseType == BaseType.RELATIONSHIP) {
            // Relationship source link.
            String sourceId = object.getObjectInfo().getSourceId();
            entry.addLink(getObjectLink(sourceId, request), AtomCMIS.LINK_CMIS_SOURCE,
                    AtomCMIS.MEDIATYPE_ATOM_ENTRY, null, null, -1);

            // Relationship target link.
            String targetId = object.getObjectInfo().getTargetId();
            entry.addLink(getObjectLink(targetId, request), AtomCMIS.LINK_CMIS_TARGET,
                    AtomCMIS.MEDIATYPE_ATOM_ENTRY, null, null, -1);

            // Must have 'content' element to conform Atom specification.
            entry.setContent(getName(object));
        }

        ObjectTypeElement objectElement = new ObjectTypeElement(request.getAbdera().getFactory(), AtomCMIS.OBJECT);
        objectElement.build(object);
        entry.addExtension(objectElement);

        return self;
    }

    @SuppressWarnings("unchecked")
    protected org.xcmis.spi.utils.MimeType convertMimeType(MimeType abderaMimeType) {
        if (abderaMimeType == null) {
            return new org.xcmis.spi.utils.MimeType();
        }
        MimeTypeParameterList abderaParameters = abderaMimeType.getParameters();
        Map<String, String> paremeters = new HashMap<String, String>();
        for (Enumeration<String> names = abderaParameters.getNames(); names.hasMoreElements();) {
            String name = names.nextElement();
            paremeters.put(name, abderaParameters.get(name));
        }
        return new org.xcmis.spi.utils.MimeType(abderaMimeType.getPrimaryType(), abderaMimeType.getSubType(),
                paremeters);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    protected Feed createFeedBase(RequestContext request) throws ResponseContextException {
        Factory factory = request.getAbdera().getFactory();
        Feed feed = factory.newFeed();
        feed.setId(getId(request));
        feed.setTitle(getTitle(request));
        feed.addAuthor(getAuthor(request));
        // Updated is incorrect when pass Date.
        // Abdera uses Calendar.getInstance(TimeZone.getTimeZone("GMT"))
        // See org.apache.abdera.model.AtomDate .
        feed.setUpdated(AtomUtils.getAtomDate(Calendar.getInstance())); // TODO proper date

        String service = getServiceLink(request);
        feed.addLink(service, AtomCMIS.LINK_SERVICE, AtomCMIS.MEDIATYPE_ATOM_SERVICE, null, null, -1);

        String self = getSelfLink(getId(request), request);
        feed.addLink(self, AtomCMIS.LINK_SELF, AtomCMIS.MEDIATYPE_ATOM_FEED, null, null, -1);

        String via = getObjectLink(getId(request), request);
        feed.addLink(via, AtomCMIS.LINK_VIA, AtomCMIS.MEDIATYPE_ATOM_ENTRY, null, null, -1);

        return feed;
    }

    /**
     * Get link to AtomPub Document that describes object's ACL.
     *
     * @param id object id
     * @param request request context
     * @return link to allowable actions document
     */
    protected String getACLLink(String id, RequestContext request) {
        Map<String, String> params = new HashMap<String, String>();
        params.put("repoid", getRepositoryId(request));
        params.put("atomdoctype", "objacl");
        params.put("id", id);
        String actions = request.absoluteUrlFor(TargetType.ENTRY, params);
        return actions;
    }

    /**
     * Get link to AtomPub Document that describes object's allowable actions.
     *
     * @param id object id
     * @param request request context
     * @return link to allowable actions document
     */
    protected String getAllowableActionsLink(String id, RequestContext request) {
        Map<String, String> params = new HashMap<String, String>();
        params.put("repoid", getRepositoryId(request));
        params.put("atomdoctype", "allowableactions");
        params.put("id", id);
        String actions = request.absoluteUrlFor(TargetType.ENTRY, params);
        return actions;
    }

    /**
     * Get link to AtomPub Document that describes object's versions.
     *
     * @param id objects id
     * @param request request context
     * @return link to AtomPub Document that describes object's versions
     */
    protected String getAllVersionsLink(String id, RequestContext request) {
        Map<String, String> params = new HashMap<String, String>();
        params.put("repoid", getRepositoryId(request));
        params.put("atomdoctype", "versions");
        params.put("id", id);
        String parents = request.absoluteUrlFor(TargetType.ENTRY, params);
        return parents;
    }

    /**
     * Get object's base type.
     *
     * @param object object
     * @return object's base type
     */
    protected BaseType getBaseObjectType(CmisObject object) {
        return object.getObjectInfo().getBaseType();
    }

    /**
     * Get link to express renditions.
     *
     * @param id objects id
     * @param request request context
     * @return link to AtomPub Document that describes object's parent(s)
     */
    protected String getBaseRenditionHref(String id, RequestContext request) {
        Map<String, String> params = new HashMap<String, String>();
        params.put("repoid", getRepositoryId(request));
        params.put("atomdoctype", "alternate");
        params.put("id", id);
        String link = request.absoluteUrlFor(TargetType.ENTRY, params);
        return link;
    }

    /**
     * Get link to AtomPub document that describes folder's children.
     *
     * @param id folder id
     * @param request the request context
     * @return link to AtomPub document that describes folder's children
     */
    protected String getChildrenLink(String id, RequestContext request) {
        Map<String, String> params = new HashMap<String, String>();
        params.put("repoid", getRepositoryId(request));
        params.put("atomdoctype", "children");
        params.put("id", id);
        String children = request.absoluteUrlFor(TargetType.ENTRY, params);
        return children;
    }

    /**
     * Get self link which provides the URI to retrieve this resource again.
     *
     * The atom:link with relation self MUST be generated to return the URI of
     * the feed. If paging or any other mechanism is used to filter, sort, or
     * change the representation of the feed, the URI MUST point back a resource
     * with the same representation.
     *
     * @param id the object id
     * @param request the request context
     * @return link which provides the URI to retrieve this resource again.
     */
    protected String getSelfLink(String id, RequestContext request) {
        Map<String, String> params = new HashMap<String, String>();
        params.put("repoid", getRepositoryId(request));
        params.put("atomdoctype", getHref().substring(1));
        params.put("id", id);
        String children = request.absoluteUrlFor(TargetType.ENTRY, params) + "?" + request.getUri().getQuery();
        return children;
    }

    /**
     * Get link to document content.
     *
     * @param id document id
     * @param request request context
     * @return link to document content
     */
    protected String getContentLink(String id, RequestContext request) {
        Map<String, String> params = new HashMap<String, String>();
        params.put("repoid", getRepositoryId(request));
        params.put("atomdoctype", "file");
        params.put("id", id);
        String content = request.absoluteUrlFor(TargetType.ENTRY, params);
        return content;
    }

    /**
     * Get content stream from entry.
     *
     * @param entry source entry
     * @param request request context
     * @return content stream as <code>ContentStream</code> or null if there is
     *         no 'content' in entry
     * @throws IOException if any i/o error occurs
     * @throws ResponseContextException other errors
     */
    protected ContentStream getContentStream(Entry entry, RequestContext request)
            throws IOException, ResponseContextException {
        ContentStream contentStream = null;
        ContentTypeElement cmisContent = entry.getExtension(AtomCMIS.CONTENT);
        if (cmisContent != null) {
            CmisContentType content = cmisContent.getContent();

            InputStream is = content.getBase64();
            contentStream = new BaseContentStream(is, is.available(), null,
                    org.xcmis.spi.utils.MimeType.fromString(content.getMediatype()));
        } else {
            Content content = entry.getContentElement();
            if (content != null) {
                final IRI src = content.getSrc();
                if (src != null) {
                    if (src.equals(new IRI(getContentLink(getId(request), request)))) {
                        // If 'src' attribute provides URI is the same to current
                        // object (document). This may happen when client does 'check-in'
                        // or 'check-out' operation.
                    } else {
                        URL url = null;
                        try {
                            url = src.toURL();
                        } catch (URISyntaxException e) {
                            String msg = "Invalid src attribute: " + src;
                            throw new ResponseContextException(createErrorResponse(msg, 400));
                        }
                        // HTTP only
                        HttpURLConnection httpConnection = (HttpURLConnection) url.openConnection();
                        httpConnection.setRequestMethod("GET");
                        httpConnection.setDoOutput(false);
                        httpConnection.setDoInput(true);
                        int status = httpConnection.getResponseCode();
                        if (200 == status) {
                            contentStream = new BaseContentStream(new HttpConnectionStream(httpConnection), null,
                                    org.xcmis.spi.utils.MimeType
                                            .fromString(httpConnection.getHeaderField("Content-Type")));
                        } else {
                            httpConnection.disconnect();
                            String msg = "Unable get content from URI : " + src.toString() + ". Response status is "
                                    + status;
                            throw new ResponseContextException(createErrorResponse(msg, 500));
                        }
                    }
                } else {
                    Type contentType = content.getContentType();
                    org.xcmis.spi.utils.MimeType mediaType;
                    if (contentType == Type.XML || contentType == Type.XHTML || contentType == Type.HTML
                            || contentType == Type.TEXT) {
                        switch (contentType) {
                        case XHTML:
                            mediaType = new org.xcmis.spi.utils.MimeType("application", "xhtml+xml");
                            break;
                        case HTML:
                            mediaType = new org.xcmis.spi.utils.MimeType("text", "html");
                            break;
                        case TEXT:
                            mediaType = new org.xcmis.spi.utils.MimeType("text", "plain");
                            break;
                        case XML:
                            mediaType = convertMimeType(content.getMimeType());
                            break;
                        default:
                            // Must never happen.
                            mediaType = new org.xcmis.spi.utils.MimeType();
                        }

                        byte[] data;
                        // XXX CMISSpaces sends XML content as Base64 encoded but
                        // Abdera waits for plain text.
                        // Done just for research work. Find good solution to fix this.
                        if (SPACES_AIR_SPECIFIC_REFERER.equalsIgnoreCase(request.getHeader("referer"))) {
                            data = Base64.decodeBase64(content.getText().getBytes());
                        } else {
                            String charset = mediaType.getParameter(CmisConstants.CHARSET);
                            if (charset == null) {
                                // workaround
                                mediaType.getParameters().put(CmisConstants.CHARSET, "UTF-8");
                            }
                            data = content.getValue().getBytes(charset == null ? "UTF-8" : charset);

                        }

                        contentStream = new BaseContentStream(data, null, mediaType);
                    } else {
                        contentStream = new BaseContentStream(content.getDataHandler().getInputStream(), null,
                                convertMimeType(content.getMimeType()));
                    }
                }
            }
        }
        return contentStream;
    }

    /**
     * Get object creation date.
     *
     * @param object source object
     * @return creation date
     */
    protected Calendar getCreationDate(CmisObject object) {
        Calendar creationDate = object.getObjectInfo().getCreationDate();
        if (creationDate == null) {
            creationDate = Calendar.getInstance();
        }
        return creationDate;
    }

    /**
     * Get link to AtomPub document that describes folder's descendants. If
     * repository does not support capability 'getDescendants' this method will
     * return null.
     *
     * @param id folder id
     * @param request request context
     * @return link to AtomPub document that describes folder's descendants or
     *         null if capability 'getDescendants' is not supported.
     * @see RepositoryInfo
     */
    protected String getDescendantsLink(String id, /*RepositoryInfo repoInfo,*/RequestContext request) {
        String children = null;
        Connection connection = getConnection(request);
        RepositoryCapabilities capabilities = connection.getStorage().getRepositoryInfo().getCapabilities();
        if (capabilities.isCapabilityGetDescendants()) {
            Map<String, String> params = new HashMap<String, String>();
            params.put("repoid", getRepositoryId(request));
            params.put("atomdoctype", "descendants");
            params.put("id", id);
            children = request.absoluteUrlFor("feed", params);
        }
        return children;
    }

    /**
     * Get link to AtomPub document that describes folder's tree. If repository
     * does not support capability 'getFolderTree' this method will return null.
     *
     * @param id folder id
     * @param request request context
     * @return link to AtomPub document that describes folder's tree or null if
     *         capability 'getFolderTree' is not supported.
     */
    protected String getFolderTreeLink(String id, RequestContext request) {
        String children = null;
        Connection conn = getConnection(request);
        RepositoryCapabilities capabilities = conn.getStorage().getRepositoryInfo().getCapabilities();
        if (capabilities.isCapabilityGetFolderTree()) {
            Map<String, String> params = new HashMap<String, String>();
            params.put("repoid", getRepositoryId(request));
            params.put("atomdoctype", "foldertree");
            params.put("id", id);
            children = request.absoluteUrlFor("feed", params);
        }
        return children;
    }

    /**
     * Get object creation date.
     *
     * @param object source object
     * @return creation date
     */
    protected Calendar getLastModificationDate(CmisObject object) {
        Calendar lastModification = object.getObjectInfo().getLastModificationDate();
        if (lastModification == null) {
            lastModification = Calendar.getInstance();
        }
        return lastModification;
    }

    /**
     * Get link to AtomPub Document that describes object with <code>id</code>.
     *
     * @param id object id
     * @param request request context
     * @return link to AtomPub Document that describes object with
     *         <code>id</code>
     */
    protected String getObjectLink(String id, RequestContext request) {
        Map<String, String> params = new HashMap<String, String>();
        params.put("repoid", getRepositoryId(request));
        params.put("atomdoctype", "object");
        params.put("id", id);
        String link = request.absoluteUrlFor(TargetType.ENTRY, params);
        return link;
    }

    /**
     * Get link to AtomPub Document that describes object's parent(s).
     *
     * @param id objects id
     * @param request request context
     * @return link to AtomPub Document that describes object's parent(s)
     */
    protected String getParentsLink(String id, RequestContext request) {
        Map<String, String> params = new HashMap<String, String>();
        params.put("repoid", getRepositoryId(request));
        params.put("atomdoctype", "parents");
        params.put("id", id);
        String parents = request.absoluteUrlFor("feed", params);
        return parents;
    }

    /**
     * Get link to AtomPub Document that describes object's policies.
     *
     * @param id objects id
     * @param request request context
     * @return link to AtomPub Document that describes policies applied to object
     */
    protected String getPoliciesLink(String id, RequestContext request) {
        Map<String, String> params = new HashMap<String, String>();
        params.put("repoid", getRepositoryId(request));
        params.put("atomdoctype", "policies");
        params.put("id", id);
        String parents = request.absoluteUrlFor("feed", params);
        return parents;
    }

    /**
     * Get object's property.
     *
     * @param object object
     * @param propertyName property name
     * @return property or null if property does not exist
     */
    protected Property<?> getProperty(CmisObject object, String propertyName) {
        Map<String, Property<?>> properties = object.getProperties();
        Property<?> property = null;
        if (properties != null && !properties.isEmpty()) {
            //         for (Property<?> prop : properties.values())
            //         {
            //            if (propertyName.equals(prop.getId()))
            //            {
            //               return prop;
            //            }
            //         }
            property = properties.get(propertyName);
        }
        return property;
    }

    /**
     * Get link to AtomPub Document that describes object's relationships.
     *
     * @param id objects id
     * @param request request context
     * @return link to AtomPub Document that describes object's relationships
     */
    protected String getRelationshipsLink(String id, RequestContext request) {
        Map<String, String> params = new HashMap<String, String>();
        params.put("repoid", getRepositoryId(request));
        params.put("atomdoctype", "relationships");
        params.put("id", id);
        String parents = request.absoluteUrlFor("feed", params);
        return parents;
    }

    /**
     * Get's the name of the specific resource requested.
     *
     * @param request RequestContext
     * @return string resource name
     */
    @Override
    protected String getResourceName(RequestContext request) {
        String path = request.getTarget().getParameter("path");
        if (path != null) {
            try {
                path = URLDecoder.decode(path, "UTF-8");
            } catch (UnsupportedEncodingException ignored) {
            }
            return path.charAt(0) == '/' ? path : ('/' + path);
        }
        return super.getResourceName(request);
    }

    /**
     * From specification (1.0-cd06), section 3.5.2 Entries. When POSTing an Atom
     * Document, the Atom elements MUST take precedence over the corresponding
     * writable CMIS property. For example, atom:title will overwrite cmis:name.
     *
     * @param object CMIS object
     * @param entry entry that delivered CMIS object.
     */
    protected void updatePropertiesFromEntry(CmisObject object, Entry entry) {
        // SPEC.: 3.5.2 Entries
        // atom:title MUST be the cmis:name property
        String title = entry.getTitle();
        if (title != null && title.length() > 0) {
            // Should never be null, but check it to avoid overwriting existed cmis:name property.
            StringProperty prop = (StringProperty) getProperty(object, CmisConstants.NAME);
            if (prop == null) {
                prop = new StringProperty();
                prop.setId(CmisConstants.NAME);
                prop.setLocalName(CmisConstants.NAME);
                prop.getValues().add(title);
                object.getProperties().put(prop.getId(), prop);
            } else {
                prop.getValues().clear();
                prop.getValues().add(title);
            }
        }
        // atom:author/atom:name MUST be cmis:createdBy
        String author = entry.getAuthor() != null ? entry.getAuthor().getName() : null;
        if (author != null && author.length() > 0) {
            StringProperty prop = (StringProperty) getProperty(object, CmisConstants.CREATED_BY);
            if (prop == null) {
                prop = new StringProperty();
                prop.setId(CmisConstants.CREATED_BY);
                prop.setLocalName(CmisConstants.CREATED_BY);
                prop.getValues().add(author);
                object.getProperties().put(prop.getId(), prop);
            } else {
                prop.getValues().clear();
                prop.getValues().add(author);
            }
        }
    }

}