/** * 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 (Exception 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 (Exception 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 (Exception 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 (Exception 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 (Exception 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 (Exception 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 (Exception 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 (Exception 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); } } } }