Code:
/ Dotnetfx_Win7_3.5.1 / Dotnetfx_Win7_3.5.1 / 3.5.1 / DEVDIV / depot / DevDiv / releases / Orcas / NetFXw7 / wpf / src / Framework / System / Windows / Annotations / Storage / XmlStreamStore.cs / 1 / XmlStreamStore.cs
//------------------------------------------------------------------------------
//
//
// Copyright (C) Microsoft Corporation. All rights reserved.
//
//
// Description:
// An AnnotationStore subclass based on XML streams. Processes XML
// streams and returns CAF 2.0 OM objects. Useful to other
// AnnotationStore subclass who can get an XML stream of their
// content.
// Spec: http://team/sites/ag/Specifications/CAF%20Storage%20Spec.doc
//
// History:
// 10/04/2002: rruiz: Added header comment to XmlStreamStore.cs.
// 05/29/2003: LGolding: Ported to WCP tree.
// 07/17/2003: rruiz: Made this a real AnnotationStore subclass; updated with
// new APIs; renamed class.
// 01/13/2004 ssimova: Added revert events
// 02/12/2004 ssimova: Added locks for thread safety
// 08/18/2004: magedz: Modify the add/delete/querying capabilities to use the map
//
//-----------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Diagnostics;
using System.IO;
using System.Windows;
using System.Windows.Annotations;
using System.Xml;
using System.Xml.Schema;
using System.Xml.XPath;
using System.Xml.Serialization;
using MS.Internal;
using MS.Internal.Annotations;
using MS.Internal.Annotations.Storage;
using MS.Utility;
using System.Windows.Markup;
using MS.Internal.Controls.StickyNote;
using System.Windows.Controls;
namespace System.Windows.Annotations.Storage
{
///
/// An AnnotationStore subclass based on XML streams. Processes XML
/// streams and returns CAF 2.0 OM objects. Useful to other
/// AnnotationStore subclass who can get an XML stream of their
/// content.
///
public sealed class XmlStreamStore : AnnotationStore
{
//-----------------------------------------------------
//
// Constructors
//
//-----------------------------------------------------
#region Constructors
///
/// This ctor initializes the Dictionary with predefined namespases
/// and their compatibility
///
static XmlStreamStore()
{
_predefinedNamespaces = new Dictionary>(6);
_predefinedNamespaces.Add(new Uri(AnnotationXmlConstants.Namespaces.CoreSchemaNamespace), null);
_predefinedNamespaces.Add(new Uri(AnnotationXmlConstants.Namespaces.BaseSchemaNamespace), null);
_predefinedNamespaces.Add(new Uri(XamlReaderHelper.DefaultNamespaceURI), null);
}
///
/// Creates an instance using the XML stream passed in as the
/// content. The XML in the stream must be valid XML and conform
/// to the CAF 2.0 schema.
///
/// stream containing annotation data in XML format
/// stream is null
/// stream contains invalid XML
public XmlStreamStore(Stream stream)
: base()
{
if (stream == null)
throw new ArgumentNullException("stream");
if (!stream.CanSeek)
throw new ArgumentException(SR.Get(SRID.StreamDoesNotSupportSeek));
SetStream(stream, null);
}
///
/// Creates an instance using the XML stream passed in as the
/// content. The XML in the stream must be valid XML and conform
/// to the Annotations V1 schema or a valid future version XML which
/// compatibility rules are that when applied they will produce
/// a valid Annotations V1 XML. This .ctor allows registration of
/// application specific known namespaces.
///
/// stream containing annotation data in XML format
/// A dictionary with known and compatible namespaces. The keys in
/// this dictionary are known namespaces. The value of each key is a list of namespaces that are compatible with
/// the key one, i.e. each of the namespaces in the value list will be transformed to the
/// key namespace while reading the input XML.
/// stream is null
/// stream contains invalid XML
/// duplicate namespace in knownNamespaces dictionary
/// null key in knownNamespaces dictionary
public XmlStreamStore(Stream stream, IDictionary> knownNamespaces)
: base()
{
if (stream == null)
throw new ArgumentNullException("stream");
SetStream(stream, knownNamespaces);
}
#endregion Constructors
//------------------------------------------------------
//
// Public Methods
//
//-----------------------------------------------------
#region Public Methods
///
/// Add a new annotation to this store. The new annotation's Id
/// is set to a new value.
///
/// the annotation to be added to the store
/// newAnnotation is null
/// newAnnotation already exists in this store, as determined by its Id
/// if no stream has been set on the store
/// if object has been Disposed
public override void AddAnnotation(Annotation newAnnotation)
{
if (newAnnotation == null)
throw new ArgumentNullException("newAnnotation");
// We are going to modify internal data. Lock the object
// to avoid modifications from other threads
lock (SyncRoot)
{
//fire trace event
EventTrace.NormalTraceEvent(EventTraceGuidId.ADDANNOTATIONGUID, EventType.StartEvent);
CheckStatus();
XPathNavigator editor = GetAnnotationNodeForId(newAnnotation.Id);
// we are making sure that the newAnnotation doesn't already exist in the store
if (editor != null)
throw new ArgumentException(SR.Get(SRID.AnnotationAlreadyExists), "newAnnotation");
// we are making sure that the newAnnotation doesn't already exist in the store map
if (_storeAnnotationsMap.FindAnnotation(newAnnotation.Id) != null)
throw new ArgumentException(SR.Get(SRID.AnnotationAlreadyExists), "newAnnotation");
// simply add the annotation to the map to save on performance
// notice that we need to tell the map that this instance of the annotation is dirty
_storeAnnotationsMap.AddAnnotation(newAnnotation, true);
//fire trace event
EventTrace.NormalTraceEvent(EventTraceGuidId.ADDANNOTATIONGUID, EventType.EndEvent);
}
OnStoreContentChanged(new StoreContentChangedEventArgs(StoreContentAction.Added, newAnnotation));
}
///
/// Delete the specified annotation.
///
/// the Id of the annotation to be deleted
/// the annotation that was deleted, or null if no annotation
/// with the specified Id was found
/// if no stream has been set on the store
/// if object has been disposed
public override Annotation DeleteAnnotation(Guid annotationId)
{
Annotation annotation = null;
// We are now going to modify internal data. Lock the object
// to avoid modifications from other threads
lock (SyncRoot)
{
//fire trace event
EventTrace.NormalTraceEvent(EventTraceGuidId.DELETEANNOTATIONGUID, EventType.StartEvent);
CheckStatus();
annotation = _storeAnnotationsMap.FindAnnotation(annotationId);
XPathNavigator editor = GetAnnotationNodeForId(annotationId);
if (editor != null)
{
// Only deserialize the annotation if its not already in our map
if (annotation == null)
{
annotation = (Annotation)_serializer.Deserialize(editor.ReadSubtree());
}
editor.DeleteSelf();
}
// Remove the instance from the map
_storeAnnotationsMap.RemoveAnnotation(annotationId);
// notice that in Add we add the annotation to the map only
// but in delete we delete it from both to the Xml and the map
//fire trace event
EventTrace.NormalTraceEvent(EventTraceGuidId.DELETEANNOTATIONGUID, EventType.EndEvent);
}
// Only fire notification if we actually removed an annotation
if (annotation != null)
{
OnStoreContentChanged(new StoreContentChangedEventArgs(StoreContentAction.Deleted, annotation));
}
return annotation;
}
///
/// Queries the Xml stream for annotations that have an anchor
/// that contains a locator that begins with the locator parts
/// in anchorLocator.
///
/// the locator we are looking for
///
/// A list of annotations that have locators in their anchors
/// starting with the same locator parts list as of the input locator
/// If no such annotations an empty list will be returned. The method
/// never returns null.
///
/// if object has been Disposed
/// the stream is null
public override IList GetAnnotations(ContentLocator anchorLocator)
{
// First we generate the XPath expression
if (anchorLocator == null)
throw new ArgumentNullException("anchorLocator");
if (anchorLocator.Parts == null)
throw new ArgumentNullException("anchorLocator.Parts");
//fire trace event
EventTrace.NormalTraceEvent(EventTraceGuidId.GETANNOTATIONBYLOCGUID, EventType.StartEvent);
string query = @"//" + AnnotationXmlConstants.Prefixes.CoreSchemaPrefix + ":" + AnnotationXmlConstants.Elements.ContentLocator;
if (anchorLocator.Parts.Count > 0)
{
query += @"/child::*[1]/self::";
for (int i = 0; i < anchorLocator.Parts.Count; i++)
{
if (anchorLocator.Parts[i] != null)
{
if (i > 0)
{
query += @"/following-sibling::";
}
string fragment = anchorLocator.Parts[i].GetQueryFragment(_namespaceManager);
if (fragment != null)
{
query += fragment;
}
else
{
query += "*";
}
}
}
}
query += @"/ancestor::" + AnnotationXmlConstants.Prefixes.CoreSchemaPrefix + ":Anchors/ancestor::" + AnnotationXmlConstants.Prefixes.CoreSchemaPrefix + ":Annotation";
//fire trace event
EventTrace.NormalTraceEvent(EventTraceGuidId.GETANNOTATIONBYLOCGUID, EventType.EndEvent);
return InternalGetAnnotations(query, anchorLocator);
}
///
/// Returns a list of all annotations in the store
///
/// annotations list. Can return an empty list, but never null.
/// if object has been disposed
public override IList GetAnnotations()
{
//fire trace event
EventTrace.NormalTraceEvent(EventTraceGuidId.GETANNOTATIONSGUID, EventType.StartEvent);
string query = "//" + AnnotationXmlConstants.Prefixes.CoreSchemaPrefix + ":Annotation";
return InternalGetAnnotations(query, null);
//fire trace event
// unreachable - previous statement is 'return' (bug 1788240)
#pragma warning disable 0162
EventTrace.NormalTraceEvent(EventTraceGuidId.GETANNOTATIONSGUID, EventType.EndEvent);
#pragma warning restore 0162
}
///
/// Finds annotation by Id
///
/// annotation id
/// The annotation. Null if the annotation does not exists
/// if object has been disposed
public override Annotation GetAnnotation(Guid annotationId)
{
lock (SyncRoot)
{
//fire trace event
EventTrace.NormalTraceEvent(EventTraceGuidId.GETANNOTATIONBYIDGUID, EventType.StartEvent);
CheckStatus();
Annotation annotation = _storeAnnotationsMap.FindAnnotation(annotationId);
// If there is no pre-existing instance, we deserialize and create an instance.
if (annotation != null)
{
return annotation;
}
XPathNavigator editor = GetAnnotationNodeForId(annotationId);
if (editor != null)
{
annotation = (Annotation)_serializer.Deserialize(editor.ReadSubtree());
// Add the new instance to the map
_storeAnnotationsMap.AddAnnotation(annotation, false);
}
//fire trace event
EventTrace.NormalTraceEvent(EventTraceGuidId.GETANNOTATIONBYIDGUID, EventType.EndEvent);
return annotation;
}
}
///
/// Causes any buffered data to be written to the underlying
/// storage mechanism. Gets called after each operation if
/// AutoFlush is set to true. The stream is truncated to
/// the length of the data written out by the store.
///
/// stream cannot be written to
/// if no stream has been set on the store
/// if object has been disposed
///
public override void Flush()
{
lock (SyncRoot)
{
CheckStatus();
if (!_stream.CanWrite)
{
throw new UnauthorizedAccessException(SR.Get(SRID.StreamCannotBeWritten));
}
if (_dirty)
{
SerializeAnnotations();
_stream.Position = 0;
_stream.SetLength(0);
_document.PreserveWhitespace = true;
_document.Save(_stream);
_stream.Flush();
_dirty = false;
}
}
}
///
/// Returns a list of namespaces that are compatible with an input namespace
///
/// namespace
/// a list of compatible namespaces. Can be null
/// This method works only with built-in AnnotationFramework namespaces.
/// For any other input namespace the return value will be null even if it is
/// registered with the XmlStreamStore ctor
public static IList GetWellKnownCompatibleNamespaces(Uri name)
{
if (name == null)
{
throw new ArgumentNullException("name");
}
if (_predefinedNamespaces.ContainsKey(name))
return _predefinedNamespaces[name];
return null;
}
#endregion Public Methods
//------------------------------------------------------
//
// Public Operators
//
//------------------------------------------------------
//-----------------------------------------------------
//
// Public Events
//
//------------------------------------------------------
//-----------------------------------------------------
//
// Public Properties
//
//-----------------------------------------------------
#region Public Properties
///
/// When set to true an implementation should call Flush()
/// as a side-effect after each operation.
///
///
/// true if the implementation is set to call Flush() after
/// each operation; false otherwise
///
public override bool AutoFlush
{
get
{
lock (SyncRoot)
{
return _autoFlush;
}
}
set
{
lock (SyncRoot)
{
_autoFlush = value;
// Commit anything that needs to be committed up to this point
if (_autoFlush)
{
Flush();
}
}
}
}
///
/// Returns a list of the namespaces that are ignored while loading
/// the Xml stream
///
/// The value is never null, but can be an empty list if nothing has been ignored
public IList IgnoredNamespaces
{
get
{
return _ignoredNamespaces;
}
}
///
/// Returns a list of all namespaces that are internaly used by the framework
///
public static IList WellKnownNamespaces
{
get
{
Uri[] res = new Uri[_predefinedNamespaces.Keys.Count];
_predefinedNamespaces.Keys.CopyTo(res, 0);
return res;
}
}
#endregion Public Properties
//-----------------------------------------------------
//
// Protected Methods
//
//------------------------------------------------------
#region Protected Methods
///
/// Disposes of the resources (other than memory) used by the store.
///
/// true to release both managed and unmanaged
/// resources; false to release only unmanaged resources
protected override void Dispose(bool disposing)
{
//call the base class first to set _disposed to false
//in order to avoid working with the store when the resources
//are released
base.Dispose(disposing);
if (disposing)
{
Cleanup();
}
}
///
/// Called after every annotation action on the store. We override it
/// to update the dirty state of the store.
///
/// arguments for the event to fire
protected override void OnStoreContentChanged(StoreContentChangedEventArgs e)
{
lock (SyncRoot)
{
_dirty = true;
}
base.OnStoreContentChanged(e);
}
#endregion Protected Methods
//-----------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
#region Private Methods
///
/// Applies the specified XPath expression to the store and returns
/// the results.
///
/// the XPath expression to be applied to the store
///
/// An IList containing zero or more annotations that match the
/// criteria in the XPath expression; never will return null. If
/// no annotations meet the criteria, an empty list is returned.
///
/// if no stream has been set on the store
/// if object has been Disposed
/// queryExpression is null
/// queryExpression is empty string
private List FindAnnotationIds(string queryExpression)
{
Invariant.Assert(queryExpression != null && queryExpression.Length > 0,
"Invalid query expression");
Guid annId;
List retObj = null;
// Lock the object so nobody can change the document
// while the query is executed
lock (SyncRoot)
{
CheckStatus();
XPathNavigator navigator = _document.CreateNavigator();
XPathNodeIterator iterator = navigator.Select(queryExpression, _namespaceManager);
if (iterator != null && iterator.Count > 0)
{
retObj = new List(iterator.Count);
foreach (XPathNavigator node in iterator)
{
string nodeId = node.GetAttribute("Id", "");
if (String.IsNullOrEmpty(nodeId))
{
throw new XmlException(SR.Get(SRID.RequiredAttributeMissing, AnnotationXmlConstants.Attributes.Id, AnnotationXmlConstants.Elements.Annotation));
}
try
{
annId = XmlConvert.ToGuid(nodeId);
}
catch (FormatException fe)
{
throw new InvalidOperationException(SR.Get(SRID.CannotParseId), fe);
}
retObj.Add(annId);
}
}
else
{
retObj = new List(0);
}
}
return retObj;
}
///
/// Used as AuthorChanged event handler for all annotations
/// handed out by the map.
///
/// annotation that sent the event
/// args for the event
private void HandleAuthorChanged(object sender, AnnotationAuthorChangedEventArgs e)
{
lock (SyncRoot)
{
_dirty = true;
}
OnAuthorChanged(e);
}
///
/// Used as AnchorChanged event handler for all annotations
/// handed out by the map.
///
/// annotation that sent the event
/// args for the event
private void HandleAnchorChanged(object sender, AnnotationResourceChangedEventArgs e)
{
lock (SyncRoot)
{
_dirty = true;
}
OnAnchorChanged(e);
}
///
/// Used as CargoChanged event handler for all annotations
/// handed out by the map.
///
/// annotation that sent the event
/// args for the event
private void HandleCargoChanged(object sender, AnnotationResourceChangedEventArgs e)
{
lock (SyncRoot)
{
_dirty = true;
}
OnCargoChanged(e);
}
///
/// 1- Merge Annotations queried from both the map and the Xml stream
/// 2- Add the annotations found in the Xml stream to the map
///
/// A dictionary of map annotations
/// A list of annotation ids from the Xml stream
///
private IList MergeAndCacheAnnotations(Dictionary mapAnnotations, List storeAnnotationsId)
{
// first put all annotations from the map in the return list
List annotations = new List((IEnumerable)mapAnnotations.Values);
// there three possible conditions
// 1- An annotation exists in xml and in the store map results
// 2- An annotation exists in xml and not in the store map results
// 2-1- The annotation is found in the map
// 2-2- The annotation is not found in the map
// Now, we need to find annotations in the store that are not in the map results
// and verify that they should be serialized
foreach (Guid annotationId in storeAnnotationsId)
{
Annotation annot;
bool foundInMapResults = mapAnnotations.TryGetValue(annotationId, out annot);
if (!foundInMapResults)
{
// it is not in the map - get it from the store
annot = GetAnnotation(annotationId);
annotations.Add(annot);
}
}
return annotations;
}
///
/// Do the GetAnnotations work inside a lock statement for thread safety reasons
///
///
///
///
private IList InternalGetAnnotations(string query, ContentLocator anchorLocator)
{
// anchorLocator being null is handled appropriately below
Invariant.Assert(query != null, "Parameter 'query' is null.");
lock (SyncRoot)
{
CheckStatus();
List annotationIds = FindAnnotationIds(query);
Dictionary annotations = null;
// Now, get the annotations in the map that satisfies the query criterion
if (anchorLocator == null)
{
annotations = _storeAnnotationsMap.FindAnnotations();
}
else
{
annotations = _storeAnnotationsMap.FindAnnotations(anchorLocator);
}
// merge both query results
return MergeAndCacheAnnotations(annotations, annotationIds);
}
}
///
/// Loads the current stream into the XmlDocument used by this
/// store as a backing store. If the stream doesn't contain any
/// data we load up a default XmlDocument for the Annotations schema.
/// The "http://schemas.microsoft.com/windows/annotations/2003/11/core"
/// namespace is registered with "anc" prefix and the
/// "http://schemas.microsoft.com/windows/annotations/2003/11/base" namespace
/// is registered with "anb" prefix as global namespaces.
/// We also select from the newly loaded document the new top
/// level node. This is used later for insertions, etc.
///
/// if the stream contains invalid XML
private void LoadStream(IDictionary> knownNamespaces)
{
//check input data first
CheckKnownNamespaces(knownNamespaces);
lock (SyncRoot)
{
_document = new XmlDocument();
_document.PreserveWhitespace = false;
if (_stream.Length == 0)
{
_document.LoadXml(" <" +
AnnotationXmlConstants.Prefixes.CoreSchemaPrefix + ":Annotations xmlns:" + AnnotationXmlConstants.Prefixes.CoreSchemaPrefix + "=\"" +
AnnotationXmlConstants.Namespaces.CoreSchemaNamespace + "\" xmlns:" + AnnotationXmlConstants.Prefixes.BaseSchemaPrefix + "=\"" + AnnotationXmlConstants.Namespaces.BaseSchemaNamespace + "\" />");
}
else
{
_xmlCompatibilityReader = SetupReader(knownNamespaces);
_document.Load(_xmlCompatibilityReader);
}
_namespaceManager = new XmlNamespaceManager(_document.NameTable);
_namespaceManager.AddNamespace(AnnotationXmlConstants.Prefixes.CoreSchemaPrefix, AnnotationXmlConstants.Namespaces.CoreSchemaNamespace);
_namespaceManager.AddNamespace(AnnotationXmlConstants.Prefixes.BaseSchemaPrefix, AnnotationXmlConstants.Namespaces.BaseSchemaNamespace);
// This use of an iterator is necessary because SelectSingleNode isn't available in the PD3
// drop of the CLR. Eventually we should be able to call SelectSingleNode and not have to
// use an iterator to get a single node.
XPathNavigator navigator = _document.CreateNavigator();
XPathNodeIterator iterator = navigator.Select("//" + AnnotationXmlConstants.Prefixes.CoreSchemaPrefix + ":Annotations", _namespaceManager);
Invariant.Assert(iterator.Count == 1, "More than one annotation returned for the query");
iterator.MoveNext();
_rootNavigator = (XPathNavigator)iterator.Current;
}
}
///
/// Checks if passed external known namespaces are valid
///
/// namespaces dictionary
/// We do not allow internal namespases in this dictionary nor
/// duplicates
private void CheckKnownNamespaces(IDictionary> knownNamespaces)
{
if (knownNamespaces == null)
return;
IList allNamespaces = new List();
//add AnnotationFramework namespaces
foreach (Uri name in _predefinedNamespaces.Keys)
{
allNamespaces.Add(name);
}
//add external namespaces
foreach (Uri knownNamespace in knownNamespaces.Keys)
{
if (knownNamespace == null)
{
throw new ArgumentException(SR.Get(SRID.NullUri), "knownNamespaces");
}
if (allNamespaces.Contains(knownNamespace))
{
throw new ArgumentException(SR.Get(SRID.DuplicatedUri), "knownNamespaces");
}
allNamespaces.Add(knownNamespace);
}
// check compatible namespaces
foreach (KeyValuePair> item in knownNamespaces)
{
if (item.Value != null)
{
foreach (Uri name in item.Value)
{
if (name == null)
{
throw new ArgumentException(SR.Get(SRID.NullUri), "knownNamespaces");
}
if (allNamespaces.Contains(name))
{
throw new ArgumentException(SR.Get(SRID.DuplicatedCompatibleUri), "knownNamespaces");
}
allNamespaces.Add(name);
}//foreach
}//if
}//foreach
}
///
/// Creates and initializes the XmlCompatibilityReader
///
/// Dictionary of external known namespaces
/// The XmlCompatibilityReader
private XmlCompatibilityReader SetupReader(IDictionary> knownNamespaces)
{
IList supportedNamespaces = new List();
//add AnnotationFramework namespaces
foreach (Uri name in _predefinedNamespaces.Keys)
{
supportedNamespaces.Add(name.ToString());
}
//add external namespaces
if (knownNamespaces != null)
{
foreach (Uri knownNamespace in knownNamespaces.Keys)
{
Debug.Assert(knownNamespace != null, "null knownNamespace");
supportedNamespaces.Add(knownNamespace.ToString());
}
}
//create XmlCompatibilityReader first
XmlCompatibilityReader reader = new XmlCompatibilityReader(new XmlTextReader(_stream),
new IsXmlNamespaceSupportedCallback(IsXmlNamespaceSupported), supportedNamespaces);
// Declare compatibility.
// Skip the Framework ones because they are all null in this version
if (knownNamespaces != null)
{
foreach (KeyValuePair> item in knownNamespaces)
{
if (item.Value != null)
{
foreach (Uri name in item.Value)
{
Debug.Assert(name != null, "null compatible namespace");
reader.DeclareNamespaceCompatibility(item.Key.ToString(), name.ToString());
}//foreach
}//if
}//foreach
}//if
//cleanup the _ignoredNamespaces
_ignoredNamespaces.Clear();
return reader;
}
///
/// A callback for XmlCompatibilityReader
///
/// inquired namespace
/// the newer subsumming namespace
/// true if the namespace is known, false if not
/// This API always returns false because all known namespaces are registered
/// before loading the Xml. It stores the namespace as ignored.
private bool IsXmlNamespaceSupported(string xmlNamespace, out string newXmlNamespace)
{
//store the namespace if needed
if (!String.IsNullOrEmpty(xmlNamespace))
{
if (!Uri.IsWellFormedUriString(xmlNamespace, UriKind.RelativeOrAbsolute))
{
throw new ArgumentException(SR.Get(SRID.InvalidNamespace, xmlNamespace), "xmlNamespace");
}
Uri namespaceUri = new Uri(xmlNamespace, UriKind.RelativeOrAbsolute);
if (!_ignoredNamespaces.Contains(namespaceUri))
_ignoredNamespaces.Add(namespaceUri);
}
newXmlNamespace = null;
return false;
}
///
/// Selects the annotation XmlElement from the current document
/// with the given id.
///
/// the id to query for in the XmlDocument
/// the XmlElement representing the annotation with the given id
private XPathNavigator GetAnnotationNodeForId(Guid id)
{
XPathNavigator navigator = null;
lock (SyncRoot)
{
XPathNavigator tempNavigator = _document.CreateNavigator();
// This use of an iterator is necessary because SelectSingleNode isn't available in the PD3
// drop of the CLR. Eventually we should be able to call SelectSingleNode and not have to
// use an iterator to get a single node.
// We use XmlConvert.ToString to turn the Guid into a string because
// that's what is used by the Annotation's serialization methods.
XPathNodeIterator iterator = tempNavigator.Select(@"//" + AnnotationXmlConstants.Prefixes.CoreSchemaPrefix + @":Annotation[@Id=""" + XmlConvert.ToString(id) + @"""]", _namespaceManager);
if (iterator.MoveNext())
{
navigator = (XPathNavigator)iterator.Current;
}
}
return navigator;
}
///
/// Verifies the store is in a valid state. Throws exceptions otherwise.
///
private void CheckStatus()
{
lock (SyncRoot)
{
if (IsDisposed)
throw new ObjectDisposedException(null, SR.Get(SRID.ObjectDisposed_StoreClosed));
if (_stream == null)
throw new InvalidOperationException(SR.Get(SRID.StreamNotSet));
}
}
///
/// Called from flush to serialize all annotations in the map
/// notice that delete takes care of the delete action both in the map
/// and in the store
///
private void SerializeAnnotations()
{
List mapAnnotations = _storeAnnotationsMap.FindDirtyAnnotations();
foreach (Annotation annotation in mapAnnotations)
{
XPathNavigator editor = GetAnnotationNodeForId(annotation.Id);
if (editor == null)
{
editor = (XPathNavigator)_rootNavigator.CreateNavigator();
XmlWriter writer = editor.AppendChild();
_serializer.Serialize(writer, annotation);
writer.Close();
}
else
{
XmlWriter writer = editor.InsertBefore();
_serializer.Serialize(writer, annotation);
writer.Close();
editor.DeleteSelf();
}
}
_storeAnnotationsMap.ValidateDirtyAnnotations();
}
///
/// Discards changes and cleans up references.
///
private void Cleanup()
{
lock (SyncRoot)
{
_xmlCompatibilityReader = null;
_ignoredNamespaces = null;
_stream = null;
_document = null;
_rootNavigator = null;
_storeAnnotationsMap = null;
}
}
///
/// Sets the stream for this store. Assumes Cleanup has
/// been previously called (or this is a new store).
///
/// new stream for this store
/// List of known and compatible namespaces used to initialize
/// the XmlCompatibilityReader
private void SetStream(Stream stream, IDictionary> knownNamespaces)
{
try
{
lock (SyncRoot)
{
_storeAnnotationsMap = new StoreAnnotationsMap(HandleAuthorChanged, HandleAnchorChanged, HandleCargoChanged);
_stream = stream;
LoadStream(knownNamespaces);
}
}
catch
{
Cleanup();
throw;
}
}
#endregion Private Methods
//------------------------------------------------------
//
// Private Fields
//
//-----------------------------------------------------
#region Private Fields
// Dirty bit for the store's in-memory cache
private bool _dirty;
// Boolean flag represents whether the store should perform a flush
// after every operation or not
private bool _autoFlush;
// XmlDocument used as an in-memory cache of the stream's XML content
private XmlDocument _document;
// Namespace manager used for all queries.
private XmlNamespaceManager _namespaceManager;
// Stream passed in by the creator of this instance.
private Stream _stream;
// The xpath navigator used to navigate the annotations Xml stream
private XPathNavigator _rootNavigator;
// map that holds AnnotationId->Annotation
StoreAnnotationsMap _storeAnnotationsMap;
//list of ignored namespaces during XmlLoad
List _ignoredNamespaces = new List();
//XmlCompatibilityReader - we need to hold that one open, so the underlying stream stays open too
// if the store is disposed the reader will be disposed and the stream will be closed too.
XmlCompatibilityReader _xmlCompatibilityReader;
///
///Static fields
///
//predefined namespaces
private static readonly Dictionary> _predefinedNamespaces;
// Serializer for Annotations
private static readonly Serializer _serializer = new Serializer(typeof(Annotation));
#endregion Private Fields
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
//------------------------------------------------------------------------------
//
//
// Copyright (C) Microsoft Corporation. All rights reserved.
//
//
// Description:
// An AnnotationStore subclass based on XML streams. Processes XML
// streams and returns CAF 2.0 OM objects. Useful to other
// AnnotationStore subclass who can get an XML stream of their
// content.
// Spec: http://team/sites/ag/Specifications/CAF%20Storage%20Spec.doc
//
// History:
// 10/04/2002: rruiz: Added header comment to XmlStreamStore.cs.
// 05/29/2003: LGolding: Ported to WCP tree.
// 07/17/2003: rruiz: Made this a real AnnotationStore subclass; updated with
// new APIs; renamed class.
// 01/13/2004 ssimova: Added revert events
// 02/12/2004 ssimova: Added locks for thread safety
// 08/18/2004: magedz: Modify the add/delete/querying capabilities to use the map
//
//-----------------------------------------------------------------------------
using System;
using System.Collections;
using System.Collections.Generic;
using System.Globalization;
using System.Diagnostics;
using System.IO;
using System.Windows;
using System.Windows.Annotations;
using System.Xml;
using System.Xml.Schema;
using System.Xml.XPath;
using System.Xml.Serialization;
using MS.Internal;
using MS.Internal.Annotations;
using MS.Internal.Annotations.Storage;
using MS.Utility;
using System.Windows.Markup;
using MS.Internal.Controls.StickyNote;
using System.Windows.Controls;
namespace System.Windows.Annotations.Storage
{
///
/// An AnnotationStore subclass based on XML streams. Processes XML
/// streams and returns CAF 2.0 OM objects. Useful to other
/// AnnotationStore subclass who can get an XML stream of their
/// content.
///
public sealed class XmlStreamStore : AnnotationStore
{
//-----------------------------------------------------
//
// Constructors
//
//-----------------------------------------------------
#region Constructors
///
/// This ctor initializes the Dictionary with predefined namespases
/// and their compatibility
///
static XmlStreamStore()
{
_predefinedNamespaces = new Dictionary>(6);
_predefinedNamespaces.Add(new Uri(AnnotationXmlConstants.Namespaces.CoreSchemaNamespace), null);
_predefinedNamespaces.Add(new Uri(AnnotationXmlConstants.Namespaces.BaseSchemaNamespace), null);
_predefinedNamespaces.Add(new Uri(XamlReaderHelper.DefaultNamespaceURI), null);
}
///
/// Creates an instance using the XML stream passed in as the
/// content. The XML in the stream must be valid XML and conform
/// to the CAF 2.0 schema.
///
/// stream containing annotation data in XML format
/// stream is null
/// stream contains invalid XML
public XmlStreamStore(Stream stream)
: base()
{
if (stream == null)
throw new ArgumentNullException("stream");
if (!stream.CanSeek)
throw new ArgumentException(SR.Get(SRID.StreamDoesNotSupportSeek));
SetStream(stream, null);
}
///
/// Creates an instance using the XML stream passed in as the
/// content. The XML in the stream must be valid XML and conform
/// to the Annotations V1 schema or a valid future version XML which
/// compatibility rules are that when applied they will produce
/// a valid Annotations V1 XML. This .ctor allows registration of
/// application specific known namespaces.
///
/// stream containing annotation data in XML format
/// A dictionary with known and compatible namespaces. The keys in
/// this dictionary are known namespaces. The value of each key is a list of namespaces that are compatible with
/// the key one, i.e. each of the namespaces in the value list will be transformed to the
/// key namespace while reading the input XML.
/// stream is null
/// stream contains invalid XML
/// duplicate namespace in knownNamespaces dictionary
/// null key in knownNamespaces dictionary
public XmlStreamStore(Stream stream, IDictionary> knownNamespaces)
: base()
{
if (stream == null)
throw new ArgumentNullException("stream");
SetStream(stream, knownNamespaces);
}
#endregion Constructors
//------------------------------------------------------
//
// Public Methods
//
//-----------------------------------------------------
#region Public Methods
///
/// Add a new annotation to this store. The new annotation's Id
/// is set to a new value.
///
/// the annotation to be added to the store
/// newAnnotation is null
/// newAnnotation already exists in this store, as determined by its Id
/// if no stream has been set on the store
/// if object has been Disposed
public override void AddAnnotation(Annotation newAnnotation)
{
if (newAnnotation == null)
throw new ArgumentNullException("newAnnotation");
// We are going to modify internal data. Lock the object
// to avoid modifications from other threads
lock (SyncRoot)
{
//fire trace event
EventTrace.NormalTraceEvent(EventTraceGuidId.ADDANNOTATIONGUID, EventType.StartEvent);
CheckStatus();
XPathNavigator editor = GetAnnotationNodeForId(newAnnotation.Id);
// we are making sure that the newAnnotation doesn't already exist in the store
if (editor != null)
throw new ArgumentException(SR.Get(SRID.AnnotationAlreadyExists), "newAnnotation");
// we are making sure that the newAnnotation doesn't already exist in the store map
if (_storeAnnotationsMap.FindAnnotation(newAnnotation.Id) != null)
throw new ArgumentException(SR.Get(SRID.AnnotationAlreadyExists), "newAnnotation");
// simply add the annotation to the map to save on performance
// notice that we need to tell the map that this instance of the annotation is dirty
_storeAnnotationsMap.AddAnnotation(newAnnotation, true);
//fire trace event
EventTrace.NormalTraceEvent(EventTraceGuidId.ADDANNOTATIONGUID, EventType.EndEvent);
}
OnStoreContentChanged(new StoreContentChangedEventArgs(StoreContentAction.Added, newAnnotation));
}
///
/// Delete the specified annotation.
///
/// the Id of the annotation to be deleted
/// the annotation that was deleted, or null if no annotation
/// with the specified Id was found
/// if no stream has been set on the store
/// if object has been disposed
public override Annotation DeleteAnnotation(Guid annotationId)
{
Annotation annotation = null;
// We are now going to modify internal data. Lock the object
// to avoid modifications from other threads
lock (SyncRoot)
{
//fire trace event
EventTrace.NormalTraceEvent(EventTraceGuidId.DELETEANNOTATIONGUID, EventType.StartEvent);
CheckStatus();
annotation = _storeAnnotationsMap.FindAnnotation(annotationId);
XPathNavigator editor = GetAnnotationNodeForId(annotationId);
if (editor != null)
{
// Only deserialize the annotation if its not already in our map
if (annotation == null)
{
annotation = (Annotation)_serializer.Deserialize(editor.ReadSubtree());
}
editor.DeleteSelf();
}
// Remove the instance from the map
_storeAnnotationsMap.RemoveAnnotation(annotationId);
// notice that in Add we add the annotation to the map only
// but in delete we delete it from both to the Xml and the map
//fire trace event
EventTrace.NormalTraceEvent(EventTraceGuidId.DELETEANNOTATIONGUID, EventType.EndEvent);
}
// Only fire notification if we actually removed an annotation
if (annotation != null)
{
OnStoreContentChanged(new StoreContentChangedEventArgs(StoreContentAction.Deleted, annotation));
}
return annotation;
}
///
/// Queries the Xml stream for annotations that have an anchor
/// that contains a locator that begins with the locator parts
/// in anchorLocator.
///
/// the locator we are looking for
///
/// A list of annotations that have locators in their anchors
/// starting with the same locator parts list as of the input locator
/// If no such annotations an empty list will be returned. The method
/// never returns null.
///
/// if object has been Disposed
/// the stream is null
public override IList GetAnnotations(ContentLocator anchorLocator)
{
// First we generate the XPath expression
if (anchorLocator == null)
throw new ArgumentNullException("anchorLocator");
if (anchorLocator.Parts == null)
throw new ArgumentNullException("anchorLocator.Parts");
//fire trace event
EventTrace.NormalTraceEvent(EventTraceGuidId.GETANNOTATIONBYLOCGUID, EventType.StartEvent);
string query = @"//" + AnnotationXmlConstants.Prefixes.CoreSchemaPrefix + ":" + AnnotationXmlConstants.Elements.ContentLocator;
if (anchorLocator.Parts.Count > 0)
{
query += @"/child::*[1]/self::";
for (int i = 0; i < anchorLocator.Parts.Count; i++)
{
if (anchorLocator.Parts[i] != null)
{
if (i > 0)
{
query += @"/following-sibling::";
}
string fragment = anchorLocator.Parts[i].GetQueryFragment(_namespaceManager);
if (fragment != null)
{
query += fragment;
}
else
{
query += "*";
}
}
}
}
query += @"/ancestor::" + AnnotationXmlConstants.Prefixes.CoreSchemaPrefix + ":Anchors/ancestor::" + AnnotationXmlConstants.Prefixes.CoreSchemaPrefix + ":Annotation";
//fire trace event
EventTrace.NormalTraceEvent(EventTraceGuidId.GETANNOTATIONBYLOCGUID, EventType.EndEvent);
return InternalGetAnnotations(query, anchorLocator);
}
///
/// Returns a list of all annotations in the store
///
/// annotations list. Can return an empty list, but never null.
/// if object has been disposed
public override IList GetAnnotations()
{
//fire trace event
EventTrace.NormalTraceEvent(EventTraceGuidId.GETANNOTATIONSGUID, EventType.StartEvent);
string query = "//" + AnnotationXmlConstants.Prefixes.CoreSchemaPrefix + ":Annotation";
return InternalGetAnnotations(query, null);
//fire trace event
// unreachable - previous statement is 'return' (bug 1788240)
#pragma warning disable 0162
EventTrace.NormalTraceEvent(EventTraceGuidId.GETANNOTATIONSGUID, EventType.EndEvent);
#pragma warning restore 0162
}
///
/// Finds annotation by Id
///
/// annotation id
/// The annotation. Null if the annotation does not exists
/// if object has been disposed
public override Annotation GetAnnotation(Guid annotationId)
{
lock (SyncRoot)
{
//fire trace event
EventTrace.NormalTraceEvent(EventTraceGuidId.GETANNOTATIONBYIDGUID, EventType.StartEvent);
CheckStatus();
Annotation annotation = _storeAnnotationsMap.FindAnnotation(annotationId);
// If there is no pre-existing instance, we deserialize and create an instance.
if (annotation != null)
{
return annotation;
}
XPathNavigator editor = GetAnnotationNodeForId(annotationId);
if (editor != null)
{
annotation = (Annotation)_serializer.Deserialize(editor.ReadSubtree());
// Add the new instance to the map
_storeAnnotationsMap.AddAnnotation(annotation, false);
}
//fire trace event
EventTrace.NormalTraceEvent(EventTraceGuidId.GETANNOTATIONBYIDGUID, EventType.EndEvent);
return annotation;
}
}
///
/// Causes any buffered data to be written to the underlying
/// storage mechanism. Gets called after each operation if
/// AutoFlush is set to true. The stream is truncated to
/// the length of the data written out by the store.
///
/// stream cannot be written to
/// if no stream has been set on the store
/// if object has been disposed
///
public override void Flush()
{
lock (SyncRoot)
{
CheckStatus();
if (!_stream.CanWrite)
{
throw new UnauthorizedAccessException(SR.Get(SRID.StreamCannotBeWritten));
}
if (_dirty)
{
SerializeAnnotations();
_stream.Position = 0;
_stream.SetLength(0);
_document.PreserveWhitespace = true;
_document.Save(_stream);
_stream.Flush();
_dirty = false;
}
}
}
///
/// Returns a list of namespaces that are compatible with an input namespace
///
/// namespace
/// a list of compatible namespaces. Can be null
/// This method works only with built-in AnnotationFramework namespaces.
/// For any other input namespace the return value will be null even if it is
/// registered with the XmlStreamStore ctor
public static IList GetWellKnownCompatibleNamespaces(Uri name)
{
if (name == null)
{
throw new ArgumentNullException("name");
}
if (_predefinedNamespaces.ContainsKey(name))
return _predefinedNamespaces[name];
return null;
}
#endregion Public Methods
//------------------------------------------------------
//
// Public Operators
//
//------------------------------------------------------
//-----------------------------------------------------
//
// Public Events
//
//------------------------------------------------------
//-----------------------------------------------------
//
// Public Properties
//
//-----------------------------------------------------
#region Public Properties
///
/// When set to true an implementation should call Flush()
/// as a side-effect after each operation.
///
///
/// true if the implementation is set to call Flush() after
/// each operation; false otherwise
///
public override bool AutoFlush
{
get
{
lock (SyncRoot)
{
return _autoFlush;
}
}
set
{
lock (SyncRoot)
{
_autoFlush = value;
// Commit anything that needs to be committed up to this point
if (_autoFlush)
{
Flush();
}
}
}
}
///
/// Returns a list of the namespaces that are ignored while loading
/// the Xml stream
///
/// The value is never null, but can be an empty list if nothing has been ignored
public IList IgnoredNamespaces
{
get
{
return _ignoredNamespaces;
}
}
///
/// Returns a list of all namespaces that are internaly used by the framework
///
public static IList WellKnownNamespaces
{
get
{
Uri[] res = new Uri[_predefinedNamespaces.Keys.Count];
_predefinedNamespaces.Keys.CopyTo(res, 0);
return res;
}
}
#endregion Public Properties
//-----------------------------------------------------
//
// Protected Methods
//
//------------------------------------------------------
#region Protected Methods
///
/// Disposes of the resources (other than memory) used by the store.
///
/// true to release both managed and unmanaged
/// resources; false to release only unmanaged resources
protected override void Dispose(bool disposing)
{
//call the base class first to set _disposed to false
//in order to avoid working with the store when the resources
//are released
base.Dispose(disposing);
if (disposing)
{
Cleanup();
}
}
///
/// Called after every annotation action on the store. We override it
/// to update the dirty state of the store.
///
/// arguments for the event to fire
protected override void OnStoreContentChanged(StoreContentChangedEventArgs e)
{
lock (SyncRoot)
{
_dirty = true;
}
base.OnStoreContentChanged(e);
}
#endregion Protected Methods
//-----------------------------------------------------
//
// Private Methods
//
//------------------------------------------------------
#region Private Methods
///
/// Applies the specified XPath expression to the store and returns
/// the results.
///
/// the XPath expression to be applied to the store
///
/// An IList containing zero or more annotations that match the
/// criteria in the XPath expression; never will return null. If
/// no annotations meet the criteria, an empty list is returned.
///
/// if no stream has been set on the store
/// if object has been Disposed
/// queryExpression is null
/// queryExpression is empty string
private List FindAnnotationIds(string queryExpression)
{
Invariant.Assert(queryExpression != null && queryExpression.Length > 0,
"Invalid query expression");
Guid annId;
List retObj = null;
// Lock the object so nobody can change the document
// while the query is executed
lock (SyncRoot)
{
CheckStatus();
XPathNavigator navigator = _document.CreateNavigator();
XPathNodeIterator iterator = navigator.Select(queryExpression, _namespaceManager);
if (iterator != null && iterator.Count > 0)
{
retObj = new List(iterator.Count);
foreach (XPathNavigator node in iterator)
{
string nodeId = node.GetAttribute("Id", "");
if (String.IsNullOrEmpty(nodeId))
{
throw new XmlException(SR.Get(SRID.RequiredAttributeMissing, AnnotationXmlConstants.Attributes.Id, AnnotationXmlConstants.Elements.Annotation));
}
try
{
annId = XmlConvert.ToGuid(nodeId);
}
catch (FormatException fe)
{
throw new InvalidOperationException(SR.Get(SRID.CannotParseId), fe);
}
retObj.Add(annId);
}
}
else
{
retObj = new List(0);
}
}
return retObj;
}
///
/// Used as AuthorChanged event handler for all annotations
/// handed out by the map.
///
/// annotation that sent the event
/// args for the event
private void HandleAuthorChanged(object sender, AnnotationAuthorChangedEventArgs e)
{
lock (SyncRoot)
{
_dirty = true;
}
OnAuthorChanged(e);
}
///
/// Used as AnchorChanged event handler for all annotations
/// handed out by the map.
///
/// annotation that sent the event
/// args for the event
private void HandleAnchorChanged(object sender, AnnotationResourceChangedEventArgs e)
{
lock (SyncRoot)
{
_dirty = true;
}
OnAnchorChanged(e);
}
///
/// Used as CargoChanged event handler for all annotations
/// handed out by the map.
///
/// annotation that sent the event
/// args for the event
private void HandleCargoChanged(object sender, AnnotationResourceChangedEventArgs e)
{
lock (SyncRoot)
{
_dirty = true;
}
OnCargoChanged(e);
}
///
/// 1- Merge Annotations queried from both the map and the Xml stream
/// 2- Add the annotations found in the Xml stream to the map
///
/// A dictionary of map annotations
/// A list of annotation ids from the Xml stream
///
private IList MergeAndCacheAnnotations(Dictionary mapAnnotations, List storeAnnotationsId)
{
// first put all annotations from the map in the return list
List annotations = new List((IEnumerable)mapAnnotations.Values);
// there three possible conditions
// 1- An annotation exists in xml and in the store map results
// 2- An annotation exists in xml and not in the store map results
// 2-1- The annotation is found in the map
// 2-2- The annotation is not found in the map
// Now, we need to find annotations in the store that are not in the map results
// and verify that they should be serialized
foreach (Guid annotationId in storeAnnotationsId)
{
Annotation annot;
bool foundInMapResults = mapAnnotations.TryGetValue(annotationId, out annot);
if (!foundInMapResults)
{
// it is not in the map - get it from the store
annot = GetAnnotation(annotationId);
annotations.Add(annot);
}
}
return annotations;
}
///
/// Do the GetAnnotations work inside a lock statement for thread safety reasons
///
///
///
///
private IList InternalGetAnnotations(string query, ContentLocator anchorLocator)
{
// anchorLocator being null is handled appropriately below
Invariant.Assert(query != null, "Parameter 'query' is null.");
lock (SyncRoot)
{
CheckStatus();
List annotationIds = FindAnnotationIds(query);
Dictionary annotations = null;
// Now, get the annotations in the map that satisfies the query criterion
if (anchorLocator == null)
{
annotations = _storeAnnotationsMap.FindAnnotations();
}
else
{
annotations = _storeAnnotationsMap.FindAnnotations(anchorLocator);
}
// merge both query results
return MergeAndCacheAnnotations(annotations, annotationIds);
}
}
///
/// Loads the current stream into the XmlDocument used by this
/// store as a backing store. If the stream doesn't contain any
/// data we load up a default XmlDocument for the Annotations schema.
/// The "http://schemas.microsoft.com/windows/annotations/2003/11/core"
/// namespace is registered with "anc" prefix and the
/// "http://schemas.microsoft.com/windows/annotations/2003/11/base" namespace
/// is registered with "anb" prefix as global namespaces.
/// We also select from the newly loaded document the new top
/// level node. This is used later for insertions, etc.
///
/// if the stream contains invalid XML
private void LoadStream(IDictionary> knownNamespaces)
{
//check input data first
CheckKnownNamespaces(knownNamespaces);
lock (SyncRoot)
{
_document = new XmlDocument();
_document.PreserveWhitespace = false;
if (_stream.Length == 0)
{
_document.LoadXml(" <" +
AnnotationXmlConstants.Prefixes.CoreSchemaPrefix + ":Annotations xmlns:" + AnnotationXmlConstants.Prefixes.CoreSchemaPrefix + "=\"" +
AnnotationXmlConstants.Namespaces.CoreSchemaNamespace + "\" xmlns:" + AnnotationXmlConstants.Prefixes.BaseSchemaPrefix + "=\"" + AnnotationXmlConstants.Namespaces.BaseSchemaNamespace + "\" />");
}
else
{
_xmlCompatibilityReader = SetupReader(knownNamespaces);
_document.Load(_xmlCompatibilityReader);
}
_namespaceManager = new XmlNamespaceManager(_document.NameTable);
_namespaceManager.AddNamespace(AnnotationXmlConstants.Prefixes.CoreSchemaPrefix, AnnotationXmlConstants.Namespaces.CoreSchemaNamespace);
_namespaceManager.AddNamespace(AnnotationXmlConstants.Prefixes.BaseSchemaPrefix, AnnotationXmlConstants.Namespaces.BaseSchemaNamespace);
// This use of an iterator is necessary because SelectSingleNode isn't available in the PD3
// drop of the CLR. Eventually we should be able to call SelectSingleNode and not have to
// use an iterator to get a single node.
XPathNavigator navigator = _document.CreateNavigator();
XPathNodeIterator iterator = navigator.Select("//" + AnnotationXmlConstants.Prefixes.CoreSchemaPrefix + ":Annotations", _namespaceManager);
Invariant.Assert(iterator.Count == 1, "More than one annotation returned for the query");
iterator.MoveNext();
_rootNavigator = (XPathNavigator)iterator.Current;
}
}
///
/// Checks if passed external known namespaces are valid
///
/// namespaces dictionary
/// We do not allow internal namespases in this dictionary nor
/// duplicates
private void CheckKnownNamespaces(IDictionary> knownNamespaces)
{
if (knownNamespaces == null)
return;
IList allNamespaces = new List();
//add AnnotationFramework namespaces
foreach (Uri name in _predefinedNamespaces.Keys)
{
allNamespaces.Add(name);
}
//add external namespaces
foreach (Uri knownNamespace in knownNamespaces.Keys)
{
if (knownNamespace == null)
{
throw new ArgumentException(SR.Get(SRID.NullUri), "knownNamespaces");
}
if (allNamespaces.Contains(knownNamespace))
{
throw new ArgumentException(SR.Get(SRID.DuplicatedUri), "knownNamespaces");
}
allNamespaces.Add(knownNamespace);
}
// check compatible namespaces
foreach (KeyValuePair> item in knownNamespaces)
{
if (item.Value != null)
{
foreach (Uri name in item.Value)
{
if (name == null)
{
throw new ArgumentException(SR.Get(SRID.NullUri), "knownNamespaces");
}
if (allNamespaces.Contains(name))
{
throw new ArgumentException(SR.Get(SRID.DuplicatedCompatibleUri), "knownNamespaces");
}
allNamespaces.Add(name);
}//foreach
}//if
}//foreach
}
///
/// Creates and initializes the XmlCompatibilityReader
///
/// Dictionary of external known namespaces
/// The XmlCompatibilityReader
private XmlCompatibilityReader SetupReader(IDictionary> knownNamespaces)
{
IList supportedNamespaces = new List();
//add AnnotationFramework namespaces
foreach (Uri name in _predefinedNamespaces.Keys)
{
supportedNamespaces.Add(name.ToString());
}
//add external namespaces
if (knownNamespaces != null)
{
foreach (Uri knownNamespace in knownNamespaces.Keys)
{
Debug.Assert(knownNamespace != null, "null knownNamespace");
supportedNamespaces.Add(knownNamespace.ToString());
}
}
//create XmlCompatibilityReader first
XmlCompatibilityReader reader = new XmlCompatibilityReader(new XmlTextReader(_stream),
new IsXmlNamespaceSupportedCallback(IsXmlNamespaceSupported), supportedNamespaces);
// Declare compatibility.
// Skip the Framework ones because they are all null in this version
if (knownNamespaces != null)
{
foreach (KeyValuePair> item in knownNamespaces)
{
if (item.Value != null)
{
foreach (Uri name in item.Value)
{
Debug.Assert(name != null, "null compatible namespace");
reader.DeclareNamespaceCompatibility(item.Key.ToString(), name.ToString());
}//foreach
}//if
}//foreach
}//if
//cleanup the _ignoredNamespaces
_ignoredNamespaces.Clear();
return reader;
}
///
/// A callback for XmlCompatibilityReader
///
/// inquired namespace
/// the newer subsumming namespace
/// true if the namespace is known, false if not
/// This API always returns false because all known namespaces are registered
/// before loading the Xml. It stores the namespace as ignored.
private bool IsXmlNamespaceSupported(string xmlNamespace, out string newXmlNamespace)
{
//store the namespace if needed
if (!String.IsNullOrEmpty(xmlNamespace))
{
if (!Uri.IsWellFormedUriString(xmlNamespace, UriKind.RelativeOrAbsolute))
{
throw new ArgumentException(SR.Get(SRID.InvalidNamespace, xmlNamespace), "xmlNamespace");
}
Uri namespaceUri = new Uri(xmlNamespace, UriKind.RelativeOrAbsolute);
if (!_ignoredNamespaces.Contains(namespaceUri))
_ignoredNamespaces.Add(namespaceUri);
}
newXmlNamespace = null;
return false;
}
///
/// Selects the annotation XmlElement from the current document
/// with the given id.
///
/// the id to query for in the XmlDocument
/// the XmlElement representing the annotation with the given id
private XPathNavigator GetAnnotationNodeForId(Guid id)
{
XPathNavigator navigator = null;
lock (SyncRoot)
{
XPathNavigator tempNavigator = _document.CreateNavigator();
// This use of an iterator is necessary because SelectSingleNode isn't available in the PD3
// drop of the CLR. Eventually we should be able to call SelectSingleNode and not have to
// use an iterator to get a single node.
// We use XmlConvert.ToString to turn the Guid into a string because
// that's what is used by the Annotation's serialization methods.
XPathNodeIterator iterator = tempNavigator.Select(@"//" + AnnotationXmlConstants.Prefixes.CoreSchemaPrefix + @":Annotation[@Id=""" + XmlConvert.ToString(id) + @"""]", _namespaceManager);
if (iterator.MoveNext())
{
navigator = (XPathNavigator)iterator.Current;
}
}
return navigator;
}
///
/// Verifies the store is in a valid state. Throws exceptions otherwise.
///
private void CheckStatus()
{
lock (SyncRoot)
{
if (IsDisposed)
throw new ObjectDisposedException(null, SR.Get(SRID.ObjectDisposed_StoreClosed));
if (_stream == null)
throw new InvalidOperationException(SR.Get(SRID.StreamNotSet));
}
}
///
/// Called from flush to serialize all annotations in the map
/// notice that delete takes care of the delete action both in the map
/// and in the store
///
private void SerializeAnnotations()
{
List mapAnnotations = _storeAnnotationsMap.FindDirtyAnnotations();
foreach (Annotation annotation in mapAnnotations)
{
XPathNavigator editor = GetAnnotationNodeForId(annotation.Id);
if (editor == null)
{
editor = (XPathNavigator)_rootNavigator.CreateNavigator();
XmlWriter writer = editor.AppendChild();
_serializer.Serialize(writer, annotation);
writer.Close();
}
else
{
XmlWriter writer = editor.InsertBefore();
_serializer.Serialize(writer, annotation);
writer.Close();
editor.DeleteSelf();
}
}
_storeAnnotationsMap.ValidateDirtyAnnotations();
}
///
/// Discards changes and cleans up references.
///
private void Cleanup()
{
lock (SyncRoot)
{
_xmlCompatibilityReader = null;
_ignoredNamespaces = null;
_stream = null;
_document = null;
_rootNavigator = null;
_storeAnnotationsMap = null;
}
}
///
/// Sets the stream for this store. Assumes Cleanup has
/// been previously called (or this is a new store).
///
/// new stream for this store
/// List of known and compatible namespaces used to initialize
/// the XmlCompatibilityReader
private void SetStream(Stream stream, IDictionary> knownNamespaces)
{
try
{
lock (SyncRoot)
{
_storeAnnotationsMap = new StoreAnnotationsMap(HandleAuthorChanged, HandleAnchorChanged, HandleCargoChanged);
_stream = stream;
LoadStream(knownNamespaces);
}
}
catch
{
Cleanup();
throw;
}
}
#endregion Private Methods
//------------------------------------------------------
//
// Private Fields
//
//-----------------------------------------------------
#region Private Fields
// Dirty bit for the store's in-memory cache
private bool _dirty;
// Boolean flag represents whether the store should perform a flush
// after every operation or not
private bool _autoFlush;
// XmlDocument used as an in-memory cache of the stream's XML content
private XmlDocument _document;
// Namespace manager used for all queries.
private XmlNamespaceManager _namespaceManager;
// Stream passed in by the creator of this instance.
private Stream _stream;
// The xpath navigator used to navigate the annotations Xml stream
private XPathNavigator _rootNavigator;
// map that holds AnnotationId->Annotation
StoreAnnotationsMap _storeAnnotationsMap;
//list of ignored namespaces during XmlLoad
List _ignoredNamespaces = new List();
//XmlCompatibilityReader - we need to hold that one open, so the underlying stream stays open too
// if the store is disposed the reader will be disposed and the stream will be closed too.
XmlCompatibilityReader _xmlCompatibilityReader;
///
///Static fields
///
//predefined namespaces
private static readonly Dictionary> _predefinedNamespaces;
// Serializer for Annotations
private static readonly Serializer _serializer = new Serializer(typeof(Annotation));
#endregion Private Fields
}
}
// File provided for Reference Use Only by Microsoft Corporation (c) 2007.
// Copyright (c) Microsoft Corporation. All rights reserved.
Link Menu

This book is available now!
Buy at Amazon US or
Buy at Amazon UK
- Propagator.cs
- SimpleBitVector32.cs
- HwndSubclass.cs
- MediaContextNotificationWindow.cs
- OdbcTransaction.cs
- GroupItemAutomationPeer.cs
- DecryptedHeader.cs
- WebPartMinimizeVerb.cs
- XmlWellformedWriter.cs
- DbConvert.cs
- ToolTip.cs
- FolderLevelBuildProviderAppliesToAttribute.cs
- AsyncResult.cs
- VsPropertyGrid.cs
- DataRecord.cs
- WebRequestModuleElement.cs
- ControlValuePropertyAttribute.cs
- LOSFormatter.cs
- Decorator.cs
- WebPartManagerDesigner.cs
- TypedElement.cs
- AuthenticationConfig.cs
- Schema.cs
- NavigationService.cs
- ConfigurationHelpers.cs
- TableLayoutColumnStyleCollection.cs
- SerializerDescriptor.cs
- ElementNotAvailableException.cs
- CellLabel.cs
- CompositeActivityValidator.cs
- SiteOfOriginPart.cs
- DataListCommandEventArgs.cs
- ContentPosition.cs
- NestedContainer.cs
- MultipartIdentifier.cs
- GroupItem.cs
- ConditionedDesigner.cs
- StateManagedCollection.cs
- FamilyCollection.cs
- TraceLevelHelper.cs
- SystemIPInterfaceProperties.cs
- coordinatorfactory.cs
- NoClickablePointException.cs
- XmlSchemaGroupRef.cs
- sqlser.cs
- ToolStripDropTargetManager.cs
- HitTestParameters.cs
- MethodCallTranslator.cs
- HtmlSelect.cs
- Msec.cs
- GetIndexBinder.cs
- DetailsViewPagerRow.cs
- UniformGrid.cs
- XmlNamespaceMappingCollection.cs
- CustomErrorCollection.cs
- XhtmlBasicPageAdapter.cs
- OleDbFactory.cs
- ConfigDefinitionUpdates.cs
- ResourceDisplayNameAttribute.cs
- ButtonField.cs
- Win32MouseDevice.cs
- StoreItemCollection.cs
- DeleteBookmarkScope.cs
- RequestNavigateEventArgs.cs
- DataGridViewDataConnection.cs
- FormsAuthenticationEventArgs.cs
- XmlDeclaration.cs
- QilCloneVisitor.cs
- ClientRolePrincipal.cs
- CorePropertiesFilter.cs
- FrameworkObject.cs
- SingleStorage.cs
- BuildProvidersCompiler.cs
- PageThemeBuildProvider.cs
- TextTreeTextBlock.cs
- ConfigurationStrings.cs
- Delegate.cs
- securitycriticaldata.cs
- _ProxyRegBlob.cs
- HelloMessageApril2005.cs
- SchemaMapping.cs
- BinHexEncoder.cs
- Ref.cs
- BitmapCacheBrush.cs
- HtmlElementCollection.cs
- AnnotationResourceChangedEventArgs.cs
- DataGridDefaultColumnWidthTypeConverter.cs
- QueryOutputWriter.cs
- Transform3DGroup.cs
- UserThread.cs
- AsyncOperationManager.cs
- CmsInterop.cs
- ProjectionPlanCompiler.cs
- ChainedAsyncResult.cs
- DataGridItem.cs
- TemplateContentLoader.cs
- ApplicationDirectoryMembershipCondition.cs
- PrintPreviewGraphics.cs
- LogExtent.cs
- MailSettingsSection.cs