/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * Kind, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ using PortCMIS.Binding; using PortCMIS.Binding.Http; using PortCMIS.Binding.Impl; using PortCMIS.Binding.Services; using PortCMIS.Client; using PortCMIS.Client.Impl; using PortCMIS.Data; using PortCMIS.Data.Extensions; using PortCMIS.Enums; using PortCMIS.Exceptions; using System; using System.Collections.Generic; using System.IO; using System.Linq; using System.Net.Http; using System.Numerics; using System.Xml; namespace PortCMIS.Binding.AtomPub { /// /// Browser binding SPI. /// internal class CmisAtomPubSpi : ICmisSpi { public const string SessionLinkCache = "org.apache.chemistry.portcmis.binding.atompub.linkcache"; private BindingSession session; private RepositoryService repositoryService; private NavigationService navigationService; private ObjectService objectService; private VersioningService versioningService; private DiscoveryService discoveryService; private MultiFilingService multiFilingService; private RelationshipService relationshipService; private PolicyService policyService; private AclService aclService; public void Initialize(IBindingSession session) { this.session = session as BindingSession; if (this.session == null) { throw new ArgumentException("Invalid binding session!"); } repositoryService = new RepositoryService(this.session); navigationService = new NavigationService(this.session); objectService = new ObjectService(this.session); versioningService = new VersioningService(this.session); discoveryService = new DiscoveryService(this.session); multiFilingService = new MultiFilingService(this.session); relationshipService = new RelationshipService(this.session); policyService = new PolicyService(this.session); aclService = new AclService(this.session); } public IRepositoryService GetRepositoryService() { return repositoryService; } public INavigationService GetNavigationService() { return navigationService; } public IObjectService GetObjectService() { return objectService; } public IVersioningService GetVersioningService() { return versioningService; } public IRelationshipService GetRelationshipService() { return relationshipService; } public IDiscoveryService GetDiscoveryService() { return discoveryService; } public IMultiFilingService GetMultiFilingService() { return multiFilingService; } public IAclService GetAclService() { return aclService; } public IPolicyService GetPolicyService() { return policyService; } public void ClearAllCaches() { session.RemoveValue(SessionLinkCache); } public void ClearRepositoryCache(string repositoryId) { LinkCache linkCache = session.GetValue(SessionLinkCache) as LinkCache; if (linkCache != null) { linkCache.ClearRepository(repositoryId); } } public void Dispose() { // nothing to do } } /// /// Common service data and operations. /// internal abstract class AbstractAtomPubService { protected enum IdentifierType { ID, PATH } protected const string NameCollection = "collection"; protected const string NameUriTemplate = "uritemplate"; protected const string NamePathSegment = "pathSegment"; protected const string NameRelativePathSegment = "relativePathSegment"; protected const string NameNumItems = "numItems"; private BindingSession session; public BindingSession Session { get { return session; } protected set { session = value; } } /// /// Returns the service document URL of this session. /// protected string GetServiceDocURL() { return Session.GetValue(SessionParameter.AtomPubUrl) as string; } /// /// Return the CMIS version of the given repository. /// protected CmisVersion GetCmisVersion(string repositoryId) { CmisVersion? forcedVersion = Session.GetValue(SessionParameter.ForceCmisVersion) as CmisVersion?; if (forcedVersion != null) { return (CmisVersion)forcedVersion; } RepositoryInfoCache cache = Session.GetRepositoryInfoCache(); IRepositoryInfo info = cache.Get(repositoryId); if (info == null) { IList infoList = GetRepositoriesInternal(repositoryId); if (infoList != null && infoList.Count > 0) { info = infoList[0]; cache.Put(info); } } return info == null ? CmisVersion.Cmis_1_0 : info.CmisVersion; } // ---- link cache ---- /// /// Returns the link cache or creates a new cache if it doesn't exist. /// protected LinkCache GetLinkCache() { LinkCache linkCache = (LinkCache)Session.GetValue(CmisAtomPubSpi.SessionLinkCache); if (linkCache == null) { linkCache = new LinkCache(Session); Session.PutValue(CmisAtomPubSpi.SessionLinkCache, linkCache); } return linkCache; } /// /// Gets a link from the cache. /// protected string GetLink(string repositoryId, string id, string rel, string type) { if (repositoryId == null) { throw new CmisInvalidArgumentException("Repository ID must be set!"); } if (id == null) { throw new CmisInvalidArgumentException("Object ID must be set!"); } return GetLinkCache().GetLink(repositoryId, id, rel, type); } /// /// Gets a link from the cache. /// protected string GetLink(string repositoryId, string id, string rel) { return GetLink(repositoryId, id, rel, null); } /// /// Gets a link from the cache if it is there or Loads it into the cache if /// it is not there. /// public string LoadLink(string repositoryId, string id, string rel, string type) { string link = GetLink(repositoryId, id, rel, type); if (link == null) { GetObjectInternal(repositoryId, IdentifierType.ID, id, ReturnVersion.This, "cmis:objectId", false, IncludeRelationships.None, "cmis:none", false, false, null); link = GetLink(repositoryId, id, rel, type); } return link; } /// /// Gets the content link from the cache if it is there or Loads it into the /// cache if it is not there. /// public string LoadContentLink(string repositoryId, string id) { return LoadLink(repositoryId, id, AtomPubParser.LinkRelContent, null); } /// /// Gets a rendition content link from the cache if it is there or Loads it /// into the cache if it is not there. /// public string LoadRenditionContentLink(string repositoryId, string id, string streamId) { return LoadLink(repositoryId, id, BindingConstants.RelAlternate, streamId); } /// /// Adds a link to the cache. /// protected void AddLink(string repositoryId, string id, string rel, string type, string link) { GetLinkCache().AddLink(repositoryId, id, rel, type, link); } /// /// Adds a link to the cache. /// protected void AddLink(string repositoryId, string id, AtomLink link) { GetLinkCache().AddLink(repositoryId, id, link.Rel, link.Type, link.Href); } /// /// Removes all links of an object. /// protected void RemoveLinks(string repositoryId, string id) { GetLinkCache().RemoveLinks(repositoryId, id); } /// /// Locks the link cache. /// protected void LockLinks() { GetLinkCache().LockLinks(); } /// /// Unlocks the link cache. /// protected void UnlockLinks() { GetLinkCache().UnlockLinks(); } /// /// Checks a link throw an appropriate exception. /// protected void ThrowLinkException(string repositoryId, string id, string rel, string type) { int index = GetLinkCache().CheckLink(repositoryId, id, rel, type); switch (index) { case 0: throw new CmisObjectNotFoundException("Unknown repository!"); case 1: throw new CmisObjectNotFoundException("Unknown object!"); case 2: throw new CmisNotSupportedException("Operation not supported by the repository for this object!"); case 3: throw new CmisNotSupportedException("No link with matching media type!"); case 4: throw new CmisRuntimeException("Nothing wrong! Either this is a bug or a threading issue."); default: throw new CmisRuntimeException("Unknown error!"); } } /// /// Gets a type link from the cache. /// protected string GetTypeLink(string repositoryId, string typeId, string rel, string type) { if (repositoryId == null) { throw new CmisInvalidArgumentException("Repository ID must be set!"); } if (typeId == null) { throw new CmisInvalidArgumentException("Type ID must be set!"); } return GetLinkCache().GetTypeLink(repositoryId, typeId, rel, type); } /// /// Gets a type link from the cache. /// protected string GetTypeLink(string repositoryId, string typeId, string rel) { return GetTypeLink(repositoryId, typeId, rel, null); } /// /// Gets a link from the cache if it is there or Loads it into the cache if /// it is not there. /// protected string LoadTypeLink(string repositoryId, string typeId, string rel, string type) { string link = GetTypeLink(repositoryId, typeId, rel, type); if (link == null) { GetTypeDefinitionInternal(repositoryId, typeId); link = GetTypeLink(repositoryId, typeId, rel, type); } return link; } /// /// Adds a type link to the cache. /// protected void AddTypeLink(string repositoryId, string typeId, string rel, string type, string link) { GetLinkCache().AddTypeLink(repositoryId, typeId, rel, type, link); } /// /// Adds a type link to the cache. /// protected void AddTypeLink(string repositoryId, string typeId, AtomLink link) { GetLinkCache().AddTypeLink(repositoryId, typeId, link.Rel, link.Type, link.Href); } /// /// Removes all links of a type. /// protected void RemoveTypeLinks(string repositoryId, string id) { GetLinkCache().RemoveTypeLinks(repositoryId, id); } /// /// Locks the type link cache. /// protected void LockTypeLinks() { GetLinkCache().LockTypeLinks(); } /// /// Unlocks the type link cache. /// protected void UnlockTypeLinks() { GetLinkCache().UnlockTypeLinks(); } /// /// Gets a collection from the cache. /// protected string GetCollection(string repositoryId, string collection) { return GetLinkCache().GetCollection(repositoryId, collection); } /// /// Gets a collection from the cache if it is there or Loads it into the /// cache if it is not there. /// protected string LoadCollection(string repositoryId, string collection) { string link = GetCollection(repositoryId, collection); if (link == null) { // cache repository info GetRepositoriesInternal(repositoryId); link = GetCollection(repositoryId, collection); } return link; } /// /// Adds a collection to the cache. /// protected void AddCollection(string repositoryId, string collection, string link) { GetLinkCache().AddCollection(repositoryId, collection, link); } /// /// Gets a repository link from the cache. /// protected string GetRepositoryLink(string repositoryId, string rel) { return GetLinkCache().GetRepositoryLink(repositoryId, rel); } /// /// Gets a repository link from the cache if it is there or Loads it into the /// cache if it is not there. /// protected string LoadRepositoryLink(string repositoryId, string rel) { string link = GetRepositoryLink(repositoryId, rel); if (link == null) { // cache repository info GetRepositoriesInternal(repositoryId); link = GetRepositoryLink(repositoryId, rel); } return link; } /// /// Adds a repository link to the cache. /// protected void AddRepositoryLink(string repositoryId, string rel, string link) { GetLinkCache().AddRepositoryLink(repositoryId, rel, link); } /// /// Adds a repository link to the cache. /// protected void AddRepositoryLink(string repositoryId, AtomLink link) { AddRepositoryLink(repositoryId, link.Rel, link.Href); } /// /// Gets an URI template from the cache. /// protected string GetTemplateLink(string repositoryId, string type, Dictionary parameters) { return GetLinkCache().GetTemplateLink(repositoryId, type, parameters); } /// /// Gets a template link from the cache if it is there or Loads it into the /// cache if it is not there. /// protected string LoadTemplateLink(string repositoryId, string type, Dictionary parameters) { string link = GetTemplateLink(repositoryId, type, parameters); if (link == null) { // cache repository info GetRepositoriesInternal(repositoryId); link = GetTemplateLink(repositoryId, type, parameters); } return link; } /// /// Adds an URI template to the cache. /// protected void AddTemplate(string repositoryId, string type, string link) { GetLinkCache().AddTemplate(repositoryId, type, link); } // ---- exceptions ---- /// /// Converts a HTTP status code into an Exception. /// protected CmisBaseException ConvertStatusCode(int code, string message, string errorContent, Exception ex) { string exception = ExtractException(errorContent); message = ExtractErrorMessage(message, errorContent); switch (code) { case 301: case 302: case 303: case 307: return new CmisConnectionException("Redirects are not supported (HTTP status code " + code + "): " + message, errorContent, ex); case 400: if (CmisFilterNotValidException.ExceptionName.Equals(exception, StringComparison.OrdinalIgnoreCase)) { return new CmisFilterNotValidException(message, errorContent, ex); } return new CmisInvalidArgumentException(message, errorContent, ex); case 401: return new CmisUnauthorizedException(message, errorContent, ex); case 403: if (CmisStreamNotSupportedException.ExceptionName.Equals(exception, StringComparison.OrdinalIgnoreCase)) { return new CmisStreamNotSupportedException(message, errorContent, ex); } return new CmisPermissionDeniedException(message, errorContent, ex); case 404: return new CmisObjectNotFoundException(message, errorContent, ex); case 405: return new CmisNotSupportedException(message, errorContent, ex); case 407: return new CmisProxyAuthenticationException(message, errorContent, ex); case 409: if (CmisContentAlreadyExistsException.ExceptionName.Equals(exception, StringComparison.OrdinalIgnoreCase)) { return new CmisContentAlreadyExistsException(message, errorContent, ex); } else if (CmisVersioningException.ExceptionName.Equals(exception, StringComparison.OrdinalIgnoreCase)) { return new CmisVersioningException(message, errorContent, ex); } else if (CmisUpdateConflictException.ExceptionName.Equals(exception, StringComparison.OrdinalIgnoreCase)) { return new CmisUpdateConflictException(message, errorContent, ex); } else if (CmisNameConstraintViolationException.ExceptionName.Equals(exception, StringComparison.OrdinalIgnoreCase)) { return new CmisNameConstraintViolationException(message, errorContent, ex); } return new CmisConstraintException(message, errorContent, ex); case 503: return new CmisServiceUnavailableException(message, errorContent, ex); default: if (CmisStorageException.ExceptionName.Equals(exception, StringComparison.OrdinalIgnoreCase)) { return new CmisStorageException(message, errorContent, ex); } return new CmisRuntimeException(message, errorContent, ex); } } protected string ExtractException(string errorContent) { if (errorContent == null) { return null; } int begin = errorContent.IndexOf(""); int end = errorContent.IndexOf(""); if (begin == -1 || end == -1 || begin > end) { return null; } int textStart = begin + "".Length; return errorContent.Substring(textStart, end - textStart); } protected string ExtractErrorMessage(string message, string errorContent) { if (errorContent == null) { return message; } int begin = errorContent.IndexOf(""); int end = errorContent.IndexOf(""); if (begin == -1 || end == -1 || begin > end) { return message; } int textStart = begin + "".Length; return errorContent.Substring(textStart, end - textStart); } // ---- helpers ---- protected bool Matches(string name, AtomElement element) { return name == element.LocalName; } protected bool IsStr(string name, AtomElement element) { return Matches(name, element) && (element.Object is string); } protected bool IsInt(string name, AtomElement element) { return Matches(name, element) && (element.Object is BigInteger); } protected bool IsNextLink(AtomElement element) { return BindingConstants.RelNext == ((AtomLink)element.Object).Rel; } /// /// Creates a CMIS object with properties and policy IDs. /// protected ObjectData CreateObject(IProperties properties, string changeToken, IList policies) { ObjectData obj = new ObjectData(); bool omitChangeToken = Session.GetValue(SessionParameter.OmitChangeTokens, false); if (properties == null) { properties = new Properties(); if (changeToken != null && !omitChangeToken) { PropertyData changeTokenProp = new PropertyData(PropertyType.String); changeTokenProp.Id = PropertyIds.ChangeToken; changeTokenProp.AddValue(changeToken); ((Properties)properties).AddProperty(changeTokenProp); } } else { if (omitChangeToken) { if (properties[PropertyIds.ChangeToken] != null) { properties = new Properties(properties); ((Properties)properties).RemoveProperty(PropertyIds.ChangeToken); } } else { if (changeToken != null && properties[PropertyIds.ChangeToken] == null) { PropertyData changeTokenProp = new PropertyData(PropertyType.String); changeTokenProp.Id = PropertyIds.ChangeToken; changeTokenProp.AddValue(changeToken); properties = new Properties(properties); ((Properties)properties).AddProperty(changeTokenProp); } } } obj.Properties = properties; if (policies != null && policies.Count > 0) { PolicyIdList policyIdList = new PolicyIdList(); policyIdList.PolicyIds = policies; obj.PolicyIds = policyIdList; } return obj; } /// /// Creates a CMIS object that only contains an ID in the property list. /// protected IObjectData CreateIdObject(string objectId) { ObjectData obj = new ObjectData(); Properties properties = new Properties(); obj.Properties = properties; PropertyData idProp = new PropertyData(PropertyType.Id); idProp.Id = PropertyIds.ObjectId; idProp.AddValue(objectId); properties.AddProperty(idProp); return obj; } /// /// Parses an input stream. /// protected T Parse(Stream stream) where T : AtomBase { AtomPubParser parser = new AtomPubParser(stream); try { parser.Parse(); } catch (Exception e) { throw new CmisConnectionException("Parsing exception!", e); } AtomBase parseResult = parser.GetResults(); T result = parseResult as T; if (result == null) { throw new CmisConnectionException("Unexpected document! Received " + (parseResult == null ? "something unknown" : parseResult.Type) + "!"); } return (T)parseResult; } /// /// Performs a GET on an URL, checks the response code and returns the /// result. /// protected IResponse Read(UrlBuilder url) { // make the call IResponse resp = Session.GetHttpInvoker().InvokeGET(url, session); // check response code if (resp.StatusCode != 200) { throw ConvertStatusCode(resp.StatusCode, resp.Message, resp.ErrorContent, null); } return resp; } /// /// Performs a POST on an URL, checks the response code and returns the /// result. /// protected IResponse Post(UrlBuilder url, HttpContent content) { // make the call IResponse resp = Session.GetHttpInvoker().InvokePOST(url, content, session); // check response code if (resp.StatusCode != 201) { throw ConvertStatusCode(resp.StatusCode, resp.Message, resp.ErrorContent, null); } return resp; } /// /// Performs a POST on an URL, checks the response code and consumes the /// response. /// protected void PostAndConsume(UrlBuilder url, HttpContent content) { IResponse resp = Post(url, content); IOUtils.ConsumeAndClose(resp.Stream); } /// /// Performs a PUT on an URL, checks the response code and returns the /// result. /// protected IResponse Put(UrlBuilder url, HttpContent content) { return Put(url, null, content); } /// /// Performs a PUT on an URL, checks the response code and returns the /// result. /// protected IResponse Put(UrlBuilder url, IDictionary headers, HttpContent content) { // make the call IResponse resp = Session.GetHttpInvoker().InvokePUT(url, headers, content, session); // check response code if (resp.StatusCode < 200 || resp.StatusCode > 299) { throw ConvertStatusCode(resp.StatusCode, resp.Message, resp.ErrorContent, null); } return resp; } /// /// Performs a DELETE on an URL, checks the response code and returns the /// result. /// protected void Delete(UrlBuilder url) { // make the call IResponse resp = Session.GetHttpInvoker().InvokeDELETE(url, session); // check response code if (resp.StatusCode != 204) { throw ConvertStatusCode(resp.StatusCode, resp.Message, resp.ErrorContent, null); } } // ---- common operations ---- /// /// Checks if at least one ACE list is not empty. /// protected bool IsAclMergeRequired(IAcl addAces, IAcl removeAces) { return (addAces != null && addAces.Aces != null && addAces.Aces.Count > 0) || (removeAces != null && removeAces.Aces != null && removeAces.Aces.Count > 0); } /// /// Merges the new ACL from original, add and remove ACEs lists. /// protected IAcl MergeAcls(IAcl originalAces, IAcl addAces, IAcl removeAces) { Dictionary> originals = ConvertAclToMap(originalAces); Dictionary> adds = ConvertAclToMap(addAces); Dictionary> removes = ConvertAclToMap(removeAces); IList newAces = new List(); // iterate through the original ACEs foreach (KeyValuePair> ace in originals) { // add permissions HashSet addPermissions; if (adds.TryGetValue(ace.Key, out addPermissions)) { foreach (string perm in addPermissions) { ace.Value.Add(perm); } } // remove permissions HashSet removePermissions; if (removes.TryGetValue(ace.Key, out removePermissions)) { foreach (string perm in removePermissions) { ace.Value.Remove(perm); } } // create new ACE if (ace.Value.Count > 0) { Ace newAce = new Ace(); newAce.Principal = new Principal() { Id = ace.Key }; newAce.Permissions = ace.Value.ToList(); newAces.Add(newAce); } } // find all ACEs that should be added but are not in the original ACE list foreach (KeyValuePair> ace in adds) { if (!originals.ContainsKey(ace.Key) && ace.Value.Count > 0) { Ace newAce = new Ace(); newAce.Principal = new Principal() { Id = ace.Key }; newAce.Permissions = ace.Value.ToList(); newAces.Add(newAce); } } return new Acl() { Aces = newAces }; } /// /// Converts a list of ACEs into Map for better handling. /// private static Dictionary> ConvertAclToMap(IAcl acl) { Dictionary> result = new Dictionary>(); if (acl == null || acl.Aces == null) { return result; } foreach (IAce ace in acl.Aces) { // don't consider indirect ACEs - we can't change them if (!ace.IsDirect) { // ignore continue; } // although a principal must not be null, check it if (ace.Principal == null || ace.Principal.Id == null) { // ignore continue; } HashSet permissions; if (!result.TryGetValue(ace.Principal.Id, out permissions)) { permissions = new HashSet(); result[ace.Principal.Id] = permissions; } if (ace.Permissions != null) { foreach (string perm in ace.Permissions) { permissions.Add(perm); } } } return result; } /// /// Retrieves the Service Document from the server and caches the repository /// info objects, collections, links, URI templates, etc. /// protected IList GetRepositoriesInternal(string repositoryId) { List repInfos = new List(); // retrieve service doc UrlBuilder url = new UrlBuilder(GetServiceDocURL()); url.AddParameter(BindingConstants.ParamRepositoryId, repositoryId); // read and parse IResponse resp = Read(url); ServiceDoc serviceDoc = Parse(resp.Stream); // walk through the workspaces foreach (RepositoryWorkspace ws in serviceDoc.Workspaces) { if (ws.Id == null) { // found a non-CMIS workspace continue; } foreach (AtomElement element in ws.Elements) { if (Matches(NameCollection, element)) { Dictionary colMap = (Dictionary)element.Object; string collectionType; colMap.TryGetValue("collectionType", out collectionType); string href; colMap.TryGetValue("href", out href); AddCollection(ws.Id, collectionType, href); } else if (element.Object is AtomLink) { AddRepositoryLink(ws.Id, (AtomLink)element.Object); } else if (Matches(NameUriTemplate, element)) { Dictionary tempMap = (Dictionary)element.Object; string type; tempMap.TryGetValue("type", out type); string template; tempMap.TryGetValue("template", out template); AddTemplate(ws.Id, type, template); } else if (element.Object is RepositoryInfo) { repInfos.Add((RepositoryInfo)element.Object); } } } return repInfos; } /// /// Retrieves an object from the server and caches the links. /// protected IObjectData GetObjectInternal(string repositoryId, IdentifierType idOrPath, string objectIdOrPath, ReturnVersion? returnVersion, string filter, bool? includeAllowableActions, IncludeRelationships? includeRelationships, string renditionFilter, bool? includePolicyIds, bool? includeAcl, IExtensionsData extension) { Dictionary parameters = new Dictionary(); parameters[BindingConstants.ParamId] = objectIdOrPath; parameters[BindingConstants.ParamPath] = objectIdOrPath; parameters[BindingConstants.ParamReturnVersion] = returnVersion; parameters[BindingConstants.ParamFilter] = filter; parameters[BindingConstants.ParamAllowableActions] = includeAllowableActions; parameters[BindingConstants.ParamAcl] = includeAcl; parameters[BindingConstants.ParamPolicyIds] = includePolicyIds; parameters[BindingConstants.ParamRelationships] = includeRelationships; parameters[BindingConstants.ParamRenditionfilter] = renditionFilter; string link = LoadTemplateLink(repositoryId, (idOrPath == IdentifierType.ID ? BindingConstants.TemplateObjectById : BindingConstants.TemplateObjectByPath), parameters); if (link == null) { throw new CmisObjectNotFoundException("Unknown repository!"); } UrlBuilder url = new UrlBuilder(link); // workaround for missing template parameter in the CMIS spec if (returnVersion != null && returnVersion != ReturnVersion.This) { url.AddParameter(BindingConstants.ParamReturnVersion, returnVersion); } // read and parse IResponse resp = Read(url); AtomEntry entry = Parse(resp.Stream); // we expect a CMIS entry if (entry.Id == null) { throw new CmisConnectionException("Received Atom entry is not a CMIS entry!"); } LockLinks(); IObjectData result = null; try { // clean up cache RemoveLinks(repositoryId, entry.Id); // walk through the entry foreach (AtomElement element in entry.Elements) { if (element.Object is AtomLink) { AddLink(repositoryId, entry.Id, (AtomLink)element.Object); } else if (element.Object is IObjectData) { result = (IObjectData)element.Object; } } } finally { UnlockLinks(); } return result; } /// /// Retrieves a type definition. /// protected ITypeDefinition GetTypeDefinitionInternal(string repositoryId, string typeId) { Dictionary parameters = new Dictionary(); parameters[BindingConstants.ParamId] = typeId; string link = LoadTemplateLink(repositoryId, BindingConstants.TemplateTypeById, parameters); if (link == null) { throw new CmisObjectNotFoundException("Unknown repository!"); } // read and parse IResponse resp = Read(new UrlBuilder(link)); AtomEntry entry = Parse(resp.Stream); // we expect a CMIS entry if (entry.Id == null) { throw new CmisConnectionException("Received Atom entry is not a CMIS entry!"); } LockTypeLinks(); ITypeDefinition result = null; try { // clean up cache RemoveTypeLinks(repositoryId, entry.Id); // walk through the entry foreach (AtomElement element in entry.Elements) { if (element.Object is AtomLink) { AddTypeLink(repositoryId, entry.Id, (AtomLink)element.Object); } else if (element.Object is ITypeDefinition) { result = (ITypeDefinition)element.Object; } } } finally { UnlockTypeLinks(); } return result; } /// /// Retrieves the ACL of an object. /// public IAcl GetAclInternal(string repositoryId, string objectId, bool? onlyBasicPermissions, IExtensionsData extension) { // find the link string link = LoadLink(repositoryId, objectId, BindingConstants.RelAcl, BindingConstants.MediaTypeAcl); if (link == null) { ThrowLinkException(repositoryId, objectId, BindingConstants.RelAcl, BindingConstants.MediaTypeAcl); } UrlBuilder url = new UrlBuilder(link); url.AddParameter(BindingConstants.ParamOnlyBasicPermissions, onlyBasicPermissions); // read and parse IResponse resp = Read(url); AtomAcl acl = Parse(resp.Stream); return acl.Acl; } /// /// Updates the ACL of an object. /// protected AtomAcl UpdateAcl(string repositoryId, string objectId, IAcl acl, AclPropagation? aclPropagation) { // find the link string link = LoadLink(repositoryId, objectId, BindingConstants.RelAcl, BindingConstants.MediaTypeAcl); if (link == null) { ThrowLinkException(repositoryId, objectId, BindingConstants.RelAcl, BindingConstants.MediaTypeAcl); } UrlBuilder aclUrl = new UrlBuilder(link); aclUrl.AddParameter(BindingConstants.ParamAclPropagation, aclPropagation); CmisVersion cmisVersion = GetCmisVersion(repositoryId); // update IResponse resp = Put(aclUrl, new AtomPubHttpContent(BindingConstants.MediaTypeAcl, (stream) => { using (XmlWriter writer = XmlUtils.CreateWriter(stream)) { XmlUtils.StartXmlDocument(writer); XmlConverter.WriteAcl(writer, cmisVersion, true, acl); XmlUtils.EndXmlDocument(writer); } })); // parse new entry return Parse(resp.Stream); } } internal class RepositoryService : AbstractAtomPubService, IRepositoryService { public RepositoryService(BindingSession session) { Session = session; } public IList GetRepositoryInfos(IExtensionsData extension) { return GetRepositoriesInternal(null); } public IRepositoryInfo GetRepositoryInfo(string repositoryId, IExtensionsData extension) { IList repositoryInfos = GetRepositoriesInternal(repositoryId); if (repositoryInfos.Count == 0) { throw new CmisObjectNotFoundException("Repository '" + repositoryId + "'not found!"); } // find the repository foreach (IRepositoryInfo info in repositoryInfos) { if (info.Id == null) { continue; } if (info.Id == repositoryId) { return info; } } throw new CmisObjectNotFoundException("Repository '" + repositoryId + "' not found!"); } public ITypeDefinitionList GetTypeChildren(string repositoryId, string typeId, bool? includePropertyDefinitions, BigInteger? maxItems, BigInteger? skipCount, IExtensionsData extension) { TypeDefinitionList result = new TypeDefinitionList(); // find the link string link = null; if (typeId == null) { link = LoadCollection(repositoryId, BindingConstants.CollectionTypes); } else { link = LoadTypeLink(repositoryId, typeId, BindingConstants.RelDown, BindingConstants.MediaTypeChildren); } if (link == null) { throw new CmisObjectNotFoundException("Unknown repository or type!"); } UrlBuilder url = new UrlBuilder(link); url.AddParameter(BindingConstants.ParamPropertyDefinitions, includePropertyDefinitions); url.AddParameter(BindingConstants.ParamMaxItems, maxItems); url.AddParameter(BindingConstants.ParamSkipCount, skipCount); // read and parse IResponse resp = Read(url); AtomFeed feed = Parse(resp.Stream); // handle top level foreach (AtomElement element in feed.Elements) { if (element.Object is AtomLink) { if (IsNextLink(element)) { result.HasMoreItems = true; } } else if (IsInt(NameNumItems, element)) { result.NumItems = (BigInteger)element.Object; } } result.List = new List(feed.Entries.Count); // get the children if (feed.Entries.Count > 0) { foreach (AtomEntry entry in feed.Entries) { ITypeDefinition child = null; LockTypeLinks(); try { // walk through the entry foreach (AtomElement element in entry.Elements) { if (element.Object is AtomLink) { AddTypeLink(repositoryId, entry.Id, (AtomLink)element.Object); } else if (element.Object is ITypeDefinition) { child = (ITypeDefinition)element.Object; } } } finally { UnlockTypeLinks(); } if (child != null) { result.List.Add(child); } } } return result; } public IList GetTypeDescendants(string repositoryId, string typeId, BigInteger? depth, bool? includePropertyDefinitions, IExtensionsData extension) { IList result = new List(); // find the link string link = null; if (typeId == null) { link = LoadRepositoryLink(repositoryId, BindingConstants.RepRelTypeDesc); } else { link = LoadTypeLink(repositoryId, typeId, BindingConstants.RelDown, BindingConstants.MediaTypeDecendants); } if (link == null) { throw new CmisObjectNotFoundException("Unknown repository or type!"); } UrlBuilder url = new UrlBuilder(link); url.AddParameter(BindingConstants.ParamDepth, depth); url.AddParameter(BindingConstants.ParamPropertyDefinitions, includePropertyDefinitions); // read and parse IResponse resp = Read(url); AtomFeed feed = Parse(resp.Stream); // process tree AddTypeDescendantsLevel(repositoryId, feed, result); return result; } /// /// Adds type descendants level recursively. /// private void AddTypeDescendantsLevel(string repositoryId, AtomFeed feed, IList containerList) { if (feed == null || feed.Entries.Count == 0) { return; } // walk through the feed foreach (AtomEntry entry in feed.Entries) { TypeDefinitionContainer childContainer = null; IList childContainerList = new List(); // walk through the entry LockTypeLinks(); try { foreach (AtomElement element in entry.Elements) { if (element.Object is AtomLink) { AddTypeLink(repositoryId, entry.Id, (AtomLink)element.Object); } else if (element.Object is ITypeDefinition) { childContainer = new TypeDefinitionContainer() { TypeDefinition = (ITypeDefinition)element.Object }; } else if (element.Object is AtomFeed) { AddTypeDescendantsLevel(repositoryId, (AtomFeed)element.Object, childContainerList); } } } finally { UnlockTypeLinks(); } if (childContainer != null) { childContainer.Children = childContainerList; containerList.Add(childContainer); } } } public ITypeDefinition GetTypeDefinition(string repositoryId, string typeId, IExtensionsData extension) { return GetTypeDefinitionInternal(repositoryId, typeId); } public ITypeDefinition CreateType(string repositoryId, ITypeDefinition type, IExtensionsData extension) { if (type == null) { throw new CmisInvalidArgumentException("Type definition must be set!"); } string parentId = type.ParentTypeId; if (parentId == null) { throw new CmisInvalidArgumentException("Type definition has no parent type id!"); } // find the link string link = LoadTypeLink(repositoryId, parentId, BindingConstants.RelDown, BindingConstants.MediaTypeChildren); if (link == null) { throw new CmisObjectNotFoundException("Unknown repository or parent type!"); } // set up writer AtomEntryWriter entryWriter = new AtomEntryWriter(type, GetCmisVersion(repositoryId)); // post the new type definition IResponse resp = Post(new UrlBuilder(link), new AtomPubHttpContent(BindingConstants.MediaTypeEntry, (stream) => { entryWriter.Write(stream); })); // parse the response AtomEntry entry = Parse(resp.Stream); // we expect a CMIS entry if (entry.Id == null) { throw new CmisConnectionException("Received Atom entry is not a CMIS entry!"); } LockTypeLinks(); ITypeDefinition result = null; try { // clean up cache RemoveTypeLinks(repositoryId, entry.Id); // walk through the entry foreach (AtomElement element in entry.Elements) { if (element.Object is AtomLink) { AddTypeLink(repositoryId, entry.Id, (AtomLink)element.Object); } else if (element.Object is ITypeDefinition) { result = (ITypeDefinition)element.Object; } } } finally { UnlockTypeLinks(); } return result; } public ITypeDefinition UpdateType(string repositoryId, ITypeDefinition type, IExtensionsData extension) { if (type == null) { throw new CmisInvalidArgumentException("Type definition must be set!"); } string typeId = type.Id; if (typeId == null) { throw new CmisInvalidArgumentException("Type definition has no type ID!"); } // find the link Dictionary parameters = new Dictionary(); parameters.Add(BindingConstants.ParamId, typeId); string link = LoadTemplateLink(repositoryId, BindingConstants.TemplateTypeById, parameters); if (link == null) { throw new CmisObjectNotFoundException("Unknown repository or type!"); } // set up writer AtomEntryWriter entryWriter = new AtomEntryWriter(type, GetCmisVersion(repositoryId)); // post the new type definition IResponse resp = Put(new UrlBuilder(link), new AtomPubHttpContent(BindingConstants.MediaTypeEntry, (stream) => { entryWriter.Write(stream); })); // parse the response AtomEntry entry = Parse(resp.Stream); // we expect a CMIS entry if (entry.Id == null) { throw new CmisConnectionException("Received Atom entry is not a CMIS entry!"); } LockTypeLinks(); ITypeDefinition result = null; try { // clean up cache RemoveTypeLinks(repositoryId, entry.Id); // walk through the entry foreach (AtomElement element in entry.Elements) { if (element.Object is AtomLink) { AddTypeLink(repositoryId, entry.Id, (AtomLink)element.Object); } else if (element.Object is ITypeDefinition) { result = (ITypeDefinition)element.Object; } } } finally { UnlockTypeLinks(); } return result; } public void DeleteType(string repositoryId, string typeId, IExtensionsData extension) { Dictionary parameters = new Dictionary(); parameters.Add(BindingConstants.ParamId, typeId); string link = LoadTemplateLink(repositoryId, BindingConstants.TemplateTypeById, parameters); if (link == null) { throw new CmisObjectNotFoundException("Unknown repository!"); } Delete(new UrlBuilder(link)); } } internal class NavigationService : AbstractAtomPubService, INavigationService { public NavigationService(BindingSession session) { Session = session; } public IObjectInFolderList GetChildren(string repositoryId, string folderId, string filter, string orderBy, bool? includeAllowableActions, IncludeRelationships? includeRelationships, string renditionFilter, bool? includePathSegment, BigInteger? maxItems, BigInteger? skipCount, IExtensionsData extension) { ObjectInFolderList result = new ObjectInFolderList(); // find the link string link = LoadLink(repositoryId, folderId, BindingConstants.RelDown, BindingConstants.MediaTypeChildren); if (link == null) { ThrowLinkException(repositoryId, folderId, BindingConstants.RelDown, BindingConstants.MediaTypeChildren); } UrlBuilder url = new UrlBuilder(link); url.AddParameter(BindingConstants.ParamFilter, filter); url.AddParameter(BindingConstants.ParamOrderBy, orderBy); url.AddParameter(BindingConstants.ParamAllowableActions, includeAllowableActions); url.AddParameter(BindingConstants.ParamRelationships, includeRelationships); url.AddParameter(BindingConstants.ParamRenditionfilter, renditionFilter); url.AddParameter(BindingConstants.ParamPathSegment, includePathSegment); url.AddParameter(BindingConstants.ParamMaxItems, maxItems); url.AddParameter(BindingConstants.ParamSkipCount, skipCount); // read and parse IResponse resp = Read(url); AtomFeed feed = Parse(resp.Stream); // handle top level foreach (AtomElement element in feed.Elements) { if (element.Object is AtomLink) { if (IsNextLink(element)) { result.HasMoreItems = true; } } else if (IsInt(NameNumItems, element)) { result.NumItems = (BigInteger)element.Object; } } // get the children if (feed.Entries.Count > 0) { result.Objects = new List(feed.Entries.Count); foreach (AtomEntry entry in feed.Entries) { ObjectInFolderData child = null; string pathSegment = null; LockLinks(); try { // clean up cache RemoveLinks(repositoryId, entry.Id); // walk through the entry foreach (AtomElement element in entry.Elements) { if (element.Object is AtomLink) { AddLink(repositoryId, entry.Id, (AtomLink)element.Object); } else if (IsStr(NamePathSegment, element)) { pathSegment = (string)element.Object; } else if (element.Object is IObjectData) { child = new ObjectInFolderData(); child.Object = (IObjectData)element.Object; } } } finally { UnlockLinks(); } if (child != null) { child.PathSegment = pathSegment; result.Objects.Add(child); } } } return result; } public IList GetDescendants(string repositoryId, string folderId, BigInteger? depth, string filter, bool? includeAllowableActions, IncludeRelationships? includeRelationships, string renditionFilter, bool? includePathSegment, IExtensionsData extension) { IList result = new List(); // find the link string link = LoadLink(repositoryId, folderId, BindingConstants.RelDown, BindingConstants.MediaTypeDecendants); if (link == null) { ThrowLinkException(repositoryId, folderId, BindingConstants.RelDown, BindingConstants.MediaTypeEntry); } UrlBuilder url = new UrlBuilder(link); url.AddParameter(BindingConstants.ParamDepth, depth); url.AddParameter(BindingConstants.ParamFilter, filter); url.AddParameter(BindingConstants.ParamAllowableActions, includeAllowableActions); url.AddParameter(BindingConstants.ParamRelationships, includeRelationships); url.AddParameter(BindingConstants.ParamRenditionfilter, renditionFilter); url.AddParameter(BindingConstants.ParamPathSegment, includePathSegment); // read and parse IResponse resp = Read(url); AtomFeed feed = Parse(resp.Stream); // process tree AddDescendantsLevel(repositoryId, feed, result); return result; } public IList GetFolderTree(string repositoryId, string folderId, BigInteger? depth, string filter, bool? includeAllowableActions, IncludeRelationships? includeRelationships, string renditionFilter, bool? includePathSegment, IExtensionsData extension) { IList result = new List(); // find the link string link = LoadLink(repositoryId, folderId, BindingConstants.RelFolderTree, BindingConstants.MediaTypeDecendants); if (link == null) { ThrowLinkException(repositoryId, folderId, BindingConstants.RelFolderTree, BindingConstants.MediaTypeEntry); } UrlBuilder url = new UrlBuilder(link); url.AddParameter(BindingConstants.ParamDepth, depth); url.AddParameter(BindingConstants.ParamFilter, filter); url.AddParameter(BindingConstants.ParamAllowableActions, includeAllowableActions); url.AddParameter(BindingConstants.ParamRelationships, includeRelationships); url.AddParameter(BindingConstants.ParamRenditionfilter, renditionFilter); url.AddParameter(BindingConstants.ParamPathSegment, includePathSegment); // read and parse IResponse resp = Read(url); AtomFeed feed = Parse(resp.Stream); // process tree AddDescendantsLevel(repositoryId, feed, result); return result; } /// /// Adds descendants level recursively. /// private void AddDescendantsLevel(string repositoryId, AtomFeed feed, IList containerList) { if (feed == null || feed.Entries.Count == 0) { return; } // walk through the feed foreach (AtomEntry entry in feed.Entries) { ObjectInFolderData objectInFolder = null; string pathSegment = null; IList childContainerList = new List(); LockLinks(); try { // clean up cache RemoveLinks(repositoryId, entry.Id); // walk through the entry foreach (AtomElement element in entry.Elements) { if (element.Object is AtomLink) { AddLink(repositoryId, entry.Id, (AtomLink)element.Object); } else if (element.Object is IObjectData) { objectInFolder = new ObjectInFolderData() { Object = (IObjectData)element.Object }; } else if (IsStr(NamePathSegment, element)) { pathSegment = (string)element.Object; } else if (element.Object is AtomFeed) { AddDescendantsLevel(repositoryId, (AtomFeed)element.Object, childContainerList); } } } finally { UnlockLinks(); } if (objectInFolder != null) { objectInFolder.PathSegment = pathSegment; ObjectInFolderContainer childContainer = new ObjectInFolderContainer() { Object = objectInFolder }; childContainer.Children = childContainerList; containerList.Add(childContainer); } } } public IList GetObjectParents(string repositoryId, string objectId, string filter, bool? includeAllowableActions, IncludeRelationships? includeRelationships, string renditionFilter, bool? includeRelativePathSegment, IExtensionsData extension) { IList result = new List(); // find the link string link = LoadLink(repositoryId, objectId, BindingConstants.RelUp, BindingConstants.MediaTypeFeed); if (link == null) { // root and unfiled objects have no UP link return result; } UrlBuilder url = new UrlBuilder(link); url.AddParameter(BindingConstants.ParamFilter, filter); url.AddParameter(BindingConstants.ParamAllowableActions, includeAllowableActions); url.AddParameter(BindingConstants.ParamRelationships, includeRelationships); url.AddParameter(BindingConstants.ParamRenditionfilter, renditionFilter); url.AddParameter(BindingConstants.ParamRelativePathSegment, includeRelativePathSegment); // read and parse IResponse resp = Read(url); AtomBase atomBase = Parse(resp.Stream); if (atomBase is AtomFeed) { // it's a feed AtomFeed feed = (AtomFeed)atomBase; // walk through the feed foreach (AtomEntry entry in feed.Entries) { IObjectParentData objectParent = ProcessParentEntry(entry, repositoryId); if (objectParent != null) { result.Add(objectParent); } } } else if (atomBase is AtomEntry) { // it's an entry AtomEntry entry = (AtomEntry)atomBase; IObjectParentData objectParent = ProcessParentEntry(entry, repositoryId); if (objectParent != null) { result.Add(objectParent); } } return result; } private ObjectParentData ProcessParentEntry(AtomEntry entry, string repositoryId) { ObjectParentData result = null; string relativePathSegment = null; LockLinks(); try { // clean up cache RemoveLinks(repositoryId, entry.Id); // walk through the entry foreach (AtomElement element in entry.Elements) { if (element.Object is AtomLink) { AddLink(repositoryId, entry.Id, (AtomLink)element.Object); } else if (element.Object is ObjectData) { result = new ObjectParentData() { Object = (ObjectData)element.Object }; } else if (IsStr(NameRelativePathSegment, element)) { relativePathSegment = (string)element.Object; } } } finally { UnlockLinks(); } if (result != null) { result.RelativePathSegment = relativePathSegment; } return result; } public IObjectData GetFolderParent(string repositoryId, string folderId, string filter, IExtensionsData extension) { IObjectData result = null; // find the link string link = LoadLink(repositoryId, folderId, BindingConstants.RelUp, BindingConstants.MediaTypeEntry); if (link == null) { ThrowLinkException(repositoryId, folderId, BindingConstants.RelUp, BindingConstants.MediaTypeEntry); } UrlBuilder url = new UrlBuilder(link); url.AddParameter(BindingConstants.ParamFilter, filter); // read IResponse resp = Read(url); AtomBase atomBase = Parse(resp.Stream); // get the entry AtomEntry entry = null; if (atomBase is AtomFeed) { AtomFeed feed = (AtomFeed)atomBase; if (feed.Entries.Count == 0) { throw new CmisRuntimeException("Parent feed is empty!"); } entry = feed.Entries[0]; } else if (atomBase is AtomEntry) { entry = (AtomEntry)atomBase; } else { throw new CmisRuntimeException("Unexpected document!"); } LockLinks(); try { // clean up cache RemoveLinks(repositoryId, entry.Id); // walk through the entry foreach (AtomElement element in entry.Elements) { if (element.Object is AtomLink) { AddLink(repositoryId, entry.Id, (AtomLink)element.Object); } else if (element.Object is IObjectData) { result = (IObjectData)element.Object; } } } finally { UnlockLinks(); } return result; } public IObjectList GetCheckedOutDocs(string repositoryId, string folderId, string filter, string orderBy, bool? includeAllowableActions, IncludeRelationships? includeRelationships, string renditionFilter, BigInteger? maxItems, BigInteger? skipCount, IExtensionsData extension) { ObjectList result = new ObjectList(); // find the link string link = LoadCollection(repositoryId, BindingConstants.CollectionCheckedout); if (link == null) { throw new CmisObjectNotFoundException("Unknown repository or checkedout collection not supported!"); } UrlBuilder url = new UrlBuilder(link); url.AddParameter(BindingConstants.ParamFolderId, folderId); url.AddParameter(BindingConstants.ParamFilter, filter); url.AddParameter(BindingConstants.ParamOrderBy, orderBy); url.AddParameter(BindingConstants.ParamAllowableActions, includeAllowableActions); url.AddParameter(BindingConstants.ParamRelationships, includeRelationships); url.AddParameter(BindingConstants.ParamRenditionfilter, renditionFilter); url.AddParameter(BindingConstants.ParamMaxItems, maxItems); url.AddParameter(BindingConstants.ParamSkipCount, skipCount); // read and parse IResponse resp = Read(url); AtomFeed feed = Parse(resp.Stream); // handle top level foreach (AtomElement element in feed.Elements) { if (element.Object is AtomLink) { if (IsNextLink(element)) { result.HasMoreItems = true; } } else if (IsInt(NameNumItems, element)) { result.NumItems = (BigInteger)element.Object; } } // get the documents if (feed.Entries.Count > 0) { result.Objects = new List(feed.Entries.Count); foreach (AtomEntry entry in feed.Entries) { IObjectData child = null; LockLinks(); try { // clean up cache RemoveLinks(repositoryId, entry.Id); // walk through the entry foreach (AtomElement element in entry.Elements) { if (element.Object is AtomLink) { AddLink(repositoryId, entry.Id, (AtomLink)element.Object); } else if (element.Object is IObjectData) { child = (IObjectData)element.Object; } } } finally { UnlockLinks(); } if (child != null) { result.Objects.Add(child); } } } return result; } } internal class ObjectService : AbstractAtomPubService, IObjectService { public ObjectService(BindingSession session) { Session = session; } public string CreateDocument(string repositoryId, IProperties properties, string folderId, IContentStream contentStream, VersioningState? versioningState, IList policies, IAcl addAces, IAcl removeAces, IExtensionsData extension) { CheckCreateProperties(properties); // find the link string link = null; if (folderId == null) { // Creation of unfiled objects via AtomPub is not defined in the // CMIS 1.0 specification. This implementation follow the CMIS // 1.1 draft and POSTs the document to the Unfiled collection. link = LoadCollection(repositoryId, BindingConstants.CollectionUnfiled); if (link == null) { throw new CmisObjectNotFoundException("Unknown repository or unfiling not supported!"); } } else { link = LoadLink(repositoryId, folderId, BindingConstants.RelDown, BindingConstants.MediaTypeChildren); if (link == null) { ThrowLinkException(repositoryId, folderId, BindingConstants.RelDown, BindingConstants.MediaTypeChildren); } } UrlBuilder url = new UrlBuilder(link); url.AddParameter(BindingConstants.ParamVersioningState, versioningState); // set up writer AtomEntryWriter entryWriter = new AtomEntryWriter(CreateObject(properties, null, policies), GetCmisVersion(repositoryId), contentStream); // post the new document object IResponse resp = Post(url, new AtomPubHttpContent(BindingConstants.MediaTypeEntry, (stream) => { entryWriter.Write(stream); })); // parse the response AtomEntry entry = Parse(resp.Stream); // handle ACL modifications HandleAclModifications(repositoryId, entry, addAces, removeAces); return entry.Id; } public string CreateDocumentFromSource(string repositoryId, string sourceId, IProperties properties, string folderId, VersioningState? versioningState, IList policies, IAcl addAces, IAcl removeAces, IExtensionsData extension) { throw new CmisNotSupportedException("createDocumentFromSource is not supported by the AtomPub binding!"); } public string CreateFolder(string repositoryId, IProperties properties, string folderId, IList policies, IAcl addAces, IAcl removeAces, IExtensionsData extension) { CheckCreateProperties(properties); // find the link string link = LoadLink(repositoryId, folderId, BindingConstants.RelDown, BindingConstants.MediaTypeChildren); if (link == null) { ThrowLinkException(repositoryId, folderId, BindingConstants.RelDown, BindingConstants.MediaTypeChildren); } UrlBuilder url = new UrlBuilder(link); // set up writer AtomEntryWriter entryWriter = new AtomEntryWriter(CreateObject(properties, null, policies), GetCmisVersion(repositoryId)); // post the new folder object IResponse resp = Post(url, new AtomPubHttpContent(BindingConstants.MediaTypeEntry, (stream) => { entryWriter.Write(stream); })); // Parse the response AtomEntry entry = Parse(resp.Stream); // handle ACL modifications HandleAclModifications(repositoryId, entry, addAces, removeAces); return entry.Id; } public string CreateRelationship(string repositoryId, IProperties properties, IList policies, IAcl addAces, IAcl removeAces, IExtensionsData extension) { CheckCreateProperties(properties); // find source id IPropertyData sourceIdProperty = properties[PropertyIds.SourceId]; if (sourceIdProperty == null) { throw new CmisInvalidArgumentException("Source Id is not set!"); } string sourceId = sourceIdProperty.FirstValue as string; if (sourceId == null) { throw new CmisInvalidArgumentException("Source Id is not set!"); } // find the link string link = LoadLink(repositoryId, sourceId, BindingConstants.RelRelationships, BindingConstants.MediaTypeFeed); if (link == null) { ThrowLinkException(repositoryId, sourceId, BindingConstants.RelRelationships, BindingConstants.MediaTypeFeed); } UrlBuilder url = new UrlBuilder(link); // set up writer AtomEntryWriter entryWriter = new AtomEntryWriter(CreateObject(properties, null, policies), GetCmisVersion(repositoryId)); // post the new relationship object IResponse resp = Post(url, new AtomPubHttpContent(BindingConstants.MediaTypeEntry, (stream) => { entryWriter.Write(stream); })); // Parse the response AtomEntry entry = Parse(resp.Stream); // handle ACL modifications HandleAclModifications(repositoryId, entry, addAces, removeAces); return entry.Id; } public string CreatePolicy(string repositoryId, IProperties properties, string folderId, IList policies, IAcl addAces, IAcl removeAces, IExtensionsData extension) { CheckCreateProperties(properties); // find the link string link = null; if (folderId == null) { // Creation of unfiled objects via AtomPub is not defined in the // CMIS 1.0 specification. This implementation follow the CMIS // 1.1 draft and POSTs the policy to the Unfiled collection. link = LoadCollection(repositoryId, BindingConstants.CollectionUnfiled); if (link == null) { throw new CmisObjectNotFoundException("Unknown repository or unfiling not supported!"); } } else { link = LoadLink(repositoryId, folderId, BindingConstants.RelDown, BindingConstants.MediaTypeChildren); if (link == null) { ThrowLinkException(repositoryId, folderId, BindingConstants.RelDown, BindingConstants.MediaTypeChildren); } } UrlBuilder url = new UrlBuilder(link); // set up writer AtomEntryWriter entryWriter = new AtomEntryWriter(CreateObject(properties, null, policies), GetCmisVersion(repositoryId)); // post the new policy object IResponse resp = Post(url, new AtomPubHttpContent(BindingConstants.MediaTypeEntry, (stream) => { entryWriter.Write(stream); })); // parse the response AtomEntry entry = Parse(resp.Stream); // handle ACL modifications HandleAclModifications(repositoryId, entry, addAces, removeAces); return entry.Id; } public string CreateItem(string repositoryId, IProperties properties, string folderId, IList policies, IAcl addAces, IAcl removeAces, IExtensionsData extension) { CheckCreateProperties(properties); // find the link string link = null; if (folderId == null) { link = LoadCollection(repositoryId, BindingConstants.CollectionUnfiled); if (link == null) { throw new CmisObjectNotFoundException("Unknown repository or unfiling not supported!"); } } else { link = LoadLink(repositoryId, folderId, BindingConstants.RelDown, BindingConstants.MediaTypeChildren); if (link == null) { ThrowLinkException(repositoryId, folderId, BindingConstants.RelDown, BindingConstants.MediaTypeChildren); } } UrlBuilder url = new UrlBuilder(link); // set up writer AtomEntryWriter entryWriter = new AtomEntryWriter(CreateObject(properties, null, policies), GetCmisVersion(repositoryId)); // post the new item object IResponse resp = Post(url, new AtomPubHttpContent(BindingConstants.MediaTypeEntry, (stream) => { entryWriter.Write(stream); })); // parse the response AtomEntry entry = Parse(resp.Stream); // handle ACL modifications HandleAclModifications(repositoryId, entry, addAces, removeAces); return entry.Id; } private void CheckCreateProperties(IProperties properties) { if (properties == null) { throw new CmisInvalidArgumentException("Properties must be set!"); } if (properties[PropertyIds.ObjectTypeId] == null) { throw new CmisInvalidArgumentException("Property " + PropertyIds.ObjectTypeId + " must be set!"); } if (properties[PropertyIds.ObjectId] != null) { throw new CmisInvalidArgumentException("Property " + PropertyIds.ObjectId + " must not be set!"); } } private void HandleAclModifications(string repositoryId, AtomEntry entry, IAcl addAces, IAcl removeAces) { if (!IsAclMergeRequired(addAces, removeAces)) { return; } IAcl originalAces = GetAclInternal(repositoryId, entry.Id, false, null); if (originalAces != null) { // merge and update ACL IAcl newACL = MergeAcls(originalAces, addAces, removeAces); if (newACL != null) { UpdateAcl(repositoryId, entry.Id, newACL, null); } } } public IAllowableActions GetAllowableActions(string repositoryId, string objectId, IExtensionsData extension) { // find the link string link = LoadLink(repositoryId, objectId, BindingConstants.RelAllowableActions, BindingConstants.MediaTypeAllowableAction); if (link == null) { ThrowLinkException(repositoryId, objectId, BindingConstants.RelAllowableActions, BindingConstants.MediaTypeAllowableAction); } UrlBuilder url = new UrlBuilder(link); // read and parse IResponse resp = Read(url); AtomAllowableActions allowableActions = Parse(resp.Stream); return allowableActions.AllowableActions; } public IProperties GetProperties(string repositoryId, string objectId, string filter, IExtensionsData extension) { IObjectData objectData = GetObjectInternal(repositoryId, IdentifierType.ID, objectId, ReturnVersion.This, filter, false, IncludeRelationships.None, "cmis:none", false, false, extension); return objectData.Properties; } public IList GetRenditions(string repositoryId, string objectId, string renditionFilter, BigInteger? maxItems, BigInteger? skipCount, IExtensionsData extension) { IObjectData objectData = GetObjectInternal(repositoryId, IdentifierType.ID, objectId, ReturnVersion.This, PropertyIds.ObjectId, false, IncludeRelationships.None, renditionFilter, false, false, extension); IList result = objectData.Renditions; if (result == null) { result = new List(); } return result; } public IObjectData GetObject(string repositoryId, string objectId, string filter, bool? includeAllowableActions, IncludeRelationships? includeRelationships, string renditionFilter, bool? includePolicyIds, bool? includeAcl, IExtensionsData extension) { return GetObjectInternal(repositoryId, IdentifierType.ID, objectId, ReturnVersion.This, filter, includeAllowableActions, includeRelationships, renditionFilter, includePolicyIds, includeAcl, extension); } public IObjectData GetObjectByPath(string repositoryId, string path, string filter, bool? includeAllowableActions, IncludeRelationships? includeRelationships, string renditionFilter, bool? includePolicyIds, bool? includeAcl, IExtensionsData extension) { return GetObjectInternal(repositoryId, IdentifierType.PATH, path, ReturnVersion.This, filter, includeAllowableActions, includeRelationships, renditionFilter, includePolicyIds, includeAcl, extension); } public IContentStream GetContentStream(string repositoryId, string objectId, string streamId, BigInteger? offset, BigInteger? length, IExtensionsData extension) { // find the link string link = null; if (streamId != null) { // use the alternate link per spec link = LoadLink(repositoryId, objectId, BindingConstants.RelAlternate, streamId); if (link != null) { streamId = null; // we have a full URL now } } if (link == null) { link = LoadLink(repositoryId, objectId, AtomPubParser.LinkRelContent, null); } if (link == null) { throw new CmisConstraintException("No content stream"); } UrlBuilder url = new UrlBuilder(link); // using the content URL and adding a streamId param is not // spec-compliant url.AddParameter(BindingConstants.ParamStreamId, streamId); // get the content IResponse resp = Session.GetHttpInvoker().InvokeGET(url, Session, (long?)offset, (long?)length); // check response code if (resp.StatusCode != 200 && resp.StatusCode != 206) { throw ConvertStatusCode(resp.StatusCode, resp.Message, resp.ErrorContent, null); } ContentStream result; if (resp.StatusCode == 206) { result = new PartialContentStream(); } else { result = new ContentStream(); } result.Length = resp.ContentLength; result.MimeType = resp.ContentType; result.Stream = resp.Stream; return result; } public void UpdateProperties(string repositoryId, ref string objectId, ref string changeToken, IProperties properties, IExtensionsData extension) { // we need an object id if (objectId == null || objectId.Length == 0) { throw new CmisInvalidArgumentException("Object ID must be set!"); } // find the link string link = LoadLink(repositoryId, objectId, BindingConstants.RelSelf, BindingConstants.MediaTypeEntry); if (link == null) { ThrowLinkException(repositoryId, objectId, BindingConstants.RelSelf, BindingConstants.MediaTypeEntry); } UrlBuilder url = new UrlBuilder(link); if (changeToken != null) { if (Session.GetValue(SessionParameter.OmitChangeTokens, false)) { changeToken = null; } else { // not required by the CMIS specification -> keep for backwards // compatibility with older OpenCMIS servers url.AddParameter(BindingConstants.ParamChangeToken, changeToken); } } // set up writer AtomEntryWriter entryWriter = new AtomEntryWriter(CreateObject(properties, changeToken, null), GetCmisVersion(repositoryId)); // update IResponse resp = Put(url, new AtomPubHttpContent(BindingConstants.MediaTypeEntry, (stream) => { entryWriter.Write(stream); })); // parse new entry AtomEntry entry = Parse(resp.Stream); // we expect a CMIS entry if (entry.Id == null) { throw new CmisConnectionException("Received Atom entry is not a CMIS entry!"); } // set object id objectId = entry.Id; changeToken = null; // just in case LockLinks(); try { // clean up cache RemoveLinks(repositoryId, entry.Id); // walk through the entry foreach (AtomElement element in entry.Elements) { if (element.Object is AtomLink) { AddLink(repositoryId, entry.Id, (AtomLink)element.Object); } else if (element.Object is IObjectData) { // extract new change toke IObjectData objectData = (IObjectData)element.Object; if (objectData.Properties != null) { IPropertyData changeTokenStr = objectData.Properties[PropertyIds.ChangeToken]; if (changeTokenStr != null) { changeToken = changeTokenStr.FirstValue as string; } } } } } finally { UnlockLinks(); } } public IList BulkUpdateProperties(string repositoryId, IList objectIdAndChangeToken, IProperties properties, IList addSecondaryTypeIds, IList removeSecondaryTypeIds, IExtensionsData extension) { // find link string link = LoadCollection(repositoryId, BindingConstants.CollectionBulkUpdate); if (link == null) { throw new CmisObjectNotFoundException("Unknown repository or bulk update properties is not supported!"); } // set up writer BulkUpdate bulkUpdate = new BulkUpdate(); bulkUpdate.ObjectIdAndChangeToken = objectIdAndChangeToken; bulkUpdate.Properties = properties; bulkUpdate.AddSecondaryTypeIds = addSecondaryTypeIds; bulkUpdate.RemoveSecondaryTypeIds = removeSecondaryTypeIds; AtomEntryWriter entryWriter = new AtomEntryWriter(bulkUpdate); // post update IResponse resp = Post(new UrlBuilder(link), new AtomPubHttpContent(BindingConstants.MediaTypeEntry, (stream) => { entryWriter.Write(stream); })); AtomFeed feed = Parse(resp.Stream); List result = new List(feed.Entries.Count); // get the results if (feed.Entries.Count > 0) { foreach (AtomEntry entry in feed.Entries) { // walk through the entry // we are not interested in the links this time because they // could belong to a new document version foreach (AtomElement element in entry.Elements) { if (element.Object is IObjectData) { IObjectData objectData = (IObjectData)element.Object; string id = objectData.Id; if (id != null) { string changeToken = null; IPropertyData changeTokenStr = objectData.Properties[PropertyIds.ChangeToken]; if (changeTokenStr != null) { changeToken = changeTokenStr.FirstValue as string; } result.Add(new BulkUpdateObjectIdAndChangeToken() { Id = id, ChangeToken = changeToken }); } } } } } return result; } public void MoveObject(string repositoryId, ref string objectId, string targetFolderId, string sourceFolderId, IExtensionsData extension) { if (objectId == null || objectId.Length == 0) { throw new CmisInvalidArgumentException("Object ID must be set!"); } if (targetFolderId == null || targetFolderId.Length == 0 || sourceFolderId == null || sourceFolderId.Length == 0) { throw new CmisInvalidArgumentException("Source and target folder must be set!"); } // find the link string link = LoadLink(repositoryId, targetFolderId, BindingConstants.RelDown, BindingConstants.MediaTypeChildren); if (link == null) { ThrowLinkException(repositoryId, targetFolderId, BindingConstants.RelDown, BindingConstants.MediaTypeChildren); } UrlBuilder url = new UrlBuilder(link); url.AddParameter(BindingConstants.ParamSourceFolderId, sourceFolderId); // workaround for SharePoint 2010 - see CMIS-839 bool objectIdOnMove = Session.GetValue(SessionParameter.IncludeObjectIdUrlParamOnMove, false); if (objectIdOnMove) { url.AddParameter("objectId", objectId); url.AddParameter("targetFolderId", targetFolderId); } // set up object and writer AtomEntryWriter entryWriter = new AtomEntryWriter(CreateIdObject(objectId), GetCmisVersion(repositoryId)); // post move request IResponse resp = Post(url, new AtomPubHttpContent(BindingConstants.MediaTypeEntry, (stream) => { entryWriter.Write(stream); })); // workaround for SharePoint 2010 - see CMIS-839 if (objectIdOnMove) { // SharePoint doesn't return a new object ID // we assume that the object ID hasn't changed return; } // parse the response AtomEntry entry = Parse(resp.Stream); objectId = entry.Id; } public void DeleteObject(string repositoryId, string objectId, bool? allVersions, IExtensionsData extension) { // find the link string link = LoadLink(repositoryId, objectId, BindingConstants.RelSelf, BindingConstants.MediaTypeEntry); if (link == null) { ThrowLinkException(repositoryId, objectId, BindingConstants.RelSelf, BindingConstants.MediaTypeEntry); } UrlBuilder url = new UrlBuilder(link); url.AddParameter(BindingConstants.ParamAllVersions, allVersions); Delete(url); } public IFailedToDeleteData DeleteTree(string repositoryId, string folderId, bool? allVersions, UnfileObject? unfileObjects, bool? continueOnFailure, IExtensionsData extension) { // find the down links string link = LoadLink(repositoryId, folderId, BindingConstants.RelDown, null); string childrenLink = null; if (link != null) { // found only a children link, but no descendants link // -> try folder tree link childrenLink = link; link = null; } else { // found no or two down links // -> get only the descendants link link = LoadLink(repositoryId, folderId, BindingConstants.RelDown, BindingConstants.MediaTypeDecendants); } if (link == null) { link = LoadLink(repositoryId, folderId, BindingConstants.RelFolderTree, BindingConstants.MediaTypeDecendants); } if (link == null) { link = LoadLink(repositoryId, folderId, BindingConstants.RelFolderTree, BindingConstants.MediaTypeFeed); } if (link == null) { link = childrenLink; } if (link == null) { ThrowLinkException(repositoryId, folderId, BindingConstants.RelDown, BindingConstants.MediaTypeDecendants); } UrlBuilder url = new UrlBuilder(link); url.AddParameter(BindingConstants.ParamAllVersions, allVersions); url.AddParameter(BindingConstants.ParamUnfileObjects, unfileObjects); url.AddParameter(BindingConstants.ParamContinueOnFailure, continueOnFailure); // make the call IResponse resp = Session.GetHttpInvoker().InvokeDELETE(url, Session); // check response code if (resp.StatusCode == 200 || resp.StatusCode == 202 || resp.StatusCode == 204) { return new FailedToDeleteData(); } // If the server returned an internal server error, get the remaining // children of the folder. We only retrieve the first level, since // getDescendants() is not supported by all repositories. if (resp.StatusCode == 500) { link = LoadLink(repositoryId, folderId, BindingConstants.RelDown, BindingConstants.MediaTypeChildren); if (link != null) { url = new UrlBuilder(link); // we only want the object ids url.AddParameter(BindingConstants.ParamFilter, "cmis:objectId"); url.AddParameter(BindingConstants.ParamAllowableActions, false); url.AddParameter(BindingConstants.ParamRelationships, IncludeRelationships.None); url.AddParameter(BindingConstants.ParamRenditionfilter, "cmis:none"); url.AddParameter(BindingConstants.ParamPathSegment, false); // 1000 children should be enough to indicate a problem url.AddParameter(BindingConstants.ParamMaxItems, 1000); url.AddParameter(BindingConstants.ParamSkipCount, 0); // read and parse resp = Read(url); AtomFeed feed = Parse(resp.Stream); // prepare result FailedToDeleteData result = new FailedToDeleteData(); IList ids = new List(); result.Ids = ids; // get the children ids foreach (AtomEntry entry in feed.Entries) { ids.Add(entry.Id); } return result; } } throw ConvertStatusCode(resp.StatusCode, resp.Message, resp.ErrorContent, null); } public void SetContentStream(string repositoryId, ref string objectId, bool? overwriteFlag, ref string changeToken, IContentStream contentStream, IExtensionsData extension) { SetOrAppendContent(repositoryId, ref objectId, overwriteFlag, ref changeToken, contentStream, true, false, extension); } public void DeleteContentStream(string repositoryId, ref string objectId, ref string changeToken, IExtensionsData extension) { // we need an object id if (objectId == null) { throw new CmisInvalidArgumentException("Object ID must be set!"); } // find the link string link = LoadLink(repositoryId, objectId, BindingConstants.RelEditMedia, null); if (link == null) { ThrowLinkException(repositoryId, objectId, BindingConstants.RelEditMedia, null); } UrlBuilder url = new UrlBuilder(link); if (changeToken != null && !Session.GetValue(SessionParameter.OmitChangeTokens, false)) { url.AddParameter(BindingConstants.ParamChangeToken, changeToken); } Delete(url); objectId = null; changeToken = null; } public void AppendContentStream(string repositoryId, ref string objectId, bool? isLastChunk, ref string changeToken, IContentStream contentStream, IExtensionsData extension) { SetOrAppendContent(repositoryId, ref objectId, null, ref changeToken, contentStream, isLastChunk, true, extension); } /// /// Sets or appends content. /// private void SetOrAppendContent(string repositoryId, ref string objectId, bool? overwriteFlag, ref string changeToken, IContentStream contentStream, bool? isLastChunk, bool append, IExtensionsData extension) { // we need an object id if (objectId == null) { throw new CmisInvalidArgumentException("Object ID must be set!"); } // we need content if (contentStream == null || contentStream.Stream == null || contentStream.MimeType == null) { throw new CmisInvalidArgumentException("Content must be set!"); } // find the link string link = LoadLink(repositoryId, objectId, BindingConstants.RelEditMedia, null); if (link == null) { ThrowLinkException(repositoryId, objectId, BindingConstants.RelEditMedia, null); } UrlBuilder url = new UrlBuilder(link); if (changeToken != null && !Session.GetValue(SessionParameter.OmitChangeTokens, false)) { url.AddParameter(BindingConstants.ParamChangeToken, changeToken); } if (append) { url.AddParameter(BindingConstants.ParamAppend, true); url.AddParameter(BindingConstants.ParamIsLastChunk, isLastChunk); } else { url.AddParameter(BindingConstants.ParamOverwriteFlag, overwriteFlag); } Stream content = contentStream.Stream; // Content-Disposition header for the filename IDictionary headers = null; if (contentStream.FileName != null) { headers = new Dictionary(); headers.Add(MimeHelper.ContentDisposition, MimeHelper.EncodeContentDisposition(MimeHelper.DispositionAttachment, contentStream.FileName)); } // send content IResponse resp = Put(url, headers, new AtomPubHttpContent(BindingConstants.MediaTypeEntry, (stream) => { content.CopyTo(stream); })); // check response code further if (resp.StatusCode != 200 && resp.StatusCode != 201 && resp.StatusCode != 204) { throw ConvertStatusCode(resp.StatusCode, resp.Message, resp.ErrorContent, null); } if (resp.StatusCode == 201) { // unset the object ID if a new resource has been created // (if the resource has been updated (200 and 204), the object ID // hasn't changed) objectId = null; } changeToken = null; } } internal class VersioningService : AbstractAtomPubService, IVersioningService { public VersioningService(BindingSession session) { Session = session; } public void CheckOut(string repositoryId, ref string objectId, IExtensionsData extension, out bool? contentCopied) { if (objectId == null || objectId.Length == 0) { throw new CmisInvalidArgumentException("Object ID must be set!"); } // find the link string link = LoadCollection(repositoryId, BindingConstants.CollectionCheckedout); if (link == null) { throw new CmisObjectNotFoundException("Unknown repository or checkedout collection not supported!"); } UrlBuilder url = new UrlBuilder(link); // workaround for SharePoint 2010 - see CMIS-362 if (Session.GetValue(SessionParameter.IncludeObjectIdUrlParamOnCheckout, false)) { url.AddParameter("objectId", objectId); } // set up object and writer AtomEntryWriter entryWriter = new AtomEntryWriter(CreateIdObject(objectId), GetCmisVersion(repositoryId)); // post check out request IResponse resp = Post(url, new AtomPubHttpContent(BindingConstants.MediaTypeEntry, (stream) => { entryWriter.Write(stream); })); // parse the response AtomEntry entry = Parse(resp.Stream); objectId = entry.Id; LockLinks(); try { // clean up cache RemoveLinks(repositoryId, entry.Id); // walk through the entry foreach (AtomElement element in entry.Elements) { if (element.Object is AtomLink) { AddLink(repositoryId, entry.Id, (AtomLink)element.Object); } } } finally { UnlockLinks(); } contentCopied = null; } public void CancelCheckOut(string repositoryId, string objectId, IExtensionsData extension) { // find the link string link = LoadLink(repositoryId, objectId, BindingConstants.RelSelf, BindingConstants.MediaTypeEntry); if (link == null) { ThrowLinkException(repositoryId, objectId, BindingConstants.RelSelf, BindingConstants.MediaTypeEntry); } // prefer working copy link if available // (workaround for non-compliant repositories) string wcLink = GetLink(repositoryId, objectId, BindingConstants.RelWorkingCopy, BindingConstants.MediaTypeEntry); if (wcLink != null) { link = wcLink; } Delete(new UrlBuilder(link)); } public void CheckIn(string repositoryId, ref string objectId, bool? major, IProperties properties, IContentStream contentStream, string checkinComment, IList policies, IAcl addAces, IAcl removeAces, IExtensionsData extension) { // we need an object id if (objectId == null || objectId.Length == 0) { throw new CmisInvalidArgumentException("Object ID must be set!"); } // find the link string link = LoadLink(repositoryId, objectId, BindingConstants.RelSelf, BindingConstants.MediaTypeEntry); if (link == null) { ThrowLinkException(repositoryId, objectId, BindingConstants.RelSelf, BindingConstants.MediaTypeEntry); } // prefer working copy link if available // (workaround for non-compliant repositories) string wcLink = GetLink(repositoryId, objectId, BindingConstants.RelWorkingCopy, BindingConstants.MediaTypeEntry); if (wcLink != null) { link = wcLink; } UrlBuilder url = new UrlBuilder(link); url.AddParameter(BindingConstants.ParamCheckinComment, checkinComment); url.AddParameter(BindingConstants.ParamMajor, major); url.AddParameter(BindingConstants.ParamCheckIn, "true"); // workaround for SharePoint - check in without property change if (Session.GetValue(SessionParameter.AddNameOnCheckIn, false)) { if (properties == null || properties.PropertyList.Count == 0) { properties = new Properties(); try { string name = null; // fetch the current name IObjectData obj = GetObjectInternal(repositoryId, IdentifierType.ID, objectId, ReturnVersion.This, "cmis:objectId,cmis:name", false, IncludeRelationships.None, "cmis:none", false, false, null); if (obj != null && obj.Properties != null && obj.Properties[PropertyIds.Name] != null) { name = obj.Properties[PropertyIds.Name].FirstValue as string; } if (name == null) { throw new CmisRuntimeException("Could not determine the name of the PWC!"); } // set the document name to the same value - silly, but // SharePoint requires that at least one property value has // to be changed and the name is the only reliable property PropertyData newNameProp = new PropertyData(PropertyType.String); newNameProp.Id = PropertyIds.Name; newNameProp.AddValue(name); ((Properties)properties).AddProperty(newNameProp); } catch (CmisBaseException e) { throw new CmisRuntimeException("Could not determine the name of the PWC: " + e.ToString(), e); } } } // set up writer AtomEntryWriter entryWriter = new AtomEntryWriter(CreateObject(properties, null, policies), GetCmisVersion(repositoryId), contentStream); // update IResponse resp = Put(url, new AtomPubHttpContent(BindingConstants.MediaTypeEntry, (stream) => { entryWriter.Write(stream); })); // parse new entry AtomEntry entry = Parse(resp.Stream); // we expect a CMIS entry if (entry.Id == null) { throw new CmisConnectionException("Received Atom entry is not a CMIS entry!"); } // set object id objectId = entry.Id; Acl originalAces = null; LockLinks(); try { // clean up cache RemoveLinks(repositoryId, entry.Id); // walk through the entry foreach (AtomElement element in entry.Elements) { if (element.Object is AtomLink) { AddLink(repositoryId, entry.Id, (AtomLink)element.Object); } else if (element.Object is IObjectData) { // extract current ACL IObjectData objectData = (IObjectData)element.Object; if (objectData.Acl != null) { originalAces = new Acl() { Aces = objectData.Acl.Aces }; originalAces.IsExact = objectData.IsExactAcl; } } } } finally { UnlockLinks(); } // handle ACL modifications if (originalAces != null && IsAclMergeRequired(addAces, removeAces)) { // merge and update ACL IAcl newACL = MergeAcls(originalAces, addAces, removeAces); if (newACL != null) { UpdateAcl(repositoryId, entry.Id, newACL, null); } } } public IObjectData GetObjectOfLatestVersion(string repositoryId, string objectId, string versionSeriesId, bool? major, string filter, bool? includeAllowableActions, IncludeRelationships? includeRelationships, string renditionFilter, bool? includePolicyIds, bool? includeAcl, IExtensionsData extension) { ReturnVersion returnVersion = (major == true ? ReturnVersion.LatestMajor : ReturnVersion.Latest); return GetObjectInternal(repositoryId, IdentifierType.ID, objectId, returnVersion, filter, includeAllowableActions, includeRelationships, renditionFilter, includePolicyIds, includeAcl, extension); } public IProperties GetPropertiesOfLatestVersion(string repositoryId, string objectId, string versionSeriesId, bool? major, string filter, IExtensionsData extension) { ReturnVersion returnVersion = (major == true ? ReturnVersion.LatestMajor : ReturnVersion.Latest); IObjectData objectData = GetObjectInternal(repositoryId, IdentifierType.ID, objectId, returnVersion, filter, false, IncludeRelationships.None, "cmis:none", false, false, extension); return objectData.Properties; } public IList GetAllVersions(string repositoryId, string objectId, string versionSeriesId, string filter, bool? includeAllowableActions, IExtensionsData extension) { IList result = new List(); // find the link string link = LoadLink(repositoryId, objectId, BindingConstants.RelVersionHistory, BindingConstants.MediaTypeFeed); if (link == null) { ThrowLinkException(repositoryId, objectId, BindingConstants.RelVersionHistory, BindingConstants.MediaTypeFeed); } UrlBuilder url = new UrlBuilder(link); url.AddParameter(BindingConstants.ParamFilter, filter); url.AddParameter(BindingConstants.ParamAllowableActions, includeAllowableActions); // read and parse IResponse resp = Read(url); AtomFeed feed = Parse(resp.Stream); // get the versions if (feed.Entries.Count > 0) { foreach (AtomEntry entry in feed.Entries) { IObjectData version = null; LockLinks(); try { // clean up cache RemoveLinks(repositoryId, entry.Id); // walk through the entry foreach (AtomElement element in entry.Elements) { if (element.Object is AtomLink) { AddLink(repositoryId, entry.Id, (AtomLink)element.Object); } else if (element.Object is IObjectData) { version = (IObjectData)element.Object; } } } finally { UnlockLinks(); } if (version != null) { result.Add(version); } } } return result; } } internal class DiscoveryService : AbstractAtomPubService, IDiscoveryService { public DiscoveryService(BindingSession session) { Session = session; } public IObjectList Query(string repositoryId, string statement, bool? searchAllVersions, bool? includeAllowableActions, IncludeRelationships? includeRelationships, string renditionFilter, BigInteger? maxItems, BigInteger? skipCount, IExtensionsData extension) { ObjectList result = new ObjectList(); // find the link string link = LoadCollection(repositoryId, BindingConstants.CollectionQuery); if (link == null) { throw new CmisObjectNotFoundException("Unknown repository or query not supported!"); } UrlBuilder url = new UrlBuilder(link); // compile query request QueryType query = new QueryType(); query.Statement = statement; query.SearchAllVersions = searchAllVersions; query.IncludeAllowableActions = includeAllowableActions; query.IncludeRelationships = includeRelationships; query.RenditionFilter = renditionFilter; query.MaxItems = maxItems; query.SkipCount = skipCount; CmisVersion cmisVersion = GetCmisVersion(repositoryId); // post the query and parse results IResponse resp = Post(url, new AtomPubHttpContent(BindingConstants.MediaTypeQuery, (stream) => { using (XmlWriter writer = XmlUtils.CreateWriter(stream)) { XmlUtils.StartXmlDocument(writer); XmlConverter.WriteQuery(writer, cmisVersion, query); XmlUtils.EndXmlDocument(writer); } })); AtomFeed feed = Parse(resp.Stream); // handle top level foreach (AtomElement element in feed.Elements) { if (element.Object is AtomLink) { if (IsNextLink(element)) { result.HasMoreItems = true; } } else if (IsInt(NameNumItems, element)) { result.NumItems = (BigInteger)element.Object; } } // get the result set if (feed.Entries.Count > 0) { result.Objects = new List(feed.Entries.Count); foreach (AtomEntry entry in feed.Entries) { IObjectData hit = null; // walk through the entry foreach (AtomElement element in entry.Elements) { if (element.Object is IObjectData) { hit = (IObjectData)element.Object; } } if (hit != null) { result.Objects.Add(hit); } } } return result; } public IObjectList GetContentChanges(string repositoryId, ref string changeLogToken, bool? includeProperties, string filter, bool? includePolicyIds, bool? includeAcl, BigInteger? maxItems, IExtensionsData extension) { ObjectList result = new ObjectList(); // find the link string link = null; UrlBuilder url = null; // if the application didn't provide a link to next Atom feed if (link == null) { link = LoadRepositoryLink(repositoryId, BindingConstants.RepRelChanges); if (link != null) { url = new UrlBuilder(link); url.AddParameter(BindingConstants.ParamChangeLogToken, changeLogToken); url.AddParameter(BindingConstants.ParamProperties, includeProperties); url.AddParameter(BindingConstants.ParamFilter, filter); url.AddParameter(BindingConstants.ParamPolicyIds, includePolicyIds); url.AddParameter(BindingConstants.ParamAcl, includeAcl); url.AddParameter(BindingConstants.ParamMaxItems, maxItems); } } if (link == null) { throw new CmisObjectNotFoundException("Unknown repository or content changes not supported!"); } // read and parse IResponse resp = Read(url); AtomFeed feed = Parse(resp.Stream); string lastChangeLogToken = null; // handle top level string nextLink = null; foreach (AtomElement element in feed.Elements) { if (element.Object is AtomLink) { if (IsNextLink(element)) { result.HasMoreItems = true; nextLink = ((AtomLink)element.Object).Href; } } else if (IsInt(NameNumItems, element)) { result.NumItems = (BigInteger)element.Object; } else if (IsStr("changeLogToken", element)) { lastChangeLogToken = (String)element.Object; } } // get the changes if (feed.Entries.Count > 0) { result.Objects = new List(feed.Entries.Count); foreach (AtomEntry entry in feed.Entries) { IObjectData hit = null; // walk through the entry foreach (AtomElement element in entry.Elements) { if (element.Object is IObjectData) { hit = (IObjectData)element.Object; } } if (hit != null) { result.Objects.Add(hit); } } } changeLogToken = lastChangeLogToken; return result; } } internal class MultiFilingService : AbstractAtomPubService, IMultiFilingService { public MultiFilingService(BindingSession session) { Session = session; } public void AddObjectToFolder(string repositoryId, string objectId, string folderId, bool? allVersions, IExtensionsData extension) { if (objectId == null) { throw new CmisInvalidArgumentException("Object ID must be set!"); } // find the link string link = LoadLink(repositoryId, folderId, BindingConstants.RelDown, BindingConstants.MediaTypeChildren); if (link == null) { ThrowLinkException(repositoryId, folderId, BindingConstants.RelDown, BindingConstants.MediaTypeChildren); } UrlBuilder url = new UrlBuilder(link); url.AddParameter(BindingConstants.ParamAllVersions, allVersions); // set up object and writer AtomEntryWriter entryWriter = new AtomEntryWriter(CreateIdObject(objectId), GetCmisVersion(repositoryId)); // post addObjectToFolder request PostAndConsume(url, new AtomPubHttpContent(BindingConstants.MediaTypeEntry, (stream) => { entryWriter.Write(stream); })); } public void RemoveObjectFromFolder(string repositoryId, string objectId, string folderId, IExtensionsData extension) { if (objectId == null) { throw new CmisInvalidArgumentException("Object ID must be set!"); } // find the link string link = LoadCollection(repositoryId, BindingConstants.CollectionUnfiled); if (link == null) { throw new CmisObjectNotFoundException("Unknown repository or unfiling not supported!"); } UrlBuilder url = new UrlBuilder(link); url.AddParameter(BindingConstants.ParamRemoveFrom, folderId); // set up object and writer AtomEntryWriter entryWriter = new AtomEntryWriter(CreateIdObject(objectId), GetCmisVersion(repositoryId)); // post removeObjectFromFolder request PostAndConsume(url, new AtomPubHttpContent(BindingConstants.MediaTypeEntry, (stream) => { entryWriter.Write(stream); })); } } internal class RelationshipService : AbstractAtomPubService, IRelationshipService { public RelationshipService(BindingSession session) { Session = session; } public IObjectList GetObjectRelationships(string repositoryId, string objectId, bool? includeSubRelationshipTypes, RelationshipDirection? relationshipDirection, string typeId, string filter, bool? includeAllowableActions, BigInteger? maxItems, BigInteger? skipCount, IExtensionsData extension) { ObjectList result = new ObjectList(); // find the link string link = LoadLink(repositoryId, objectId, BindingConstants.RelRelationships, BindingConstants.MediaTypeFeed); if (link == null) { ThrowLinkException(repositoryId, objectId, BindingConstants.RelRelationships, BindingConstants.MediaTypeFeed); } UrlBuilder url = new UrlBuilder(link); url.AddParameter(BindingConstants.ParamSubRelationshipTypes, includeSubRelationshipTypes); url.AddParameter(BindingConstants.ParamRelationshipDirection, relationshipDirection); url.AddParameter(BindingConstants.ParamTypeId, typeId); url.AddParameter(BindingConstants.ParamFilter, filter); url.AddParameter(BindingConstants.ParamAllowableActions, includeAllowableActions); url.AddParameter(BindingConstants.ParamMaxItems, maxItems); url.AddParameter(BindingConstants.ParamSkipCount, skipCount); // read and parse IResponse resp = Read(url); AtomFeed feed = Parse(resp.Stream); // handle top level foreach (AtomElement element in feed.Elements) { if (element.Object is AtomLink) { if (IsNextLink(element)) { result.HasMoreItems = true; } } else if (IsInt(NameNumItems, element)) { result.NumItems = (BigInteger)element.Object; } } // get the children if (feed.Entries.Count > 0) { result.Objects = new List(feed.Entries.Count); foreach (AtomEntry entry in feed.Entries) { IObjectData relationship = null; LockLinks(); try { // clean up cache RemoveLinks(repositoryId, entry.Id); // walk through the entry foreach (AtomElement element in entry.Elements) { if (element.Object is AtomLink) { AddLink(repositoryId, entry.Id, (AtomLink)element.Object); } else if (element.Object is IObjectData) { relationship = (IObjectData)element.Object; } } } finally { UnlockLinks(); } if (relationship != null) { result.Objects.Add(relationship); } } } return result; } } internal class PolicyService : AbstractAtomPubService, IPolicyService { public PolicyService(BindingSession session) { Session = session; } public void ApplyPolicy(string repositoryId, string policyId, string objectId, IExtensionsData extension) { // find the link string link = LoadLink(repositoryId, objectId, BindingConstants.RelPolicies, BindingConstants.MediaTypeFeed); if (link == null) { ThrowLinkException(repositoryId, objectId, BindingConstants.RelPolicies, BindingConstants.MediaTypeFeed); } UrlBuilder url = new UrlBuilder(link); // set up object and writer AtomEntryWriter entryWriter = new AtomEntryWriter(CreateIdObject(policyId), GetCmisVersion(repositoryId)); // post applyPolicy request PostAndConsume(url, new AtomPubHttpContent(BindingConstants.MediaTypeEntry, (stream) => { entryWriter.Write(stream); })); } public void RemovePolicy(string repositoryId, string policyId, string objectId, IExtensionsData extension) { // we need a policy id if (policyId == null) { throw new CmisInvalidArgumentException("Policy id must be set!"); } // find the link string link = LoadLink(repositoryId, objectId, BindingConstants.RelPolicies, BindingConstants.MediaTypeFeed); if (link == null) { ThrowLinkException(repositoryId, objectId, BindingConstants.RelPolicies, BindingConstants.MediaTypeFeed); } UrlBuilder url = new UrlBuilder(link); url.AddParameter(BindingConstants.ParamFilter, PropertyIds.ObjectId); // read and parse IResponse resp = Read(url); AtomFeed feed = Parse(resp.Stream); // find the policy string policyLink = null; bool found = false; if (feed.Entries.Count > 0) { foreach (AtomEntry entry in feed.Entries) { // walk through the entry foreach (AtomElement element in entry.Elements) { if (element.Object is AtomLink) { AtomLink atomLink = (AtomLink)element.Object; if (BindingConstants.RelSelf == atomLink.Rel) { policyLink = atomLink.Href; } } else if (element.Object is IObjectData) { string id = ((IObjectData)element.Object).Id; if (policyId == id) { found = true; } } } if (found) { break; } } } // if found, delete it if (found && policyLink != null) { Delete(new UrlBuilder(policyLink)); } } public IList GetAppliedPolicies(string repositoryId, string objectId, string filter, IExtensionsData extension) { IList result = new List(); // find the link string link = LoadLink(repositoryId, objectId, BindingConstants.RelPolicies, BindingConstants.MediaTypeFeed); if (link == null) { ThrowLinkException(repositoryId, objectId, BindingConstants.RelPolicies, BindingConstants.MediaTypeFeed); } UrlBuilder url = new UrlBuilder(link); url.AddParameter(BindingConstants.ParamFilter, filter); // read and parse IResponse resp = Read(url); AtomFeed feed = Parse(resp.Stream); // get the policies if (feed.Entries.Count > 0) { foreach (AtomEntry entry in feed.Entries) { IObjectData policy = null; // walk through the entry foreach (AtomElement element in entry.Elements) { if (element.Object is IObjectData) { policy = (IObjectData)element.Object; } } if (policy != null) { result.Add(policy); } } } return result; } } internal class AclService : AbstractAtomPubService, IAclService { public AclService(BindingSession session) { Session = session; } public IAcl GetAcl(string repositoryId, string objectId, bool? onlyBasicPermissions, IExtensionsData extension) { return GetAclInternal(repositoryId, objectId, onlyBasicPermissions, extension); } public IAcl ApplyAcl(string repositoryId, string objectId, IAcl addAces, IAcl removeAces, AclPropagation? aclPropagation, IExtensionsData extension) { // fetch the current ACL IAcl originalAces = GetAcl(repositoryId, objectId, false, null); // if no changes required, just return the ACL if (!IsAclMergeRequired(addAces, removeAces)) { return originalAces; } // merge ACLs IAcl newACL = MergeAcls(originalAces, addAces, removeAces); // update ACL AtomAcl acl = UpdateAcl(repositoryId, objectId, newACL, aclPropagation); IAcl result = acl.Acl; return result; } } }