/* | |
* 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 | |
{ | |
/// <summary> | |
/// Browser binding SPI. | |
/// </summary> | |
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 | |
} | |
} | |
/// <summary> | |
/// Common service data and operations. | |
/// </summary> | |
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; | |
} | |
} | |
/// <summary> | |
/// Returns the service document URL of this session. | |
/// </summary> | |
protected string GetServiceDocURL() | |
{ | |
return Session.GetValue(SessionParameter.AtomPubUrl) as string; | |
} | |
/// <summary> | |
/// Return the CMIS version of the given repository. | |
/// </summary> | |
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<IRepositoryInfo> 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 ---- | |
/// <summary> | |
/// Returns the link cache or creates a new cache if it doesn't exist. | |
/// </summary> | |
protected LinkCache GetLinkCache() | |
{ | |
LinkCache linkCache = (LinkCache)Session.GetValue(CmisAtomPubSpi.SessionLinkCache); | |
if (linkCache == null) | |
{ | |
linkCache = new LinkCache(Session); | |
Session.PutValue(CmisAtomPubSpi.SessionLinkCache, linkCache); | |
} | |
return linkCache; | |
} | |
/// <summary> | |
/// Gets a link from the cache. | |
/// </summary> | |
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); | |
} | |
/// <summary> | |
/// Gets a link from the cache. | |
/// </summary> | |
protected string GetLink(string repositoryId, string id, string rel) | |
{ | |
return GetLink(repositoryId, id, rel, null); | |
} | |
/// <summary> | |
/// Gets a link from the cache if it is there or Loads it into the cache if | |
/// it is not there. | |
/// </summary> | |
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; | |
} | |
/// <summary> | |
/// Gets the content link from the cache if it is there or Loads it into the | |
/// cache if it is not there. | |
/// </summary> | |
public string LoadContentLink(string repositoryId, string id) | |
{ | |
return LoadLink(repositoryId, id, AtomPubParser.LinkRelContent, null); | |
} | |
/// <summary> | |
/// Gets a rendition content link from the cache if it is there or Loads it | |
/// into the cache if it is not there. | |
/// </summary> | |
public string LoadRenditionContentLink(string repositoryId, string id, string streamId) | |
{ | |
return LoadLink(repositoryId, id, BindingConstants.RelAlternate, streamId); | |
} | |
/// <summary> | |
/// Adds a link to the cache. | |
/// </summary> | |
protected void AddLink(string repositoryId, string id, string rel, string type, string link) | |
{ | |
GetLinkCache().AddLink(repositoryId, id, rel, type, link); | |
} | |
/// <summary> | |
/// Adds a link to the cache. | |
/// </summary> | |
protected void AddLink(string repositoryId, string id, AtomLink link) | |
{ | |
GetLinkCache().AddLink(repositoryId, id, link.Rel, link.Type, link.Href); | |
} | |
/// <summary> | |
/// Removes all links of an object. | |
/// </summary> | |
protected void RemoveLinks(string repositoryId, string id) | |
{ | |
GetLinkCache().RemoveLinks(repositoryId, id); | |
} | |
/// <summary> | |
/// Locks the link cache. | |
/// </summary> | |
protected void LockLinks() | |
{ | |
GetLinkCache().LockLinks(); | |
} | |
/// <summary> | |
/// Unlocks the link cache. | |
/// </summary> | |
protected void UnlockLinks() | |
{ | |
GetLinkCache().UnlockLinks(); | |
} | |
/// <summary> | |
/// Checks a link throw an appropriate exception. | |
/// </summary> | |
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!"); | |
} | |
} | |
/// <summary> | |
/// Gets a type link from the cache. | |
/// </summary> | |
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); | |
} | |
/// <summary> | |
/// Gets a type link from the cache. | |
/// </summary> | |
protected string GetTypeLink(string repositoryId, string typeId, string rel) | |
{ | |
return GetTypeLink(repositoryId, typeId, rel, null); | |
} | |
/// <summary> | |
/// Gets a link from the cache if it is there or Loads it into the cache if | |
/// it is not there. | |
/// </summary> | |
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; | |
} | |
/// <summary> | |
/// Adds a type link to the cache. | |
/// </summary> | |
protected void AddTypeLink(string repositoryId, string typeId, string rel, string type, string link) | |
{ | |
GetLinkCache().AddTypeLink(repositoryId, typeId, rel, type, link); | |
} | |
/// <summary> | |
/// Adds a type link to the cache. | |
/// </summary> | |
protected void AddTypeLink(string repositoryId, string typeId, AtomLink link) | |
{ | |
GetLinkCache().AddTypeLink(repositoryId, typeId, link.Rel, link.Type, link.Href); | |
} | |
/// <summary> | |
/// Removes all links of a type. | |
/// </summary> | |
protected void RemoveTypeLinks(string repositoryId, string id) | |
{ | |
GetLinkCache().RemoveTypeLinks(repositoryId, id); | |
} | |
/// <summary> | |
/// Locks the type link cache. | |
/// </summary> | |
protected void LockTypeLinks() | |
{ | |
GetLinkCache().LockTypeLinks(); | |
} | |
/// <summary> | |
/// Unlocks the type link cache. | |
/// </summary> | |
protected void UnlockTypeLinks() | |
{ | |
GetLinkCache().UnlockTypeLinks(); | |
} | |
/// <summary> | |
/// Gets a collection from the cache. | |
/// </summary> | |
protected string GetCollection(string repositoryId, string collection) | |
{ | |
return GetLinkCache().GetCollection(repositoryId, collection); | |
} | |
/// <summary> | |
/// Gets a collection from the cache if it is there or Loads it into the | |
/// cache if it is not there. | |
/// </summary> | |
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; | |
} | |
/// <summary> | |
/// Adds a collection to the cache. | |
/// </summary> | |
protected void AddCollection(string repositoryId, string collection, string link) | |
{ | |
GetLinkCache().AddCollection(repositoryId, collection, link); | |
} | |
/// <summary> | |
/// Gets a repository link from the cache. | |
/// </summary> | |
protected string GetRepositoryLink(string repositoryId, string rel) | |
{ | |
return GetLinkCache().GetRepositoryLink(repositoryId, rel); | |
} | |
/// <summary> | |
/// Gets a repository link from the cache if it is there or Loads it into the | |
/// cache if it is not there. | |
/// </summary> | |
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; | |
} | |
/// <summary> | |
/// Adds a repository link to the cache. | |
/// </summary> | |
protected void AddRepositoryLink(string repositoryId, string rel, string link) | |
{ | |
GetLinkCache().AddRepositoryLink(repositoryId, rel, link); | |
} | |
/// <summary> | |
/// Adds a repository link to the cache. | |
/// </summary> | |
protected void AddRepositoryLink(string repositoryId, AtomLink link) | |
{ | |
AddRepositoryLink(repositoryId, link.Rel, link.Href); | |
} | |
/// <summary> | |
/// Gets an URI template from the cache. | |
/// </summary> | |
protected string GetTemplateLink(string repositoryId, string type, Dictionary<string, object> parameters) | |
{ | |
return GetLinkCache().GetTemplateLink(repositoryId, type, parameters); | |
} | |
/// <summary> | |
/// Gets a template link from the cache if it is there or Loads it into the | |
/// cache if it is not there. | |
/// </summary> | |
protected string LoadTemplateLink(string repositoryId, string type, Dictionary<string, object> parameters) | |
{ | |
string link = GetTemplateLink(repositoryId, type, parameters); | |
if (link == null) | |
{ | |
// cache repository info | |
GetRepositoriesInternal(repositoryId); | |
link = GetTemplateLink(repositoryId, type, parameters); | |
} | |
return link; | |
} | |
/// <summary> | |
/// Adds an URI template to the cache. | |
/// </summary> | |
protected void AddTemplate(string repositoryId, string type, string link) | |
{ | |
GetLinkCache().AddTemplate(repositoryId, type, link); | |
} | |
// ---- exceptions ---- | |
/// <summary> | |
/// Converts a HTTP status code into an Exception. | |
/// </summary> | |
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("<!--exception-->"); | |
int end = errorContent.IndexOf("<!--/exception-->"); | |
if (begin == -1 || end == -1 || begin > end) | |
{ | |
return null; | |
} | |
return errorContent.Substring(begin + "<!--exception-->".Length, end); | |
} | |
protected string ExtractErrorMessage(string message, string errorContent) | |
{ | |
if (errorContent == null) | |
{ | |
return message; | |
} | |
int begin = errorContent.IndexOf("<!--message-->"); | |
int end = errorContent.IndexOf("<!--/message-->"); | |
if (begin == -1 || end == -1 || begin > end) | |
{ | |
return message; | |
} | |
return errorContent.Substring(begin + "<!--message-->".Length, end); | |
} | |
// ---- 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; | |
} | |
/// <summary> | |
/// Creates a CMIS object with properties and policy IDs. | |
/// </summary> | |
protected ObjectData CreateObject(IProperties properties, string changeToken, IList<string> 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; | |
} | |
/// <summary> | |
/// Creates a CMIS object that only contains an ID in the property list. | |
/// </summary> | |
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; | |
} | |
/// <summary> | |
/// Parses an input stream. | |
/// </summary> | |
protected T Parse<T>(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; | |
} | |
/// <summary> | |
/// Performs a GET on an URL, checks the response code and returns the | |
/// result. | |
/// </summary> | |
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; | |
} | |
/// <summary> | |
/// Performs a POST on an URL, checks the response code and returns the | |
/// result. | |
/// </summary> | |
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; | |
} | |
/// <summary> | |
/// Performs a POST on an URL, checks the response code and consumes the | |
/// response. | |
/// </summary> | |
protected void PostAndConsume(UrlBuilder url, HttpContent content) | |
{ | |
IResponse resp = Post(url, content); | |
IOUtils.ConsumeAndClose(resp.Stream); | |
} | |
/// <summary> | |
/// Performs a PUT on an URL, checks the response code and returns the | |
/// result. | |
/// </summary> | |
protected IResponse Put(UrlBuilder url, HttpContent content) | |
{ | |
return Put(url, null, content); | |
} | |
/// <summary> | |
/// Performs a PUT on an URL, checks the response code and returns the | |
/// result. | |
/// </summary> | |
protected IResponse Put(UrlBuilder url, IDictionary<string, string> 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; | |
} | |
/// <summary> | |
/// Performs a DELETE on an URL, checks the response code and returns the | |
/// result. | |
/// </summary> | |
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 ---- | |
/// <summary> | |
/// Checks if at least one ACE list is not empty. | |
/// </summary> | |
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); | |
} | |
/// <summary> | |
/// Merges the new ACL from original, add and remove ACEs lists. | |
/// </summary> | |
protected IAcl MergeAcls(IAcl originalAces, IAcl addAces, IAcl removeAces) | |
{ | |
Dictionary<string, HashSet<string>> originals = ConvertAclToMap(originalAces); | |
Dictionary<string, HashSet<string>> adds = ConvertAclToMap(addAces); | |
Dictionary<string, HashSet<string>> removes = ConvertAclToMap(removeAces); | |
IList<IAce> newAces = new List<IAce>(); | |
// iterate through the original ACEs | |
foreach (KeyValuePair<string, HashSet<string>> ace in originals) | |
{ | |
// add permissions | |
HashSet<string> addPermissions; | |
if (adds.TryGetValue(ace.Key, out addPermissions)) | |
{ | |
foreach (string perm in addPermissions) | |
{ | |
ace.Value.Add(perm); | |
} | |
} | |
// remove permissions | |
HashSet<string> 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<string, HashSet<string>> 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 }; | |
} | |
/// <summary> | |
/// Converts a list of ACEs into Map for better handling. | |
/// </summary> | |
private static Dictionary<string, HashSet<string>> ConvertAclToMap(IAcl acl) | |
{ | |
Dictionary<string, HashSet<string>> result = new Dictionary<string, HashSet<string>>(); | |
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<string> permissions; | |
if (!result.TryGetValue(ace.Principal.Id, out permissions)) | |
{ | |
permissions = new HashSet<string>(); | |
result[ace.Principal.Id] = permissions; | |
} | |
if (ace.Permissions != null) | |
{ | |
foreach (string perm in ace.Permissions) | |
{ | |
permissions.Add(perm); | |
} | |
} | |
} | |
return result; | |
} | |
/// <summary> | |
/// Retrieves the Service Document from the server and caches the repository | |
/// info objects, collections, links, URI templates, etc. | |
/// </summary> | |
protected IList<IRepositoryInfo> GetRepositoriesInternal(string repositoryId) | |
{ | |
List<IRepositoryInfo> repInfos = new List<IRepositoryInfo>(); | |
// retrieve service doc | |
UrlBuilder url = new UrlBuilder(GetServiceDocURL()); | |
url.AddParameter(BindingConstants.ParamRepositoryId, repositoryId); | |
// read and parse | |
IResponse resp = Read(url); | |
ServiceDoc serviceDoc = Parse<ServiceDoc>(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<string, string> colMap = (Dictionary<string, string>)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<string, string> tempMap = (Dictionary<string, string>)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; | |
} | |
/// <summary> | |
/// Retrieves an object from the server and caches the links. | |
/// </summary> | |
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<string, object> parameters = new Dictionary<string, object>(); | |
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<AtomEntry>(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; | |
} | |
/// <summary> | |
/// Retrieves a type definition. | |
/// </summary> | |
protected ITypeDefinition GetTypeDefinitionInternal(string repositoryId, string typeId) | |
{ | |
Dictionary<string, object> parameters = new Dictionary<string, object>(); | |
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<AtomEntry>(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; | |
} | |
/// <summary> | |
/// Retrieves the ACL of an object. | |
/// </summary> | |
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<AtomAcl>(resp.Stream); | |
return acl.Acl; | |
} | |
/// <summary> | |
/// Updates the ACL of an object. | |
/// </summary> | |
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<AtomAcl>(resp.Stream); | |
} | |
} | |
internal class RepositoryService : AbstractAtomPubService, IRepositoryService | |
{ | |
public RepositoryService(BindingSession session) | |
{ | |
Session = session; | |
} | |
public IList<IRepositoryInfo> GetRepositoryInfos(IExtensionsData extension) | |
{ | |
return GetRepositoriesInternal(null); | |
} | |
public IRepositoryInfo GetRepositoryInfo(string repositoryId, IExtensionsData extension) | |
{ | |
IList<IRepositoryInfo> 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<AtomFeed>(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<ITypeDefinition>(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<ITypeDefinitionContainer> GetTypeDescendants(string repositoryId, string typeId, BigInteger? depth, | |
bool? includePropertyDefinitions, IExtensionsData extension) | |
{ | |
IList<ITypeDefinitionContainer> result = new List<ITypeDefinitionContainer>(); | |
// 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<AtomFeed>(resp.Stream); | |
// process tree | |
AddTypeDescendantsLevel(repositoryId, feed, result); | |
return result; | |
} | |
/// <summary> | |
/// Adds type descendants level recursively. | |
/// </summary> | |
private void AddTypeDescendantsLevel(string repositoryId, AtomFeed feed, IList<ITypeDefinitionContainer> containerList) | |
{ | |
if (feed == null || feed.Entries.Count == 0) | |
{ | |
return; | |
} | |
// walk through the feed | |
foreach (AtomEntry entry in feed.Entries) | |
{ | |
TypeDefinitionContainer childContainer = null; | |
IList<ITypeDefinitionContainer> childContainerList = new List<ITypeDefinitionContainer>(); | |
// 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<AtomEntry>(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<string, object> parameters = new Dictionary<string, object>(); | |
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<AtomEntry>(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<string, object> parameters = new Dictionary<string, object>(); | |
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<AtomFeed>(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<IObjectInFolderData>(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<IObjectInFolderContainer> GetDescendants(string repositoryId, string folderId, BigInteger? depth, string filter, | |
bool? includeAllowableActions, IncludeRelationships? includeRelationships, string renditionFilter, | |
bool? includePathSegment, IExtensionsData extension) | |
{ | |
IList<IObjectInFolderContainer> result = new List<IObjectInFolderContainer>(); | |
// 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<AtomFeed>(resp.Stream); | |
// process tree | |
AddDescendantsLevel(repositoryId, feed, result); | |
return result; | |
} | |
public IList<IObjectInFolderContainer> GetFolderTree(string repositoryId, string folderId, BigInteger? depth, string filter, | |
bool? includeAllowableActions, IncludeRelationships? includeRelationships, string renditionFilter, | |
bool? includePathSegment, IExtensionsData extension) | |
{ | |
IList<IObjectInFolderContainer> result = new List<IObjectInFolderContainer>(); | |
// 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<AtomFeed>(resp.Stream); | |
// process tree | |
AddDescendantsLevel(repositoryId, feed, result); | |
return result; | |
} | |
/// <summary> | |
/// Adds descendants level recursively. | |
/// </summary> | |
private void AddDescendantsLevel(string repositoryId, AtomFeed feed, IList<IObjectInFolderContainer> 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<IObjectInFolderContainer> childContainerList = new List<IObjectInFolderContainer>(); | |
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<IObjectParentData> GetObjectParents(string repositoryId, string objectId, string filter, | |
bool? includeAllowableActions, IncludeRelationships? includeRelationships, string renditionFilter, | |
bool? includeRelativePathSegment, IExtensionsData extension) | |
{ | |
IList<IObjectParentData> result = new List<IObjectParentData>(); | |
// 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<AtomBase>(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<AtomBase>(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<AtomFeed>(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<IObjectData>(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<string> 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<AtomEntry>(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<string> 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<string> 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<AtomEntry>(resp.Stream); | |
// handle ACL modifications | |
HandleAclModifications(repositoryId, entry, addAces, removeAces); | |
return entry.Id; | |
} | |
public string CreateRelationship(string repositoryId, IProperties properties, IList<string> 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<AtomEntry>(resp.Stream); | |
// handle ACL modifications | |
HandleAclModifications(repositoryId, entry, addAces, removeAces); | |
return entry.Id; | |
} | |
public string CreatePolicy(string repositoryId, IProperties properties, string folderId, IList<string> 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<AtomEntry>(resp.Stream); | |
// handle ACL modifications | |
HandleAclModifications(repositoryId, entry, addAces, removeAces); | |
return entry.Id; | |
} | |
public string CreateItem(string repositoryId, IProperties properties, string folderId, IList<string> 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<AtomEntry>(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<AtomAllowableActions>(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<IRenditionData> 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<IRenditionData> result = objectData.Renditions; | |
if (result == null) | |
{ | |
result = new List<IRenditionData>(); | |
} | |
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<AtomEntry>(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<IBulkUpdateObjectIdAndChangeToken> BulkUpdateProperties(string repositoryId, | |
IList<IBulkUpdateObjectIdAndChangeToken> objectIdAndChangeToken, IProperties properties, | |
IList<string> addSecondaryTypeIds, IList<string> 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<AtomFeed>(resp.Stream); | |
List<IBulkUpdateObjectIdAndChangeToken> result = new List<IBulkUpdateObjectIdAndChangeToken>(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<AtomEntry>(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<AtomFeed>(resp.Stream); | |
// prepare result | |
FailedToDeleteData result = new FailedToDeleteData(); | |
IList<string> ids = new List<string>(); | |
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); | |
} | |
/// <summary> | |
/// Sets or appends content. | |
/// </summary> | |
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<string, string> headers = null; | |
if (contentStream.FileName != null) | |
{ | |
headers = new Dictionary<string, string>(); | |
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<AtomEntry>(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<string> 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<AtomEntry>(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<IObjectData> GetAllVersions(string repositoryId, string objectId, string versionSeriesId, string filter, | |
bool? includeAllowableActions, IExtensionsData extension) | |
{ | |
IList<IObjectData> result = new List<IObjectData>(); | |
// 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<AtomFeed>(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<AtomFeed>(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<IObjectData>(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<AtomFeed>(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<IObjectData>(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<AtomFeed>(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<IObjectData>(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<AtomFeed>(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<IObjectData> GetAppliedPolicies(string repositoryId, string objectId, string filter, IExtensionsData extension) | |
{ | |
IList<IObjectData> result = new List<IObjectData>(); | |
// 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<AtomFeed>(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; | |
} | |
} | |
} |