/* | |
* 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.NamespaceAtom) | |
{ | |
if (local == XmlConstants.TAG_FEED) | |
{ | |
parseResult = ParseFeed(parser); | |
break; | |
} | |
else if (local == XmlConstants.TAG_ENTRY) | |
{ | |
parseResult = ParseEntry(parser); | |
break; | |
} | |
} | |
else if (uri == XmlConstants.NamespaceCmis) | |
{ | |
if (local == XmlConstants.TAG_ALLOWABLEACTIONS) | |
{ | |
parseResult = ParseAllowableActions(parser); | |
break; | |
} | |
else if (local == XmlConstants.TAG_ACL) | |
{ | |
parseResult = ParseACL(parser); | |
break; | |
} | |
} | |
else if (uri == XmlConstants.NamespaceApp) | |
{ | |
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.NamespaceApp) | |
{ | |
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.NamespaceAtom) | |
{ | |
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.NamespaceRestAtom) | |
{ | |
if (local == XmlConstants.TAG_NUM_ITEMS) | |
{ | |
result.AddElement(ParseBigInteger(parser)); | |
} | |
else | |
{ | |
XmlUtils.Skip(parser); | |
} | |
} | |
else if (uri == XmlConstants.NamespaceApacheChemistry) | |
{ | |
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.NamespaceRestAtom) | |
{ | |
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.NamespaceAtom) | |
{ | |
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.NamespaceAtom) | |
{ | |
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.NamespaceRestAtom) | |
{ | |
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.NamespaceAtom) | |
{ | |
if (local == XmlConstants.TAG_LINK) | |
{ | |
return ParseLink(parser); | |
} | |
} | |
else if (uri == XmlConstants.NamespaceApp) | |
{ | |
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.NamespaceRestAtom | |
&& 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.NamespaceRestAtom) | |
{ | |
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.PrefixAtom, "entry", XmlConstants.NamespaceAtom); | |
writer.WriteAttributeString("xmlns", XmlConstants.PrefixAtom, null, XmlConstants.NamespaceAtom); | |
writer.WriteAttributeString("xmlns", XmlConstants.PrefixCmis, null, XmlConstants.NamespaceCmis); | |
writer.WriteAttributeString("xmlns", XmlConstants.PrefixRestAtom, null, XmlConstants.NamespaceRestAtom); | |
if (contentStream != null && contentStream.FileName != null) | |
{ | |
writer.WriteAttributeString("xmlns", XmlConstants.PrefixApacheChemistry, null, XmlConstants.NamespaceApacheChemistry); | |
} | |
// atom:id | |
XmlUtils.Write(writer, XmlConstants.PrefixAtom, XmlConstants.NamespaceAtom, XmlConstants.TAG_ATOM_ID, | |
"urn:uuid:00000000-0000-0000-0000-00000000000"); | |
// atom:title | |
XmlUtils.Write(writer, XmlConstants.PrefixAtom, XmlConstants.NamespaceAtom, XmlConstants.TAG_ATOM_TITLE, GetTitle()); | |
// atom:updated | |
XmlUtils.Write(writer, XmlConstants.PrefixAtom, XmlConstants.NamespaceAtom, XmlConstants.TAG_ATOM_UPDATED, DateTime.UtcNow); | |
// content | |
if (stream != null) | |
{ | |
writer.WriteStartElement(XmlConstants.PrefixRestAtom, XmlConstants.TAG_CONTENT, XmlConstants.NamespaceRestAtom); | |
XmlUtils.Write(writer, XmlConstants.PrefixRestAtom, XmlConstants.NamespaceRestAtom, | |
XmlConstants.TAG_CONTENT_MEDIATYPE, contentStream.MimeType); | |
if (contentStream.FileName != null) | |
{ | |
XmlUtils.Write(writer, XmlConstants.PrefixApacheChemistry, XmlConstants.NamespaceApacheChemistry, | |
XmlConstants.TAG_CONTENT_FILENAME, contentStream.FileName); | |
} | |
writer.WriteStartElement(XmlConstants.PrefixRestAtom, XmlConstants.TAG_CONTENT_BASE64, XmlConstants.NamespaceRestAtom); | |
WriteContent(writer); | |
writer.WriteEndElement(); | |
writer.WriteEndElement(); | |
} | |
// object | |
if (objectData != null) | |
{ | |
XmlConverter.WriteObject(writer, cmisVersion, XmlConstants.NamespaceRestAtom, objectData); | |
} | |
// type | |
if (typeDef != null) | |
{ | |
XmlConverter.WriteTypeDefinition(writer, cmisVersion, XmlConstants.NamespaceRestAtom, typeDef); | |
} | |
// bulk update | |
if (bulkUpdate != null) | |
{ | |
XmlConverter.WriteBulkUpdate(writer, XmlConstants.NamespaceRestAtom, 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; | |
} | |
} | |
} |