/// /// Copyright © 2003-2008 JetBrains s.r.o. /// You may distribute under the terms of the GNU General Public License, as published by the Free Software Foundation, version 2 (see License.txt in the repository root folder). /// using System; using System.Collections; using System.Diagnostics; using System.IO; using System.Text; using JetBrains.Omea.Base; using JetBrains.Omea.Containers; using JetBrains.Omea.OpenAPI; using JetBrains.Omea.Database; using JetBrains.DataStructures; namespace JetBrains.Omea.ResourceStore { /** * A resource is any object living in the MyPal database. Resources have * properties (of string, int and date types) and are linked to other resources. * Properties are stored in the hashtable which Resource inherits. */ public class Resource: IntHashTable, IResource { internal class PropertyCollection: IPropertyCollection { private Resource _owner; internal PropertyCollection( Resource owner ) { _owner = owner; } public int Count { get { int count = 0; _owner.Lock(); try { foreach( Entry e in _owner ) { IntArrayList linkList = e.Value as IntArrayList; if ( linkList == null || linkList.Count > 0 ) { count++; } } } finally { _owner.UnLock(); } return count; } } public IEnumerator GetEnumerator() { return new PropertyEnumerator( _owner ); } } public class PropertyEnumerator: IEnumerator { private Resource _owner; private IEnumerator _entryEnumerator; internal PropertyEnumerator( Resource owner ) { _owner = owner; _entryEnumerator = owner.GetEnumerator(); } public void Reset() { _entryEnumerator.Reset(); } object IEnumerator.Current { get { int propId = ((Entry) _entryEnumerator.Current).Key; return new ResourceProperty( _owner, propId ); } } public IResourceProperty Current { get { int propId = ((Entry) _entryEnumerator.Current).Key; return new ResourceProperty( _owner, propId ); } } public bool MoveNext() { while( _entryEnumerator.MoveNext() ) { IntArrayList list = ((Entry) _entryEnumerator.Current).Value as IntArrayList; if ( list == null || list.Count > 0 ) { return true; } } return false; } } internal class ResourceProperty: IResourceProperty { private Resource _owner; private int _propId; internal ResourceProperty( Resource owner, int propId ) { _owner = owner; _propId = propId; } public string Name { get { return MyPalStorage.Storage.GetPropName( _propId ); } } public int PropId { get { return _propId; } } public PropDataType DataType { get { return MyPalStorage.Storage.GetPropDataType( _propId ); } } public object Value { get { if ( MyPalStorage.Storage.GetPropDataType( _propId ) == PropDataType.Link ) return _owner.GetLinkProp( _propId ); else { _owner.Lock(); try { return _owner.GetPropValue( _propId ); } finally { _owner.UnLock(); } } } } } private int _ID; private short _typeID; private ushort _propLoadedMask; private string _displayName = null; private SpinWaitLock _sync = new SpinWaitLock(); private const ushort RESOURCE_DELETING_MASK = 0x8000; private const ushort RESOURCE_TRANSIENT_MASK = 0x4000; private static object _true = true; /** * Creates a new or lazy-loaded resource with the specified type. */ internal Resource( int ID, int typeID, bool isNew ) { _ID = ID; if ( isNew ) { Debug.Assert( typeID >= 0 ); _typeID = (short) typeID; _propLoadedMask = 0x3FFF; } else { if ( typeID >= 0 ) { _typeID = (short) typeID; } else { _typeID = (short) MyPalStorage.Storage.LoadResourceType( _ID, true ); } } } /** * Returns the ID of the resource, or -1 if the resource has been deleted. */ public int Id { [DebuggerStepThrough] get { return _ID; } } public int OriginalId { get { if ( _ID == -1 ) { Lock(); try { return (int) this[ResourceProps.Id]; } finally { UnLock(); } } return _ID; } } /** * Returns the type of the resource. */ public string Type { get { return ((ResourceTypeCollection) MyPalStorage.Storage.ResourceTypes).GetItemSafe( _typeID ).Name; } } /** * Returns the ID of the type of the resource. */ public int TypeId { get { return _typeID; } } /** * Returns the display name of the resource (the default text representation * that will be shown to users). */ public string DisplayName { get { if (_displayName == null) CalcDisplayName(); return _displayName; } set { SetProp( "_DisplayName", value ); _displayName = value; } } public void Lock() { _sync.Enter(); } public bool TryLock() { return _sync.TryEnter(); } public void UnLock() { _sync.Exit(); } internal void SetTransient() { _propLoadedMask |= RESOURCE_TRANSIENT_MASK; } public bool IsTransient { get { return ( _propLoadedMask & RESOURCE_TRANSIENT_MASK ) != 0; } } public bool IsDeleting { get { return ( _propLoadedMask & RESOURCE_DELETING_MASK ) != 0; } } public bool IsDeleted { get { return _ID == -1; } } public IResourceStore ResourceStore { get { return MyPalStorage.Storage; } } /** * Invalidates the display name of the resource, so that it would be * recalculated when it is requested again. */ internal void InvalidateDisplayName() { _displayName = null; } /** * Calculates the display name from the display name mask. */ private void CalcDisplayName() { string staticDisplayName = GetStringProp( "_DisplayName" ); if ( staticDisplayName != null ) { _displayName = staticDisplayName; return; } if ( MyPalStorage.Storage == null ) { _displayName = ""; } else { DisplayNameMask displayNameMask = MyPalStorage.Storage.GetResourceTypeDisplayNameMask( _typeID ); _displayName = displayNameMask.GetValue( this ); if ( _displayName == null || _displayName.Length == 0 ) { _displayName = MyPalStorage.Storage.CalcCustomDisplayName( this ); } } } public override string ToString() { string result = DisplayName; if ( result.Length == 0 ) { result = "<" + Type + " ID=" + Id + ">"; } return result; } /** * Returns the collection allowing easy enumeration of the properties of the resource. */ public IPropertyCollection Properties { get { CheckLoadAllProperties(); return new PropertyCollection( this ); } } public void ClearProperties() { if( IsTransient ) { Clear(); _propLoadedMask = 0x3FFF; SetTransient(); } } internal bool PropertiesLoaded( PropDataType propType ) { return ( _propLoadedMask & ( 1 << (int) propType ) ) != 0; } /** * Sets a property of the resource to the specified value. */ public void SetProp( string propName, object propValue ) { int propId = MyPalStorage.Storage.GetPropId( propName ); SetProp( propId, propValue ); } public void SetProp(PropId propId, T value) { SetProp( propId.Id, value ); } public void SetReverseLinkProp(PropId propId, IResource propValue) { SetProp(-propId.Id, propValue); } /** * Sets a property with the specified ID to the specified value. */ public void SetProp( int propId, object propValue ) { IPropType propType = MyPalStorage.Storage.PropTypes [propId]; PropDataType dataType = propType.DataType; if ( propType.HasFlag( PropTypeFlags.Virtual ) ) { throw new StorageException( "Cannot use SetProp to set virtual properties" ); } if ( propValue != null ) { MyPalStorage.Storage.CheckValueType( propId, dataType, propValue ); } else { DeleteProp( propId ); return; } if ( dataType == PropDataType.Link ) { SetLinkProp( propId, (IResource) propValue ); return; } if ( dataType == PropDataType.Date && (DateTime) propValue == DateTime.MinValue ) { DeleteProp( propId ); return; } if ( dataType == PropDataType.StringList ) { throw new StorageException( "Please use IResource.GetStringListProp() and methods of IStringList " + " to change values of string list properties" ); } if ( dataType == PropDataType.Blob && propValue is string ) { propValue = new JetMemoryStream( Encoding.UTF8.GetBytes( (string) propValue ), true ); } object oldValue; Lock(); try { if ( _ID == -1 ) throw new ResourceDeletedException(); CheckLoadProperties( dataType ); bool newProp = false; oldValue = GetPropValue( propId ); if ( dataType == PropDataType.Bool ) { bool oldBoolValue = (oldValue != null); bool boolValue = (bool) propValue; if ( oldBoolValue == boolValue ) return; if( boolValue ) { this[ propId ] = _true; } else { Remove( propId ); } } else { if( oldValue == null ) { newProp = true; } else if( oldValue.Equals( propValue ) ) { return; } if( newProp || dataType != PropDataType.Blob || IsTransient ) { this[propId] = propValue; } } if( !IsTransient ) { if ( dataType == PropDataType.Bool ) { if ( (bool) propValue ) { MyPalStorage.Storage.CreateBoolProperty( this, propId ); } else { MyPalStorage.Storage.DeleteBoolProperty( this, propId ); } } else { if ( newProp ) { MyPalStorage.Storage.CreateProperty( this, propId, propValue ); if ( dataType == PropDataType.Blob ) { // propValue passed to the function is a stream, and the props hash // must actually contain a blob this[ propId ] = MyPalStorage.Storage.GetBlobProperty( _ID, propId ); } } else { MyPalStorage.Storage.UpdateProperty( this, propId, propValue ); } } } } finally { UnLock(); } MyPalStorage.Storage.OnResourceSaved( this, propId, oldValue ); } /** * Sets a link property with the specified ID to the specified value. */ private void SetLinkProp( int propId, IResource propValue ) { if ( propValue == null ) { DeleteLinks( propId ); } else { // in order not to break link restrictions, BeginUpdate/EndUpdate wrapper is needed BeginUpdate(); try { if ( !DeleteLinksExcept( propId, propValue.Id ) ) { AddLink( propId, propValue ); } } finally { EndUpdate(); } } } /** * Deletes the property of the resource with the specified name. */ public void DeleteProp( string propName ) { DeleteProp( MyPalStorage.Storage.GetPropId( propName ) ); } /** * Deletes the property of the resource with the specified ID. */ public void DeleteProp( int propId ) { object oldValue; Lock(); try { if ( _ID == -1 ) throw new ResourceDeletedException(); PropDataType propType = MyPalStorage.Storage.GetPropDataType( propId ); if ( propType == PropDataType.Link ) { DeleteLinks( propId ); return; } CheckLoadProperties( propType ); oldValue = GetPropValue( propId ); if ( oldValue != null ) { Remove( propId ); if ( !IsTransient ) { if ( propType == PropDataType.Bool ) { MyPalStorage.Storage.DeleteBoolProperty( this, propId ); } else { MyPalStorage.Storage.DeleteProperty( this, propId ); } } } } finally { UnLock(); } if ( oldValue != null ) { MyPalStorage.Storage.OnResourceSaved( this, propId, oldValue ); } } /** * Returns the value of the specified property of any type. */ public object GetProp( string propName ) { return GetProp( MyPalStorage.Storage.GetPropId( propName ) ); } public T GetProp(PropId propId) { return (T) GetProp(propId.Id); } public object GetProp( int propId ) { // this also checks if the property ID is valid PropDataType propType = MyPalStorage.Storage.GetPropDataType( propId ); if ( propType == PropDataType.Link ) { return GetLinkProp( propId ); } Lock(); try { CheckLoadProperties( propType ); object val = GetPropValue( propId ); if ( val == null && propType == PropDataType.Bool ) return false; return val; } finally { UnLock(); } } /** * Returns the value of the specified string property. Returns null if * there is no such property, throws an exception if the property is not * of the string type. */ public string GetStringProp( string propName ) { return GetStringProp( MyPalStorage.Storage.GetPropId( propName ) ); } /** * Returns the value of the string property with the specified ID. * Returns null if there is no such property, throws an exception * if the property is not of the string type. */ public string GetStringProp( int propId ) { return (string) GetPropObject( propId, PropDataType.String ); } /** * Returns the value of the specified integer property. Returns 0 if * there is no such property, throws an exception if the property is not * of the integer type. */ public int GetIntProp( string propName ) { return GetIntProp( MyPalStorage.Storage.GetPropId( propName ) ); } /** * Returns the value of the integer property with the specified ID. Returns 0 * there is no such property, throws an exception if the property is not * of the integer type. */ public int GetIntProp( int propId ) { object propValue = GetPropObject( propId, PropDataType.Int ); if ( propValue == null ) return 0; return (int) propValue; } /** * Returns the value of the date property with the specified name. */ public DateTime GetDateProp( string propName ) { return GetDateProp( MyPalStorage.Storage.GetPropId( propName ) ); } /** * Returns the value of the date property with the specified ID. */ public DateTime GetDateProp( int propId ) { object dateValue = GetPropObject( propId, PropDataType.Date ); if ( dateValue == null ) return DateTime.MinValue; return (DateTime) dateValue; } /** * Returns the value of the double property with the specified name. */ public double GetDoubleProp( string propName ) { return GetDoubleProp( MyPalStorage.Storage.GetPropId( propName ) ); } /** * Returns the value of the double property with the specified ID. */ public double GetDoubleProp( int propId ) { return (double) GetPropObject( propId, PropDataType.Double ); } /** * Returns the value of the blob property with the specified name. */ public Stream GetBlobProp( string propName ) { return GetBlobProp( MyPalStorage.Storage.GetPropId( propName ) ); } /** * Returns the value of the blob property with the specified ID. */ public Stream GetBlobProp( int propId ) { if ( _ID == -1 ) throw new ResourceDeletedException(); IBLOB blob = (IBLOB) GetPropObject( propId, PropDataType.Blob ); if ( blob != null ) { try { return blob.Stream; } catch( IOException ex ) { MyPalStorage.Storage.OnIOErrorDetected( ex ); } } return null; } /** * Returns the value of the string list property with the specified name. */ public IStringList GetStringListProp( string propName ) { return GetStringListProp( MyPalStorage.Storage.GetPropId( propName ) ); } /** * Returns the value of the string list property with the specified ID. */ public IStringList GetStringListProp( int propId ) { if ( MyPalStorage.Storage.GetPropDataType( propId ) != PropDataType.StringList ) { throw new StorageException( MyPalStorage.Storage.GetPropName( propId ) + " is not a StringList property" ); } Lock(); try { PropertyStringList stringList = (PropertyStringList) this[ propId ]; if ( stringList == null ) { stringList = new PropertyStringList( this, propId, IsTransient, IsTransient ); this[ propId ] = stringList; } return stringList; } finally { UnLock(); } } /** * Returns the first object connected to the current object by the link * of the specified type, or null if there is no such object. */ public IResource GetLinkProp( string propName ) { return GetLinkProp( MyPalStorage.Storage.GetPropId( propName ) ); } public IResource GetLinkProp( int propId ) { if ( MyPalStorage.Storage.GetPropDataType( propId ) != PropDataType.Link ) throw new StorageException( propId + " is not a link property" ); Lock(); try { IntArrayList linkList = GetLinkList( propId ); if ( linkList != null && linkList.Count > 0 ) { if ( !IsTransient ) { return MyPalStorage.Storage.TryLoadResource( linkList [0] ); } // for transient resources, the link list may contain deleted resources, // and no one notifies us about their deletion => skip deleted resources now while( linkList.Count > 0 ) { IResource res = MyPalStorage.Storage.TryLoadResource( linkList [0] ); if ( res != null ) { return res; } linkList.RemoveAt( 0 ); } } return null; } finally { UnLock(); } } public IResource GetReverseLinkProp(PropId propId) { return GetLinkProp(-propId.Id); } private class StringPropWeakReference : WeakReference { private int _offset; public StringPropWeakReference( object obj, int offset ) : base(obj ) { _offset = offset; } public int Offset { get { return _offset; } } } /// /// Returns the value of the property with the specified ID, performing the lazy-load /// of string properties if required. /// /// The ID of the property to return. /// The property value. internal object GetPropValue( int propId ) { object propValue = this[ propId ]; PropDataType dataType = MyPalStorage.Storage.PropTypes [propId].DataType; if( dataType == PropDataType.String || dataType == PropDataType.LongString ) { if ( propValue is Int32 ) { int offset = (int) propValue; IRecord rec = MyPalStorage.Storage.LoadPropertyRecord( dataType, offset ); propValue = rec.GetStringValue( 2 ); if( dataType == PropDataType.LongString ) { this[ propId ] = new StringPropWeakReference( propValue, offset ); } else { this[ propId ] = propValue; } } else if( propValue is StringPropWeakReference ) { StringPropWeakReference weakRef = (StringPropWeakReference) propValue; propValue = weakRef.Target; if( propValue == null ) { int offset = weakRef.Offset; IRecord rec = MyPalStorage.Storage.LoadPropertyRecord( PropDataType.LongString, offset ); propValue = rec.GetStringValue( 2 ); weakRef.Target = propValue; } } } Stream stream = propValue as Stream; if( stream != null ) { stream.Position = 0; } return propValue; } /** * Returns the list of resource links which have the specified type. */ private IntArrayList GetLinkList( int propId ) { IntArrayList result = (IntArrayList) this[ propId ]; if ( result == null && !PropertiesLoaded( PropDataType.Link ) ) { LoadLinks( propId ); result = (IntArrayList) this[ propId ]; } if ( result != null && result.Count == 0 ) { return null; } return result; } /** * Returns the value of the specified property, or null if there is no such * property. Throws an exception if the property is not of the specified type. */ private object GetPropObject( int propId, PropDataType expectType ) { PropDataType propType = MyPalStorage.Storage.GetPropDataType( propId ); if ( propType != expectType ) { // for LongString properties, the same GetStringProp() method is used, // so expectType will be PropType.String when propType is PropType.LongString, // which is not an error if ( (propType != PropDataType.LongString) || (expectType != PropDataType.String ) ) { throw new StorageException( MyPalStorage.Storage.GetPropName( propId ) + " is not a " + expectType + " property" ); } } Lock(); try { CheckLoadProperties( propType ); return GetPropValue( propId ); } finally { UnLock(); } } /** * Returns the textual (user-visible) representation of the specified * property. Supports link properties, too. */ public string GetPropText( string propName ) { return GetPropText( MyPalStorage.Storage.GetPropId( propName ) ); } /** * Returns the textual (user-visible) representation of the property * with the specified ID. */ public string GetPropText( int propId ) { PropDataType propType = MyPalStorage.Storage.GetPropDataType( propId ); if ( propType == PropDataType.Link ) return GetLinkPropText( propId ); object propValue; Lock(); try { CheckLoadProperties( propType ); propValue = GetPropValue( propId ); } finally { UnLock(); } if ( propValue == null ) { return string.Empty; } if( propType == PropDataType.Blob ) { IBLOB blob = propValue as IBLOB; return ( blob != null ) ? blob.ToString() : Utils.StreamToString( (Stream)propValue ); } return propValue.ToString(); } /** * Returns the value of the link property with the specified name (the display * names of all objects connected with that link type, separated with commas). */ private string GetLinkPropText( int propId ) { Lock(); try { IntArrayList resIDList = GetLinkList( propId ); if ( resIDList == null || resIDList.Count == 0 ) return ""; string resName0 = MyPalStorage.Storage.LoadResource( resIDList [0] ).DisplayName; if ( resIDList.Count == 1 ) { return resName0; } StringBuilder result = new StringBuilder( resName0 ); for( int i=1; i(PropId propId) { return HasProp(propId.Id); } private bool HasLinkProp( int propId ) { IntArrayList list; Lock(); try { list = (IntArrayList) this[ propId ]; if ( list != null ) { return list.Count > 0; } if( PropertiesLoaded( PropDataType.Link ) ) { return false; } } finally { UnLock(); } if ( propId > 0 || !MyPalStorage.Storage.IsLinkDirected( propId ) ) { using( IResultSet rs = MyPalStorage.Storage.GetLinksFrom( _ID, propId ) ) { SafeRecordValueEnumerator enumerator = new SafeRecordValueEnumerator( rs, "Resource.HasLinkProp" ); using( enumerator ) { if( enumerator.MoveNext() ) { return true; } } } } if ( propId < 0 || !MyPalStorage.Storage.IsLinkDirected( propId ) ) { int toPropId = propId; if ( propId < 0 ) { toPropId = -toPropId; } using( IResultSet rs = MyPalStorage.Storage.GetLinksTo( _ID, toPropId ) ) { SafeRecordValueEnumerator enumerator = new SafeRecordValueEnumerator( rs, "Resource.HasLinkProp" ); using( enumerator ) { if( enumerator.MoveNext() ) { return true; } } } } return false; } /** * Ensures that the properties of the resource of the specified type are loaded. */ private void CheckLoadProperties( PropDataType propType ) { Debug.Assert( propType != PropDataType.Link ); if ( !PropertiesLoaded( propType ) ) { if ( propType == PropDataType.Bool ) { LoadBoolProperties(); } else if ( propType == PropDataType.StringList ) { LoadStringListProperties(); } else if ( propType == PropDataType.String || propType == PropDataType.LongString ) { LoadStringProperties( propType ); } else { LoadProperties( propType ); } _propLoadedMask = (ushort) (_propLoadedMask | (1 << (int) propType)); } } /** * Ensures that all properties of the resource are loaded. */ private void CheckLoadAllProperties() { Lock(); try { for( PropDataType propType = PropDataType.Int; propType <= PropDataType.StringList; propType++ ) { if ( propType != PropDataType.Link ) { CheckLoadProperties( propType ); } } if ( !PropertiesLoaded( PropDataType.Link ) ) { LoadLinks(); } } finally { UnLock(); } } /** * Adds a link of the specified type between the specified resources. */ public void AddLink( string propName, IResource target ) { AddLink( MyPalStorage.Storage.GetPropId( propName ), target ); } /** * Adds the link with the specified prop ID between the specified resources. */ public void AddLink( int propId, IResource target ) { if ( target == null ) { throw new ArgumentNullException( "target" ); } Resource targetResource = (Resource) target; if ( _ID == -1 ) throw new ResourceDeletedException( "The link source resource has been deleted" ); if ( targetResource.Id == -1 ) throw new ResourceDeletedException( "The link target resource has been deleted" ); if ( MyPalStorage.Storage.GetPropDataType( propId ) != PropDataType.Link ) { throw new StorageException( propId + " is not a link property" ); } if ( !IsTransient && propId < 0 ) { throw new StorageException( "Negative link IDs may not be used with AddLink" ); } if ( targetResource.Id == _ID ) { throw new StorageException( "Cannot link a resource to itself (resource type " + Type + ", property type " + MyPalStorage.Storage.PropTypes [propId].Name + ")" ); } if ( !IsTransient && targetResource.IsTransient ) { if ( MyPalStorage.Storage.IsLinkDirected( propId ) ) { target.AddLink( -propId, this ); } else { target.AddLink( propId, this ); } return; } if ( !AddLinkSide( this, propId, targetResource ) ) return; int reversePropId = MyPalStorage.Storage.IsLinkDirected( propId ) ? -propId : propId; if ( !IsTransient ) { if ( targetResource.IsDeleting ) { return; } AddLinkSide( targetResource, reversePropId, this ); MyPalStorage.Storage.SaveLink( Id, target.Id, propId ); } MyPalStorage.Storage.OnLinkAdded( this, targetResource, propId ); if ( !IsTransient ) { MyPalStorage.Storage.OnLinkAdded( targetResource, this, reversePropId ); } } public void AddLink(PropId propId, IResource target) { AddLink(propId.Id, target); } /** * Adds one side of a resource link between two objects. Returns false * if the link already exists or true if the link is new. */ private static bool AddLinkSide( Resource from, int propId, Resource target ) { from.Lock(); try { if ( from.IsDeleting ) return false; IntArrayList linkList = from.GetLinkList( propId ); if ( linkList == null ) { linkList = new IntArrayList(); from[ propId ] = linkList; } lock( linkList ) { int index = linkList.BinarySearch( target.Id ); if ( index >= 0 ) return false; linkList.Insert( ~index, target.Id ); } } finally { from.UnLock(); } return true; } /** * Returns the count of links of the specified type. */ public int GetLinkCount( string propName ) { return GetLinkCount( MyPalStorage.Storage.GetPropId( propName ) ); } public int GetLinkCount( int propId ) { Lock(); try { IntArrayList linkList = GetLinkList( propId ); if ( linkList == null ) return 0; return linkList.Count; } finally { UnLock(); } } /** * Checks if a link with the specified type to the specified resource already * exists. */ public bool HasLink( string propName, IResource target ) { return HasLink( MyPalStorage.Storage.GetPropId( propName ), target ); } public bool HasLink( int propId, IResource target ) { if ( target == null ) throw new ArgumentNullException( "target" ); VerifyLinkProp( propId ); Lock(); try { IntArrayList linkList = GetLinkList( propId ); if ( linkList == null ) return false; return linkList.BinarySearch( target.Id ) >= 0; } finally { UnLock(); } } /** * Deletes a link from the resource to another resource. */ public void DeleteLink( string propName, IResource target ) { DeleteLink( MyPalStorage.Storage.GetPropId( propName ), target ); } public void DeleteLink( int propId, IResource target ) { if ( target == null ) throw new ArgumentNullException( "target" ); if ( MyPalStorage.Storage.GetPropDataType( propId ) != PropDataType.Link ) throw new StorageException( propId + " is not a link property" ); Resource targetRes = (Resource) target; if ( _ID == -1 ) throw new ResourceDeletedException(); if ( MyPalStorage.Storage.IsLinkDirected( propId ) ) { DeleteLinkSide( this, -propId, targetRes ); if ( !IsTransient ) { DeleteLinkSide( targetRes, -propId, this ); } } DeleteLinkSide( this, propId, targetRes ); if ( !IsTransient ) { DeleteLinkSide( targetRes, propId, this ); } } /** * Deletes one side of a resource link between two objects. */ private static void DeleteLinkSide( Resource from, int propId, Resource target ) { bool doDeleteLink = false; from.Lock(); try { IntArrayList linkList = from.GetLinkList( propId ); if ( linkList != null ) { bool doRemoveProp = false; lock( linkList ) { // the target resource may have been deleted by user code when the first // link side was being deleted, so, in order to make sure we search for the // correct ID, OriginalId needs to be used (OM-10400) int index = linkList.BinarySearch( target.OriginalId ); if ( index >= 0 ) { linkList.RemoveAt( index ); doDeleteLink = true; if ( linkList.Count == 0 ) doRemoveProp = true; } } if ( doRemoveProp ) { from.Remove( propId ); } } } finally { from.UnLock(); } if ( doDeleteLink ) { MyPalStorage.Storage.DeleteLink( from, target, propId ); } } /** * Deletes all links of the specified type for a resource. */ public void DeleteLinks( string propName ) { DeleteLinks( MyPalStorage.Storage.GetPropId( propName ) ); } public void DeleteLinks( int propId ) { if ( MyPalStorage.Storage.GetPropDataType( propId ) != PropDataType.Link ) { throw new StorageException( propId + " is not a link property" ); } DeleteLinksExcept( propId, Int32.MinValue ); } /** * Deletes all links of the specified type for a resource except for the link to * the specified resource. * @return true if the link to the specified resource was found. */ private bool DeleteLinksExcept( int propId, int exceptResourceId ) { bool foundExisting = false; IntArrayList linksToDelete = null; try { IntArrayList reverseLinksToDelete = null; Lock(); try { if ( _ID == -1 ) throw new ResourceDeletedException(); if ( exceptResourceId != Int32.MinValue && MyPalStorage.Storage.IsLinkDirected( propId ) ) { // if there is a link between the same two objects going in the // opposite direction, deletes that link IntArrayList reverseLinks = GetLinkList( -propId ); if ( reverseLinks != null ) { for( int i=0; i= 0; i-- ) { if ( oldLinks [i] == exceptResourceId ) foundExisting = true; else { if ( linksToDelete == null ) { linksToDelete = IntArrayListPool.Alloc(); } linksToDelete.Add( oldLinks [i] ); } } } } finally { UnLock(); } if ( linksToDelete != null ) { for( int i=0; i propId) { return GetLinksOfType(resType, propId.Id); } public BusinessObjectList GetLinksOfType(ResourceTypeId resType, PropId propId) where T : BusinessObject { return new BusinessObjectList(resType, GetLinksOfType(resType.Name, propId)); } public IResourceList GetLinksOfTypeLive( string resType, string propName ) { return GetLinksOfType( resType, MyPalStorage.Storage.GetPropId( propName ), true, false ); } public IResourceList GetLinksOfTypeLive( string resType, int propId ) { return GetLinksOfType( resType, propId, true, false ); } public IResourceList GetLinksOfTypeLive(string resType, PropId propId) { return GetLinksOfTypeLive(resType, propId.Id); } public IResourceList GetLinksFrom( string resType, string propName ) { int propId = MyPalStorage.Storage.GetPropId( propName ); VerifyDirectedLink( propId ); return GetLinksOfType( resType, propId, false, true ); } public IResourceList GetLinksFrom( string resType, int propId ) { VerifyDirectedLink( propId ); return GetLinksOfType( resType, propId, false, true ); } public IResourceList GetLinksFrom(string resType, PropId propId) { return GetLinksFrom(resType, propId.Id); } public BusinessObjectList GetLinksFrom(ResourceTypeId resType, PropId propId) where T : BusinessObject { return new BusinessObjectList(resType, GetLinksFrom(resType.Name, propId)); } public IResourceList GetLinksFromLive( string resType, string propName ) { int propId = MyPalStorage.Storage.GetPropId( propName ); VerifyDirectedLink( propId ); return GetLinksOfType( resType, propId, true, true ); } public IResourceList GetLinksFromLive( string resType, int propId ) { VerifyDirectedLink( propId ); return GetLinksOfType( resType, propId, true, true ); } public IResourceList GetLinksTo( string resType, string propName ) { int propId = MyPalStorage.Storage.GetPropId( propName ); VerifyDirectedLink( propId ); return GetLinksOfType( resType, -propId, false, true ); } public IResourceList GetLinksTo( string resType, int propId ) { VerifyDirectedLink( propId ); return GetLinksOfType( resType, -propId, false, true ); } public IResourceList GetLinksTo(string resType, PropId propId) { return GetLinksTo(resType, propId.Id); } public BusinessObjectList GetLinksTo(ResourceTypeId resType, PropId propId) where T : BusinessObject { return new BusinessObjectList(resType, GetLinksTo(resType.Name, propId)); } public IResourceList GetLinksToLive( string resType, string propName ) { int propId = MyPalStorage.Storage.GetPropId( propName ); VerifyDirectedLink( propId ); return GetLinksOfType( resType, -propId, true, true ); } public IResourceList GetLinksToLive( string resType, int propId ) { VerifyDirectedLink( propId ); return GetLinksOfType( resType, -propId, true, true ); } private void VerifyDirectedLink( int propId ) { if ( MyPalStorage.Storage.GetPropDataType( propId ) != PropDataType.Link ) throw new StorageException( propId + " is not a link property" ); if ( !MyPalStorage.Storage.IsLinkDirected( propId ) ) throw new StorageException( propId + " is not a directed link property" ); } private void VerifyLinkProp( int propId ) { int absPropId = propId < 0 ? -propId : propId; if ( MyPalStorage.Storage.GetPropDataType( absPropId ) != PropDataType.Link ) throw new StorageException( propId + " is not a link property" ); if ( propId < 0 && !MyPalStorage.Storage.IsLinkDirected( propId ) ) throw new StorageException( propId + " is not a directed link property" ); } private IResourceList GetLinksOfType( string resType, int propId, bool live, bool directed ) { if ( !directed ) { if ( MyPalStorage.Storage.GetPropDataType( propId ) != PropDataType.Link ) throw new StorageException( propId + " is not a link property" ); } ResourceLinkPredicate pred = new ResourceLinkPredicate( this, propId, directed ); ResourceList result; string restrictedLinkType = MyPalStorage.Storage.GetLinkResourceTypeRestriction( Type, propId ); if ( resType != null && restrictedLinkType == resType ) { pred.SetKnownType( MyPalStorage.Storage.ResourceTypes [restrictedLinkType].Id ); result = new ResourceList( pred, live ); } else { result = MyPalStorage.Storage.IntersectPredicateWithType( pred, resType, live ); } if ( !live ) { /* we need to support the following pattern: - GetLinksOfType() (not live) - Delete() - do something with resources that were linked to the deleted resource */ result.Instantiate(); } return result; } /** * Returns the list of resource IDs that are linked to the current resource * with links of specified type and possibly its reverse. */ internal IntArrayList GetListOfLinks( int propId, bool directed, bool needClone ) { Lock(); try { IntArrayList linkList = GetLinkList( propId ); if ( !directed && MyPalStorage.Storage.IsLinkDirected( propId ) ) { IntArrayList reverseList = GetLinkList( -propId ); if ( linkList == null ) linkList = reverseList; else if ( reverseList != null ) { linkList = IntArrayList.MergeSorted( linkList, reverseList ); needClone = false; } } if ( linkList != null ) { return needClone ? (IntArrayList) linkList.Clone() : linkList; } return new IntArrayList(); } finally { UnLock(); } } public int[] GetLinkTypeIds() { return GetLinkTypeIds( true ); } /** * Returns a list of the IDs of distinct link types present in the resource. */ private int[] GetLinkTypeIds( bool reverseLinks ) { Lock(); try { if ( !PropertiesLoaded( PropDataType.Link ) ) { LoadLinks(); } IntArrayList result = IntArrayListPool.Alloc(); try { foreach( Entry propEntry in this ) { // do not return reversed link type IDs int propType = propEntry.Key; if ( MyPalStorage.Storage.GetPropDataType( propType ) == PropDataType.Link ) { IntArrayList linkList = (IntArrayList) propEntry.Value; if ( linkList.Count > 0 ) { if ( reverseLinks && propType < 0 ) propType = -propType; if ( result.IndexOf( propType ) < 0 ) result.Add( propType ); } } } return result.ToArray(); } finally { IntArrayListPool.Dispose( result ); } } finally { UnLock(); } } /** * Deletes the resource from the store. */ public void Delete() { if ( IsDeleting ) return; _propLoadedMask |= RESOURCE_DELETING_MASK; if ( !IsTransient ) { MyPalStorage.Storage.CheckEndUpdate( this ); } try { MyPalStorage.Storage.OnResourceDeleting( this ); } finally { DoDelete(); } } /** * Performs the actual deletion of the resource from the store. */ private void DoDelete() { if ( !IsTransient ) { // the resource is already marked as deleting, so it is not possible that // new links will appear, and there is no need to hold the resource lock // for the entire duration of DeleteAllLinks() (see bug #4117) DeleteAllLinks(); } Lock(); try { if ( _ID == -1 ) return; if ( !IsTransient ) { MyPalStorage.Storage.DeleteResource( this ); } else { MyPalStorage.Storage.CleanTransientResource( this ); } int originalId = _ID; _ID = -1; Clear(); this[ ResourceProps.Id ] = originalId; } finally { UnLock(); } } /** * Deletes all links between this resource and other resources. */ private void DeleteAllLinks() { int[] linkTypes = GetLinkTypeIds( false ); for( int i=0; i /// Loads string properties with the specified type. /// /// The type of string properties to load. private void LoadStringProperties( PropDataType propType ) { if ( IsDeleted ) return; IResultSet rs = MyPalStorage.Storage.GetProperties( _ID, propType ); try { PropTypeCollection propTypes = MyPalStorage.Storage.PropTypes as PropTypeCollection; using( IKeyPairEnumerator enumerator = (IKeyPairEnumerator) rs.GetEnumerator() ) { while( true ) { try { if ( !enumerator.MoveNext() ) { break; } } catch( BadIndexesException ) { MyPalStorage.Storage.OnIndexCorruptionDetected( "Resource.LoadStringProperties" ); break; } catch( IOException ex ) { MyPalStorage.Storage.OnIOErrorDetected( ex ); break; } Compound compound = (Compound) enumerator.GetCurrentKey().Key; int propId = (int) compound._key2; int offset = enumerator.GetCurrentOffset(); if ( propTypes.IsValidType( propId ) ) { this[ propId ] = offset; } else { MyPalStorage.Storage.OnIndexCorruptionDetected( "Found invalid property ID " + propId + " when loading resource properties" ); } } } } finally { rs.Dispose(); } } /** * Loads all boolean properties of a resource. */ private void LoadBoolProperties() { using( IResultSet rs = MyPalStorage.Storage.GetBoolProperties( _ID ) ) { PropTypeCollection propTypes = MyPalStorage.Storage.PropTypes as PropTypeCollection; using( SafeRecordValueEnumerator enumerator = new SafeRecordValueEnumerator( rs, "Resource.LoadBoolProperties" ) ) { while( enumerator.MoveNext() ) { int value = enumerator.GetCurrentIntValue( 1 ); if ( propTypes.IsValidType( value ) && propTypes [value].DataType == PropDataType.Bool ) { this[ value ] = _true; } else { MyPalStorage.Storage.OnIndexCorruptionDetected( "Found invalid property ID " + value + " when loading Boolean resource properties" ); } } } } } /** * Loads all string list properties of a resource. */ internal void LoadStringListProperties() { using( IResultSet rs = MyPalStorage.Storage.GetStringListProperties( _ID ) ) { int lastPropId = -1; PropTypeCollection propTypes = MyPalStorage.Storage.PropTypes as PropTypeCollection; using( SafeRecordEnumerator enumerator = new SafeRecordEnumerator( rs, "Resource.LoadStringProperties" ) ) { while( enumerator.MoveNext() ) { IRecord rec = enumerator.Current; int propId = rec.GetIntValue( 1 ); if ( propId != lastPropId ) { if ( propTypes.IsValidType( propId ) && propTypes [propId].DataType == PropDataType.StringList ) { PropertyStringList stringList = (PropertyStringList) this[ propId ]; if ( stringList == null ) { stringList = new PropertyStringList( this, propId, false, false ); this[ propId ] = stringList; } } else { MyPalStorage.Storage.OnIndexCorruptionDetected( "Found invalid property ID " + propId + " when loading string list properties" ); } } lastPropId = propId; } } } _propLoadedMask = (ushort) (_propLoadedMask | (1 << (int) PropDataType.StringList)); } internal void LoadStringListProperties( PropertyStringList stringList, int propId ) { PropTypeCollection propTypes = MyPalStorage.Storage.PropTypes as PropTypeCollection; if ( !propTypes.IsValidType( propId ) || propTypes [propId].DataType != PropDataType.StringList ) { MyPalStorage.Storage.OnIndexCorruptionDetected( "Found invalid property ID " + propId + " when loading string list properties" ); } else { using( IResultSet rs = MyPalStorage.Storage.GetStringListProperties( _ID, propId ) ) { using( SafeRecordEnumerator enumerator = new SafeRecordEnumerator( rs, "Resource.LoadStringProperties" ) ) { while( enumerator.MoveNext() ) { IRecord rec = enumerator.Current; stringList.AddValue( rec.GetStringValue( 2 ) ); } } } } } private void LoadLinks() { if ( IsDeleted ) return; IntArrayList loadedLinkTypes = IntArrayListPool.Alloc(); try { using( IResultSet rs = MyPalStorage.Storage.GetLinksFrom( _ID ) ) { LoadLinksFromResultSet( rs, loadedLinkTypes, 1, 1 ); } using( IResultSet rs = MyPalStorage.Storage.GetLinksTo( _ID ) ) { LoadLinksFromResultSet( rs, loadedLinkTypes, 0, -1 ); } _propLoadedMask = (ushort) (_propLoadedMask | (1 << (int) PropDataType.Link ) ); } finally { IntArrayListPool.Dispose( loadedLinkTypes ); } } private void LoadLinks( int propId ) { if ( IsDeleted ) return; int count = 0; if ( propId > 0 || !MyPalStorage.Storage.IsLinkDirected( propId ) ) { using( IResultSet rs = MyPalStorage.Storage.GetLinksFrom( _ID, propId ) ) { count += LoadLinksFromResultSet( rs, null, 1, 1 ); } } if ( propId < 0 || !MyPalStorage.Storage.IsLinkDirected( propId ) ) { int toPropId = propId; if ( propId < 0 ) { toPropId = -toPropId; } using( IResultSet rs = MyPalStorage.Storage.GetLinksTo( _ID, toPropId ) ) { count += LoadLinksFromResultSet( rs, null, 0, -1 ); } } if ( count == 0 ) { // if we tried to load links once and found nothing, make sure we don't try // to load the same links again this[ propId ] = new IntArrayList(); } } private int LoadLinksFromResultSet( IResultSet resultSet, IntArrayList loadedLinkTypes, int targetPropIndex, int directionModifier ) { PropTypeCollection propTypes = MyPalStorage.Storage.PropTypes as PropTypeCollection; int lastPropId = Int32.MaxValue; bool skipProp = false; IntArrayList lastLinkList = null; int count = 0; using( resultSet ) { SafeRecordValueEnumerator enumerator = new SafeRecordValueEnumerator( resultSet, "Resource.LoadLinksFromResultSet" ); using( enumerator ) { while( enumerator.MoveNext() ) { count++; int propId = enumerator.GetCurrentIntValue( 2 ); int targetId = enumerator.GetCurrentIntValue( targetPropIndex ); int absPropId = propId; if ( propTypes.IsValidType( absPropId ) && MyPalStorage.Storage.IsLinkDirected( propId ) ) propId *= directionModifier; if ( propId != lastPropId ) { if ( !skipProp && lastLinkList != null ) { lastLinkList.Sort(); } if ( !propTypes.IsValidType( absPropId ) || propTypes [propId].DataType != PropDataType.Link ) { MyPalStorage.Storage.OnIndexCorruptionDetected( "Found invalid property ID " + propId + " when loading resource link properties" ); lastLinkList = null; skipProp = true; } else { lastLinkList = (IntArrayList) this[ propId ]; if ( lastLinkList != null && loadedLinkTypes != null && loadedLinkTypes.IndexOf( propId ) < 0 ) { skipProp = true; } else { if ( lastLinkList == null ) { lastLinkList = new IntArrayList(); this[ propId ] = lastLinkList; } skipProp = false; if ( loadedLinkTypes != null ) { loadedLinkTypes.Add( propId ); } } } } lastPropId = propId; if ( !skipProp ) { lastLinkList.Add( targetId ); } } if ( !skipProp && lastLinkList != null ) { lastLinkList.Sort(); } } return count; } } private void SaveTransientProperties() { MyPalStorage.Storage.CommitTransientResource( this ); _propLoadedMask = (ushort) (_propLoadedMask & 0x3FFF); ArrayList addedLinks = ArrayListPool.Alloc(); try { IntArrayList emptyLinkLists = null; Lock(); try { foreach( Entry propEntry in this ) { int propId = propEntry.Key; IntArrayList linkList = propEntry.Value as IntArrayList; if ( linkList != null ) { for( int i=linkList.Count-1; i >= 0; i-- ) { if ( MyPalStorage.Storage.IsLinkDirected( propId ) ) propId = -propId; Resource target = (Resource) MyPalStorage.Storage.LoadResource( linkList [i], true, -1 ); if ( target.IsDeleting ) { linkList.RemoveAt( i ); continue; } if ( AddLinkSide( target, propId, this ) ) { addedLinks.Add( new Pair( linkList [i], propId ) ); } if ( propEntry.Key < 0 ) { MyPalStorage.Storage.SaveLink( linkList [i], _ID, -propEntry.Key ); } else { MyPalStorage.Storage.SaveLink( _ID, linkList [i], propEntry.Key ); } } if ( linkList.Count == 0 ) { if ( emptyLinkLists == null ) { emptyLinkLists = new IntArrayList(); } emptyLinkLists.Add( propEntry.Key ); } } else if ( propEntry.Value is Boolean ) { MyPalStorage.Storage.CreateBoolProperty( this, propId ); } else if ( propEntry.Value is PropertyStringList ) { ((PropertyStringList) propEntry.Value).CommitTransient(); } else { MyPalStorage.Storage.CreateProperty( this, propId, propEntry.Value ); PropDataType dataType = MyPalStorage.Storage.PropTypes [propId].DataType; if ( dataType == PropDataType.Blob ) { // propValue passed to the function is a stream, and the props hash // must actually contain a blob propEntry.Value = MyPalStorage.Storage.GetBlobProperty( _ID, propId ); } } } if ( emptyLinkLists != null ) { for( int i=0; i