blob: 50c17442cc09baf66f868c45261ffc113277da03 [file] [log] [blame]
/*
* 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.Impl;
using PortCMIS.Client;
using PortCMIS.Data;
using PortCMIS.Enums;
using PortCMIS.Exceptions;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Xml;
namespace PortCMIS.Binding.AtomPub
{
internal class AtomPubParser
{
public const string LinkRelContent = "@@content@@";
private Stream stream;
private AtomBase parseResult;
public AtomPubParser(Stream stream)
{
if (stream == null)
{
throw new ArgumentNullException("stream");
}
this.stream = stream;
}
/// <summary>
/// Parses the stream.
/// </summary>
public void Parse()
{
XmlReader parser = XmlUtils.CreateParser(stream);
try
{
while (true)
{
XmlNodeType nodeType = parser.NodeType;
if (nodeType == XmlNodeType.Element)
{
string uri = parser.NamespaceURI;
string local = parser.LocalName;
if (uri == XmlConstants.NAMESPACE_ATOM)
{
if (local == XmlConstants.TAG_FEED)
{
parseResult = ParseFeed(parser);
break;
}
else if (local == XmlConstants.TAG_ENTRY)
{
parseResult = ParseEntry(parser);
break;
}
}
else if (uri == XmlConstants.NAMESPACE_CMIS)
{
if (local == XmlConstants.TAG_ALLOWABLEACTIONS)
{
parseResult = ParseAllowableActions(parser);
break;
}
else if (local == XmlConstants.TAG_ACL)
{
parseResult = ParseACL(parser);
break;
}
}
else if (uri == XmlConstants.NAMESPACE_APP)
{
if (local == XmlConstants.TAG_SERVICE)
{
parseResult = ParseServiceDoc(parser);
break;
}
}
else if (string.Equals(XmlConstants.TAG_HTML, local, StringComparison.OrdinalIgnoreCase))
{
parseResult = new HtmlDoc();
break;
}
}
if (!XmlUtils.Next(parser))
{
break;
}
}
}
finally
{
parser.Dispose();
// make sure the stream is read and closed in all cases
IOUtils.ConsumeAndClose(stream);
}
}
/// <summary>
/// Return the parse results.
/// </summary>
public AtomBase GetResults()
{
return parseResult;
}
/// <summary>
/// Parses a service document.
/// </summary>
private static ServiceDoc ParseServiceDoc(XmlReader parser)
{
ServiceDoc result = new ServiceDoc();
XmlUtils.Next(parser);
while (true)
{
XmlNodeType nodeType = parser.NodeType;
if (nodeType == XmlNodeType.Element)
{
string uri = parser.NamespaceURI;
string local = parser.LocalName;
if (uri == XmlConstants.NAMESPACE_APP)
{
if (local == XmlConstants.TAG_WORKSPACE)
{
result.AddWorkspace(ParseWorkspace(parser));
}
else
{
XmlUtils.Skip(parser);
}
}
else
{
XmlUtils.Skip(parser);
}
}
else if (nodeType == XmlNodeType.EndElement)
{
break;
}
else
{
if (!XmlUtils.Next(parser))
{
break;
}
}
}
return result;
}
/// <summary>
/// Parses a workspace element in a service document.
/// </summary>
private static RepositoryWorkspace ParseWorkspace(XmlReader parser)
{
RepositoryWorkspace workspace = new RepositoryWorkspace();
XmlUtils.Next(parser);
while (true)
{
XmlNodeType nodeType = parser.NodeType;
if (nodeType == XmlNodeType.Element)
{
AtomElement element = ParseWorkspaceElement(parser);
// check if we can extract the workspace id
if (element != null && (element.Object is IRepositoryInfo))
{
workspace.Id = ((IRepositoryInfo)element.Object).Id;
}
// add to workspace
workspace.AddElement(element);
}
else if (nodeType == XmlNodeType.EndElement)
{
break;
}
else
{
if (!XmlUtils.Next(parser))
{
break;
}
}
}
XmlUtils.Next(parser);
return workspace;
}
/// <summary>
/// Parses an Atom feed.
/// </summary>
private AtomFeed ParseFeed(XmlReader parser)
{
AtomFeed result = new AtomFeed();
XmlUtils.Next(parser);
while (true)
{
XmlNodeType nodeType = parser.NodeType;
if (nodeType == XmlNodeType.Element)
{
string uri = parser.NamespaceURI;
string local = parser.LocalName;
if (uri == XmlConstants.NAMESPACE_ATOM)
{
if (local == XmlConstants.TAG_LINK)
{
result.AddElement(ParseLink(parser));
}
else if (local == XmlConstants.TAG_ENTRY)
{
result.AddEntry(ParseEntry(parser));
}
else
{
XmlUtils.Skip(parser);
}
}
else if (uri == XmlConstants.NAMESPACE_RESTATOM)
{
if (local == XmlConstants.TAG_NUM_ITEMS)
{
result.AddElement(ParseBigInteger(parser));
}
else
{
XmlUtils.Skip(parser);
}
}
else if (uri == XmlConstants.NAMESPACE_APACHE_CHEMISTRY)
{
result.AddElement(ParseText(parser));
}
else
{
XmlUtils.Skip(parser);
}
}
else if (nodeType == XmlNodeType.EndElement)
{
break;
}
else
{
if (!XmlUtils.Next(parser))
{
break;
}
}
}
XmlUtils.Next(parser);
return result;
}
/// <summary>
/// Parses an Atom entry.
/// </summary>
private AtomEntry ParseEntry(XmlReader parser)
{
AtomEntry result = new AtomEntry();
XmlUtils.Next(parser);
// walk through all tags in entry
while (true)
{
XmlNodeType nodeType = parser.NodeType;
if (nodeType == XmlNodeType.Element)
{
AtomElement element = ParseElement(parser);
if (element != null)
{
// add to entry
result.AddElement(element);
// find and set object id
if (element.Object is IObjectData)
{
result.Id = ((IObjectData)element.Object).Id;
}
else if (element.Object is ITypeDefinition)
{
result.Id = ((ITypeDefinition)element.Object).Id;
}
}
}
else if (nodeType == XmlNodeType.EndElement)
{
break;
}
else
{
if (!XmlUtils.Next(parser))
{
break;
}
}
}
XmlUtils.Next(parser);
return result;
}
/// <summary>
/// Parses an Allowable Actions document.
/// </summary>
private static AtomAllowableActions ParseAllowableActions(XmlReader parser)
{
return new AtomAllowableActions() { AllowableActions = XmlConverter.convertAllowableActions(parser) };
}
/// <summary>
/// Parses an ACL document.
/// </summary>
private static AtomAcl ParseACL(XmlReader parser)
{
return new AtomAcl() { Acl = XmlConverter.convertAcl(parser) };
}
/// <summary>
/// Parses an element.
/// </summary>
private AtomElement ParseElement(XmlReader parser)
{
string uri = parser.NamespaceURI;
string local = parser.LocalName;
if (uri == XmlConstants.NAMESPACE_RESTATOM)
{
if (local == XmlConstants.TAG_OBJECT)
{
return new AtomElement(uri, local, XmlConverter.convertObject(parser));
}
else if (local == XmlConstants.TAG_PATH_SEGMENT
|| local == XmlConstants.TAG_RELATIVE_PATH_SEGMENT)
{
return ParseText(parser);
}
else if (local == XmlConstants.TAG_TYPE)
{
return new AtomElement(uri, local, XmlConverter.convertTypeDefinition(parser));
}
else if (local == XmlConstants.TAG_CHILDREN)
{
return ParseChildren(parser);
}
}
else if (uri == XmlConstants.NAMESPACE_ATOM)
{
if (local == XmlConstants.TAG_LINK)
{
return ParseLink(parser);
}
else if (local == XmlConstants.TAG_CONTENT)
{
return ParseAtomContentSrc(parser);
}
}
// we don't know it - skip it
XmlUtils.Skip(parser);
return null;
}
/// <summary>
/// Parses a children element.
/// </summary>
private AtomElement ParseChildren(XmlReader parser)
{
AtomElement result = null;
string uri = parser.NamespaceURI;
string local = parser.LocalName;
XmlUtils.Next(parser);
// walk through the children tag
while (true)
{
XmlNodeType nodeType = parser.NodeType;
if (nodeType == XmlNodeType.Element)
{
string tagUri = parser.NamespaceURI;
string tagLocal = parser.LocalName;
if (tagUri == XmlConstants.NAMESPACE_ATOM)
{
if (tagLocal == XmlConstants.TAG_FEED)
{
result = new AtomElement(uri, local, ParseFeed(parser));
}
else
{
XmlUtils.Skip(parser);
}
}
else
{
XmlUtils.Skip(parser);
}
}
else if (nodeType == XmlNodeType.EndElement)
{
break;
}
else
{
if (!XmlUtils.Next(parser))
{
break;
}
}
}
XmlUtils.Next(parser);
return result;
}
/// <summary>
/// Parses a workspace element.
/// </summary>
private static AtomElement ParseWorkspaceElement(XmlReader parser)
{
string uri = parser.NamespaceURI;
string local = parser.LocalName;
if (uri == XmlConstants.NAMESPACE_RESTATOM)
{
if (local == XmlConstants.TAG_REPOSITORY_INFO)
{
return new AtomElement(uri, local, XmlConverter.convertRepositoryInfo(parser));
}
else if (local == XmlConstants.TAG_URI_TEMPLATE)
{
return ParseTemplate(parser);
}
}
else if (uri == XmlConstants.NAMESPACE_ATOM)
{
if (local == XmlConstants.TAG_LINK)
{
return ParseLink(parser);
}
}
else if (uri == XmlConstants.NAMESPACE_APP)
{
if (local == XmlConstants.TAG_COLLECTION)
{
return ParseCollection(parser);
}
}
// we don't know it - skip it
XmlUtils.Skip(parser);
return null;
}
/// <summary>
/// Parses a collection tag.
/// </summary>
private static AtomElement ParseCollection(XmlReader parser)
{
string uri = parser.NamespaceURI;
string local = parser.LocalName;
Dictionary<string, string> result = new Dictionary<string, string>();
result["href"] = parser.GetAttribute("href");
XmlUtils.Next(parser);
while (true)
{
XmlNodeType nodeType = parser.NodeType;
if (nodeType == XmlNodeType.Element)
{
string tagUri = parser.NamespaceURI;
string tagLocal = parser.LocalName;
if (tagUri == XmlConstants.NAMESPACE_RESTATOM
&& tagLocal == XmlConstants.TAG_COLLECTION_TYPE)
{
result["collectionType"] = XmlUtils.ReadText(parser, XmlConstraints.MaxStringLength);
}
else
{
XmlUtils.Skip(parser);
}
}
else if (nodeType == XmlNodeType.EndElement)
{
break;
}
else
{
if (!XmlUtils.Next(parser))
{
break;
}
}
}
XmlUtils.Next(parser);
return new AtomElement(uri, local, result);
}
/// <summary>
/// Parses a template tag.
/// </summary>
private static AtomElement ParseTemplate(XmlReader parser)
{
string uri = parser.NamespaceURI;
string local = parser.LocalName;
Dictionary<string, string> result = new Dictionary<string, string>();
XmlUtils.Next(parser);
while (true)
{
XmlNodeType nodeType = parser.NodeType;
if (nodeType == XmlNodeType.Element)
{
string tagUri = parser.NamespaceURI;
string tagLocal = parser.LocalName;
if (tagUri == XmlConstants.NAMESPACE_RESTATOM)
{
if (tagLocal == XmlConstants.TAG_TEMPLATE_TEMPLATE)
{
result["template"] = XmlUtils.ReadText(parser, XmlConstraints.MaxStringLength);
}
else if (tagLocal == XmlConstants.TAG_TEMPLATE_TYPE)
{
result["type"] = XmlUtils.ReadText(parser, XmlConstraints.MaxStringLength);
}
else
{
XmlUtils.Skip(parser);
}
}
else
{
XmlUtils.Skip(parser);
}
}
else if (nodeType == XmlNodeType.EndElement)
{
break;
}
else
{
if (!XmlUtils.Next(parser))
{
break;
}
}
}
XmlUtils.Next(parser);
return new AtomElement(uri, local, result);
}
/// <summary>
/// Parses a link tag.
/// </summary>
private static AtomElement ParseLink(XmlReader parser)
{
string uri = parser.NamespaceURI;
string local = parser.LocalName;
AtomLink result = new AtomLink();
// save attributes
if (parser.HasAttributes)
{
for (int i = 0; i < parser.AttributeCount; i++)
{
parser.MoveToAttribute(i);
if (parser.Name == XmlConstants.LINK_REL)
{
result.Rel = parser.Value;
}
else if (parser.Name == XmlConstants.LINK_HREF)
{
result.Href = parser.Value;
}
else if (parser.Name == XmlConstants.LINK_TYPE)
{
result.Type = parser.Value;
}
}
}
// skip enclosed tags, if any
XmlUtils.Skip(parser);
return new AtomElement(uri, local, result);
}
/// <summary>
/// Parses a link tag.
/// </summary>
private static AtomElement ParseAtomContentSrc(XmlReader parser)
{
string uri = parser.NamespaceURI;
string local = parser.LocalName;
AtomLink result = new AtomLink();
result.Rel = LinkRelContent;
// save attributes
if (parser.HasAttributes)
{
for (int i = 0; i < parser.AttributeCount; i++)
{
parser.MoveToAttribute(i);
if (parser.Name == XmlConstants.CONTENT_SRC)
{
result.Href = parser.Value;
}
}
}
// skip enclosed tags, if any
XmlUtils.Skip(parser);
return new AtomElement(uri, local, result);
}
/// <summary>
/// Parses a text tag.
/// </summary>
private static AtomElement ParseText(XmlReader parser)
{
return new AtomElement(parser.NamespaceURI, parser.LocalName, XmlUtils.ReadText(parser, XmlConstraints.MaxStringLength));
}
/// <summary>
/// Parses a text tag and convert it into an integer.
/// </summary>
private static AtomElement ParseBigInteger(XmlReader parser)
{
return new AtomElement(parser.NamespaceURI, parser.LocalName,
BigInteger.Parse(XmlUtils.ReadText(parser, XmlConstraints.MaxStringLength), NumberStyles.Integer, CultureInfo.InvariantCulture));
}
}
internal class AtomElement
{
public string Namespace { get; private set; }
public string LocalName { get; private set; }
public object Object { get; private set; }
public AtomElement(string elementNamespace, string elementLocalName, object elementObject)
{
Namespace = elementNamespace;
LocalName = elementLocalName;
Object = elementObject;
}
public override string ToString()
{
return "{" + Namespace + "}" + LocalName + ": " + Object;
}
}
internal abstract class AtomBase
{
private IList<AtomElement> elements = new List<AtomElement>();
public abstract string Type { get; }
public IList<AtomElement> Elements { get { return elements; } }
public void AddElement(AtomElement element)
{
if (element != null)
{
elements.Add(element);
}
}
}
internal class RepositoryWorkspace : AtomBase
{
public string Id { get; set; }
public override string Type { get { return "Repository Workspace"; } }
public override string ToString()
{
return "Workspace \"" + Id + "\": " + Elements;
}
}
internal class ServiceDoc : AtomBase
{
private IList<RepositoryWorkspace> workspaces = new List<RepositoryWorkspace>();
public override string Type { get { return "Service Document"; } }
public IList<RepositoryWorkspace> Workspaces { get { return workspaces; } }
public void AddWorkspace(RepositoryWorkspace ws)
{
if (ws != null)
{
workspaces.Add(ws);
}
}
public override string ToString()
{
return "Service Doc: " + workspaces;
}
}
internal class AtomEntry : AtomBase
{
public string Id { get; set; }
public override string Type { get { return "Atom Entry"; } }
public override string ToString()
{
return "Entry \"" + Id + "\": " + Elements;
}
}
internal class AtomFeed : AtomBase
{
private IList<AtomEntry> entries = new List<AtomEntry>();
public override string Type { get { return "Atom Feed"; } }
public IList<AtomEntry> Entries { get { return entries; } }
public void AddEntry(AtomEntry entry)
{
if (entry != null)
{
entries.Add(entry);
}
}
public override string ToString()
{
return "Feed: " + Elements;
}
}
internal class AtomAllowableActions : AtomBase
{
public IAllowableActions AllowableActions { get; set; }
public override string Type { get { return "Allowable Actions"; } }
}
internal class AtomAcl : AtomBase
{
public IAcl Acl { get; set; }
public override string Type { get { return "ACL"; } }
}
internal class HtmlDoc : AtomBase
{
public override string Type { get { return "HTML document"; } }
}
internal class AtomLink
{
public string Rel { get; set; }
public string Type { get; set; }
public string Href { get; set; }
}
internal class LinkCache
{
private static readonly HashSet<string> KnownLinks = new HashSet<string>();
static LinkCache()
{
KnownLinks.Add(BindingConstants.RelAcl);
KnownLinks.Add(BindingConstants.RelDown);
KnownLinks.Add(BindingConstants.RelUp);
KnownLinks.Add(BindingConstants.RelFolderTree);
KnownLinks.Add(BindingConstants.RelRelationships);
KnownLinks.Add(BindingConstants.RelSelf);
KnownLinks.Add(BindingConstants.RelAllowableActions);
KnownLinks.Add(BindingConstants.RelEditMedia);
KnownLinks.Add(BindingConstants.RelPolicies);
KnownLinks.Add(BindingConstants.RelVersionHistory);
KnownLinks.Add(BindingConstants.RelWorkingCopy);
KnownLinks.Add(AtomPubParser.LinkRelContent);
}
private IBindingCache linkCache;
private IBindingCache typeLinkCache;
private IBindingCache collectionLinkCache;
private IBindingCache templateCache;
private IBindingCache repositoryLinkCache;
/// <summary>
/// Constructor.
/// </summary>
public LinkCache(BindingSession session)
{
int repCount = session.GetValue(SessionParameter.CacheSizeRepositories,
SessionParameterDefaults.CacheSizeRepositories);
if (repCount < 1)
{
repCount = SessionParameterDefaults.CacheSizeRepositories;
}
int typeCount = session.GetValue(SessionParameter.CacheSizeTypes, SessionParameterDefaults.CacheSizeTypes);
if (typeCount < 1)
{
typeCount = SessionParameterDefaults.CacheSizeTypes;
}
int objCount = session.GetValue(SessionParameter.CacheSizeLinks, SessionParameterDefaults.CacheSizeLinks);
if (objCount < 1)
{
objCount = SessionParameterDefaults.CacheSizeLinks;
}
string dictionaryLevelName = typeof(DictionaryCacheLevel).FullName;
string lruLevelName = typeof(LruCacheLevel).FullName;
string contentTypeLevelName = typeof(DictionaryCacheLevel).FullName;
linkCache = new Cache("Link Cache");
linkCache.Initialize(new string[] {
dictionaryLevelName + " " + DictionaryCacheLevel.Capacity + "=" + repCount.ToString(), // repository
lruLevelName + " " + LruCacheLevel.MaxEntries + "=" + objCount.ToString(), // id
dictionaryLevelName + " " + DictionaryCacheLevel.Capacity + "=16", // rel
contentTypeLevelName + " " + DictionaryCacheLevel.Capacity + "=3,"
+ DictionaryCacheLevel.SingleValue + "=true" // type
});
typeLinkCache = new Cache("Type Link Cache");
typeLinkCache.Initialize(new string[] {
dictionaryLevelName + " " + DictionaryCacheLevel.Capacity + "=" + repCount.ToString(), // repository
lruLevelName + " " + LruCacheLevel.MaxEntries + "=" + typeCount.ToString(), // id
dictionaryLevelName + " " + DictionaryCacheLevel.Capacity + "=16", // rel
contentTypeLevelName + " " + DictionaryCacheLevel.Capacity + "=3,"
+ DictionaryCacheLevel.SingleValue + "=true"// type
});
collectionLinkCache = new Cache("Collection Link Cache");
collectionLinkCache.Initialize(new string[] {
dictionaryLevelName + " " + DictionaryCacheLevel.Capacity + "=" + repCount.ToString(), // repository
dictionaryLevelName + " " + DictionaryCacheLevel.Capacity + "=8" // collection
});
templateCache = new Cache("URI Template Cache");
templateCache.Initialize(new string[] {
dictionaryLevelName + " " + DictionaryCacheLevel.Capacity + "=" + repCount.ToString(), // repository
dictionaryLevelName + " " + DictionaryCacheLevel.Capacity + "=6" // type
});
repositoryLinkCache = new Cache("Repository Link Cache");
repositoryLinkCache.Initialize(new string[] {
dictionaryLevelName + " " + DictionaryCacheLevel.Capacity + "=" + repCount.ToString(), // repository
dictionaryLevelName + " " + DictionaryCacheLevel.Capacity + "=6" // rel
});
}
/// <summary>
/// Adds a link.
/// </summary>
public void AddLink(string repositoryId, string id, string rel, string type, string link)
{
if (KnownLinks.Contains(rel))
{
linkCache.Put(new string[] { repositoryId, id, rel, type }, link);
}
else if (BindingConstants.RelAlternate == rel)
{
// use streamId instead of type as discriminating parameter
string streamId = extractStreamId(link);
if (streamId != null)
{
linkCache.Put(new string[] { link, repositoryId, id, rel }, streamId);
}
}
}
/// <summary>
/// Tries to extract a streamId from an alternate link.
/// this is not strictly in the spec
/// </summary>
protected string extractStreamId(string link)
{
int i = link.LastIndexOf('?');
if (i > 0)
{
string[] parameters = link.Substring(i + 1).Split('&');
foreach (string param in parameters)
{
string[] parts = param.Split(new char[] { '=' }, 2);
if (parts[0] == BindingConstants.ParamStreamId && parts.Length == 2)
{
return parts[1];
}
}
}
return null;
}
/// <summary>
/// Removes all links of an object.
/// </summary>
public void RemoveLinks(string repositoryId, string id)
{
linkCache.Remove(new string[] { repositoryId, id });
}
/// <summary>
/// Gets a link.
/// </summary>
public string GetLink(string repositoryId, string id, string rel, string type)
{
return (string)linkCache.Get(new string[] { repositoryId, id, rel, type });
}
/// <summary>
/// Gets a link.
/// </summary>
public string GetLink(string repositoryId, string id, string rel)
{
return GetLink(repositoryId, id, rel, null);
}
/// <summary>
/// Checks a link.
/// </summary>
public int CheckLink(string repositoryId, string id, string rel, string type)
{
return linkCache.Check(new string[] { repositoryId, id, rel, type });
}
/// <summary>
/// Locks the link cache.
/// </summary>
public void LockLinks()
{
linkCache.Lock();
}
/// <summary>
/// Unlocks the link cache.
/// </summary>
public void UnlockLinks()
{
linkCache.Unlock();
}
/// <summary>
/// Adds a type link.
/// </summary>
public void AddTypeLink(string repositoryId, string id, string rel, string type, string link)
{
if (KnownLinks.Contains(rel))
{
typeLinkCache.Put(new string[] { link, repositoryId, id, rel }, type);
}
}
/// <summary>
/// Removes all links of a type.
/// </summary>
public void RemoveTypeLinks(string repositoryId, string id)
{
typeLinkCache.Remove(new string[] { repositoryId, id });
}
/// <summary>
/// Gets a type link.
/// </summary>
public string GetTypeLink(string repositoryId, string id, string rel, string type)
{
return (string)typeLinkCache.Get(new string[] { repositoryId, id, rel, type });
}
/// <summary>
/// Locks the type link cache.
/// </summary>
public void LockTypeLinks()
{
typeLinkCache.Lock();
}
/// <summary>
/// Unlocks the type link cache.
/// </summary>
public void UnlockTypeLinks()
{
typeLinkCache.Unlock();
}
/// <summary>
/// Adds a collection.
/// </summary>
public void AddCollection(string repositoryId, string collection, string link)
{
collectionLinkCache.Put(new string[] { repositoryId, collection }, link);
}
/// <summary>
/// Gets a collection.
/// </summary>
public string GetCollection(string repositoryId, string collection)
{
return (string)collectionLinkCache.Get(new string[] { repositoryId, collection });
}
/// <summary>
/// Adds an URI template.
/// </summary>
public void AddTemplate(string repositoryId, string type, string link)
{
templateCache.Put(new string[] { repositoryId, type }, link);
}
/// <summary>
/// Gets an URI template and replaces place holders with the given
/// parameters.
/// </summary>
public string GetTemplateLink(string repositoryId, string type, IDictionary<string, object> parameters)
{
string template = (string)templateCache.Get(new string[] { repositoryId, type });
if (template == null)
{
return null;
}
StringBuilder result = new StringBuilder();
StringBuilder param = new StringBuilder();
bool paramMode = false;
for (int i = 0; i < template.Length; i++)
{
char c = template[i];
if (paramMode)
{
if (c == '}')
{
paramMode = false;
object paramValue;
if (parameters.TryGetValue(param.ToString(), out paramValue))
{
result.Append(paramValue == null ? "" : Uri.EscapeDataString(UrlBuilder.NormalizeParameter(paramValue)));
}
param.Length = 0;
}
else
{
param.Append(c);
}
}
else
{
if (c == '{')
{
paramMode = true;
}
else
{
result.Append(c);
}
}
}
return result.ToString();
}
// ---- repository links ----
/// <summary>
/// Adds a collection.
/// </summary>
public void AddRepositoryLink(string repositoryId, string rel, string link)
{
repositoryLinkCache.Put(new string[] { repositoryId, rel }, link);
}
/// <summary>
/// Gets a collection.
/// </summary>
public string GetRepositoryLink(string repositoryId, string rel)
{
return (string)repositoryLinkCache.Get(new string[] { repositoryId, rel });
}
// ---- clear ----
/// <summary>
/// Removes all entries of the given repository from the caches.
/// </summary>
public void ClearRepository(string repositoryId)
{
linkCache.Remove(new string[] { repositoryId });
typeLinkCache.Remove(new string[] { repositoryId });
collectionLinkCache.Remove(new string[] { repositoryId });
templateCache.Remove(new string[] { repositoryId });
repositoryLinkCache.Remove(new string[] { repositoryId });
}
public override string ToString()
{
return "Link Cache [link cache=" + linkCache + ", type link cache=" + typeLinkCache
+ ", collection link cache=" + collectionLinkCache + ", repository link cache=" + repositoryLinkCache
+ ", template cache=" + templateCache + "]";
}
}
/// <summary>
/// Writes a CMIS Atom entry to an output stream.
/// </summary>
internal class AtomEntryWriter
{
private const int BufferSize = 64 * 1024;
private CmisVersion cmisVersion;
private IObjectData objectData;
private IContentStream contentStream;
private Stream stream;
private ITypeDefinition typeDef;
private BulkUpdate bulkUpdate;
/// <summary>
/// Constructor for objects.
/// </summary>
public AtomEntryWriter(IObjectData objectData, CmisVersion cmisVersion)
: this(objectData, cmisVersion, null)
{
}
/// <summary>
/// Constructor for objects.
/// </summary>
public AtomEntryWriter(IObjectData objectData, CmisVersion cmisVersion, IContentStream contentStream)
{
if (objectData == null || objectData.Properties == null)
{
throw new CmisInvalidArgumentException("Object and properties must not be null!");
}
if (contentStream != null && contentStream.MimeType == null)
{
throw new CmisInvalidArgumentException("Media type must be set if a stream is present!");
}
this.objectData = objectData;
this.cmisVersion = cmisVersion;
this.contentStream = contentStream;
if (contentStream != null && contentStream.Stream != null)
{
// do we need buffering here?
stream = contentStream.Stream;
}
else
{
stream = null;
}
this.typeDef = null;
this.bulkUpdate = null;
}
/// <summary>
/// Constructor for types.
/// </summary>
public AtomEntryWriter(ITypeDefinition type, CmisVersion cmisVersion)
{
if (type == null)
{
throw new CmisInvalidArgumentException("Type must not be null!");
}
this.typeDef = type;
this.cmisVersion = cmisVersion;
this.objectData = null;
this.contentStream = null;
this.stream = null;
this.bulkUpdate = null;
}
/// <summary>
/// Constructor for bulk updates.
/// </summary>
public AtomEntryWriter(BulkUpdate bulkUpdate)
{
if (bulkUpdate == null)
{
throw new CmisInvalidArgumentException("Bulk update data must not be null!");
}
this.bulkUpdate = bulkUpdate;
this.typeDef = null;
this.cmisVersion = CmisVersion.Cmis_1_1;
this.objectData = null;
this.contentStream = null;
this.stream = null;
}
/// <summary>
/// Writes the entry to an output stream.
/// </summary>
public void Write(Stream outStream)
{
using (XmlWriter writer = XmlUtils.createWriter(outStream))
{
XmlUtils.StartXmlDocument(writer);
writer.WriteStartElement(XmlConstants.PREFIX_ATOM, "entry", XmlConstants.NAMESPACE_ATOM);
writer.WriteAttributeString("xmlns", XmlConstants.PREFIX_ATOM, null, XmlConstants.NAMESPACE_ATOM);
writer.WriteAttributeString("xmlns", XmlConstants.PREFIX_CMIS, null, XmlConstants.NAMESPACE_CMIS);
writer.WriteAttributeString("xmlns", XmlConstants.PREFIX_RESTATOM, null, XmlConstants.NAMESPACE_RESTATOM);
if (contentStream != null && contentStream.FileName != null)
{
writer.WriteAttributeString("xmlns", XmlConstants.PREFIX_APACHE_CHEMISTY, null, XmlConstants.NAMESPACE_APACHE_CHEMISTRY);
}
// atom:id
XmlUtils.Write(writer, XmlConstants.PREFIX_ATOM, XmlConstants.NAMESPACE_ATOM, XmlConstants.TAG_ATOM_ID,
"urn:uuid:00000000-0000-0000-0000-00000000000");
// atom:title
XmlUtils.Write(writer, XmlConstants.PREFIX_ATOM, XmlConstants.NAMESPACE_ATOM, XmlConstants.TAG_ATOM_TITLE, GetTitle());
// atom:updated
XmlUtils.Write(writer, XmlConstants.PREFIX_ATOM, XmlConstants.NAMESPACE_ATOM, XmlConstants.TAG_ATOM_UPDATED, DateTime.UtcNow);
// content
if (stream != null)
{
writer.WriteStartElement(XmlConstants.PREFIX_RESTATOM, XmlConstants.TAG_CONTENT, XmlConstants.NAMESPACE_RESTATOM);
XmlUtils.Write(writer, XmlConstants.PREFIX_RESTATOM, XmlConstants.NAMESPACE_RESTATOM,
XmlConstants.TAG_CONTENT_MEDIATYPE, contentStream.MimeType);
if (contentStream.FileName != null)
{
XmlUtils.Write(writer, XmlConstants.PREFIX_APACHE_CHEMISTY, XmlConstants.NAMESPACE_APACHE_CHEMISTRY,
XmlConstants.TAG_CONTENT_FILENAME, contentStream.FileName);
}
writer.WriteStartElement(XmlConstants.PREFIX_RESTATOM, XmlConstants.TAG_CONTENT_BASE64, XmlConstants.NAMESPACE_RESTATOM);
WriteContent(writer);
writer.WriteEndElement();
writer.WriteEndElement();
}
// object
if (objectData != null)
{
XmlConverter.writeObject(writer, cmisVersion, XmlConstants.NAMESPACE_RESTATOM, objectData);
}
// type
if (typeDef != null)
{
XmlConverter.writeTypeDefinition(writer, cmisVersion, XmlConstants.NAMESPACE_RESTATOM, typeDef);
}
// bulk update
if (bulkUpdate != null)
{
XmlConverter.writeBulkUpdate(writer, XmlConstants.NAMESPACE_RESTATOM, bulkUpdate);
}
// end entry
writer.WriteEndElement();
// end document
XmlUtils.EndXmlDocument(writer);
}
}
// ---- internal ----
private string GetTitle()
{
string result = "";
if (objectData != null)
{
IPropertyData nameProperty = objectData.Properties[PropertyIds.Name];
if (nameProperty != null)
{
result = nameProperty.FirstValue as string;
}
}
if (typeDef != null)
{
if (typeDef.DisplayName != null)
{
result = typeDef.DisplayName;
}
}
if (bulkUpdate != null)
{
result = "Bulk Update Properties";
}
return result;
}
private void WriteContent(XmlWriter writer)
{
byte[] buffer = new byte[BufferSize];
int rb;
while ((rb = stream.Read(buffer, 0, buffer.Length)) > 0)
{
writer.WriteBase64(buffer, 0, rb);
}
}
}
/// <summary>
/// HttpContent class for streaming AtomPub content.
/// </summary>
internal class AtomPubHttpContent : HttpContent
{
private readonly Action<Stream> writeAction;
public AtomPubHttpContent(string contentType, Action<Stream> writeAction)
: base()
{
MediaTypeHeaderValue contentTypeHeader = MediaTypeHeaderValue.Parse(contentType);
contentTypeHeader.CharSet = Encoding.UTF8.WebName;
this.Headers.ContentType = contentTypeHeader;
this.writeAction = writeAction;
}
protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context)
{
await Task.Run(() => writeAction(stream));
}
protected override bool TryComputeLength(out long length)
{
length = -1;
return false;
}
}
}