public bool SaveEntry(RFCatalogEntry entry, bool raiseEvent = true, bool overwrite = false) { var materialUpdate = false; if (entry is RFDocument && !(entry as RFDocument).Type.Contains(".")) { SystemLog.Warning(this, "Full type name required to save key {0}", entry.Key); } if (entry.Key.Plane == RFPlane.Ephemeral) { lock (_memoryStore) { if (!_memoryStore.ContainsKey(entry.Key.ToString())) { _memoryStore.Add(entry.Key.ToString(), entry); } else { _memoryStore[entry.Key.ToString()] = entry; } } } else { // don't raise events if not saved (i.e. same content) materialUpdate = _catalog.SaveItem(entry, overwrite); raiseEvent &= materialUpdate; } if (raiseEvent) { _events.RaiseEvent(this, new RFCatalogUpdateEvent { Key = entry.Key }, ProcessingKey); } return(materialUpdate); }
protected RFCatalogEntry ExtractEntry(RFStoreType storeType, Dictionary <string, object> dataRow, bool ignoreContent) { RFCatalogEntry entry = null; switch (storeType) { case RFStoreType.Document: entry = ExtractDocument(dataRow, ignoreContent); break; default: throw new Exception(String.Format("Unrecognized store type {0}", storeType)); } // common fields var type = dataRow["KeyType"].ToString(); entry.Key = (RFCatalogKey)RFXMLSerializer.DeserializeContract(type, dataRow["SerializedKey"].ToString()); entry.Metadata = RFMetadata.Deserialize(dataRow["Metadata"].ToString()) ?? new RFMetadata(); entry.UpdateTime = (DateTimeOffset)dataRow["UpdateTime"]; entry.Version = (int)dataRow["Version"]; entry.IsValid = (bool)dataRow["IsValid"]; return(entry); }
public override bool SaveItem(RFCatalogEntry item, bool overwrite = false) { if (item == null) { Log.Warning(this, "SaveItem with null"); return(false); } if (item.Key.Plane == RFPlane.User) { //Log.Debug(this, "SaveItem {0}", entry.Key.ToString()); } try { var serializedContent = (item is RFDocument && item.IsValid) ? SerializeContent((item as RFDocument).Content) : new byte[0]; var compressedContent = CompressContent(serializedContent); var keyType = item.Key.GetType().FullName; var keyString = item.Key.ToString(); var keyHash = RFStringHelpers.QuickHash(keyString); var keyHashResource = "KH" + keyHash; using (var scope = Transaction.Current == null ? new TransactionScope(_useTransactions ? TransactionScopeOption.Required : TransactionScopeOption.Suppress, new TransactionOptions { IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted, Timeout = TimeSpan.FromSeconds(30) }) : null) { using (var connection = new SqlConnection(_connectionString)) { connection.Open(); // acquire lock on the hash string lockSQL = "sp_getapplock"; string unlockSQL = "sp_releaseapplock @Resource = '" + keyHashResource + "';"; using (var lockCommand = new SqlCommand(lockSQL, connection)) { lockCommand.CommandType = CommandType.StoredProcedure; lockCommand.Parameters.AddWithValue("@Resource", keyHashResource); lockCommand.Parameters.AddWithValue("@LockMode", "Exclusive"); lockCommand.Parameters.AddWithValue("@LockOwner", "Transaction"); var retValue = lockCommand.Parameters.Add("@ReturnVal", SqlDbType.Int); retValue.Direction = ParameterDirection.ReturnValue; //RFStatic.Log.Debug(this, "sp_getapplock called..."); lockCommand.ExecuteNonQuery(); if ((int)retValue.Value < 0) { Log.Error(this, "sp_getapplock returned {0}", retValue.Value); } //RFStatic.Log.Debug(this, "sp_getapplock returned {0}", retValue.Value); } try { // find or create existing key long catalogKeyID = 0; var getKeySQL = String.Format("SELECT [CatalogKeyID] FROM [RIFF].[CatalogKey] WHERE [KeyHash] = @KeyHash AND [KeyType] = @KeyType AND CAST([SerializedKey] AS VARCHAR({0})) = CAST(CAST(@SerializedKey AS XML) AS VARCHAR({0}))", _maxKeyLength); if (!_trackKeyHash) { getKeySQL = String.Format("SELECT [CatalogKeyID] FROM [RIFF].[CatalogKey] WHERE [KeyType] = @KeyType AND CAST([SerializedKey] AS VARCHAR({0})) = CAST(CAST(@SerializedKey AS XML) AS VARCHAR({0}))", _maxKeyLength); } using (var getCommand = CreateCommand(getKeySQL, connection)) { getCommand.Parameters.AddWithValue("@KeyType", keyType); if (_trackKeyHash) { getCommand.Parameters.AddWithValue("@KeyHash", keyHash); } getCommand.Parameters.AddWithValue("@SerializedKey", keyString); var result = getCommand.ExecuteScalar(); if (result != null) { catalogKeyID = (long)result; } } if (catalogKeyID == 0) { string createKeySQL = "INSERT INTO [RIFF].[CatalogKey] ( [KeyType], [SerializedKey], [KeyHash], [RootHash], [FriendlyString] )" + " VALUES ( @KeyType, @SerializedKey, @KeyHash, @RootHash, @FriendlyString ); SELECT SCOPE_IDENTITY()"; using (var createKeyCommand = CreateCommand(createKeySQL, connection)) { var rootHash = RFStringHelpers.QuickHash(item.Key.RootKey().ToString()); createKeyCommand.Parameters.AddWithValue("@KeyType", keyType); createKeyCommand.Parameters.AddWithValue("@SerializedKey", keyString); createKeyCommand.Parameters.AddWithValue("@KeyHash", keyHash); createKeyCommand.Parameters.AddWithValue("@RootHash", rootHash); createKeyCommand.Parameters.AddWithValue("@FriendlyString", RFStringHelpers.StringToSQL(item.Key.FriendlyString(), true, 100, false)); var result = createKeyCommand.ExecuteScalar(); if (result != null) { catalogKeyID = (long)((decimal)result); } } } if (catalogKeyID == 0) { throw new RFSystemException(this, "Unable to create new catalog key."); } // lookup any existing entries and calculate next version int version = 1; string getVersionSQL = "SELECT MAX([Version]) FROM [RIFF].[CatalogEntry] WHERE [CatalogKeyID] = @CatalogKeyID"; using (var getVersionCommand = CreateCommand(getVersionSQL, connection)) { getVersionCommand.Parameters.AddWithValue("@CatalogKeyID", catalogKeyID); var result = getVersionCommand.ExecuteScalar(); if (result != null && result != DBNull.Value) { version = ((int)result) + 1; } } // there is an existing document - compare content if (version > 1) { long existingEntryID = 0; using (var loadExistingCommand = LoadEntryCommand(item.Key, version - 1, connection)) { using (var reader = loadExistingCommand.ExecuteReader(System.Data.CommandBehavior.SingleResult)) { var dataTable = new DataTable(); dataTable.Load(reader); if (dataTable != null && dataTable.Rows != null && dataTable.Rows.Count == 1) { switch (item.Key.StoreType) { case RFStoreType.Document: existingEntryID = (long)dataTable.Rows[0]["CatalogEntryID"]; try { var existingType = dataTable.Rows[0]["ContentType"].ToString(); var existingBinaryContent = dataTable.Rows[0]["BinaryContent"] as byte[]; var existingValid = (bool)dataTable.Rows[0]["IsValid"]; // decompress binary content to avoid // flagging update if only compression changed if (existingBinaryContent != null && existingBinaryContent.Length > 0 && existingValid == item.IsValid) { var rawExistingContent = DecompressContent(existingBinaryContent); if (Enumerable.SequenceEqual(serializedContent ?? new byte[0], rawExistingContent ?? new byte[0])) { //transaction.Rollback(); -- to avoid zombiecheck errors if (item.Key.Plane == RFPlane.User) { Log.Info(this, "Not required to update {0}/{1}/{2}", item.Key.GetType().Name, item.Key.FriendlyString(), item.Key.GetInstance()); } if (!_useTransactions) // lock will be auto released on transaction which prevents other thread coming in before transaction is completed { using (var unlockCommand = new SqlCommand(unlockSQL, connection)) { unlockCommand.ExecuteNonQuery(); } } scope.Complete(); return(false); } } } catch (Exception ex) { Log.Exception(this, "Unable to compare to existing", ex); } break; } } } } if (overwrite && existingEntryID > 0) { // update content rather than create new version var document = item as RFDocument; string updateDocumentSQL = "UPDATE [RIFF].[CatalogDocument] SET [BinaryContent] = @BinaryContent, [ContentType] = @ContentType where [CatalogEntryID] = @CatalogEntryID"; using (var updateDocumentCommand = CreateCommand(updateDocumentSQL, connection)) { updateDocumentCommand.Parameters.AddWithValue("@CatalogEntryID", existingEntryID); updateDocumentCommand.Parameters.AddWithValue("@ContentType", document.Type); if (compressedContent == null || compressedContent.Length == 0) { updateDocumentCommand.Parameters.AddWithValue("@BinaryContent", new byte[0]); } else { updateDocumentCommand.Parameters.AddWithValue("@BinaryContent", compressedContent); } var affected = updateDocumentCommand.ExecuteNonQuery(); if (affected != 1) { throw new RFSystemException(this, "Unable to update document."); } } string updateEntrySQL = "UPDATE [RIFF].[CatalogEntry] SET [UpdateTime] = @UpdateTime, [Metadata] = @Metadata, [IsValid] = @IsValid where [CatalogEntryID] = @CatalogEntryID"; using (var updateEntryCommand = CreateCommand(updateEntrySQL, connection)) { updateEntryCommand.Parameters.AddWithValue("@CatalogEntryID", existingEntryID); updateEntryCommand.Parameters.AddWithValue("@IsValid", item.IsValid); updateEntryCommand.Parameters.AddWithValue("@Metadata", item.Metadata != null ? ((object)item.Metadata.Serialize() ?? DBNull.Value) : DBNull.Value); updateEntryCommand.Parameters.AddWithValue("@UpdateTime", item.UpdateTime.Year < 1980 ? new DateTimeOffset(DateTime.Now) : item.UpdateTime); var affected = updateEntryCommand.ExecuteNonQuery(); if (affected != 1) { throw new RFSystemException(this, "Unable to update entry."); } } if (item.Key.Plane == RFPlane.User) { Log.Debug(this, "Overwritten item {0}", item.Key.ToString()); } if (!_useTransactions) // lock will be auto released on transaction which prevents other thread coming in before transaction is completed { using (var unlockCommand = new SqlCommand(unlockSQL, connection)) { unlockCommand.ExecuteNonQuery(); } } scope?.Complete(); return(true); } } // create entry long catalogEntryID = 0; string createEntrySQL = "INSERT INTO [RIFF].[CatalogEntry] ( [CatalogKeyID], [Version], [Metadata], [IsValid], [UpdateTime] )" + " VALUES (@CatalogKeyID, @Version, @Metadata, @IsValid, @UpdateTime); SELECT SCOPE_IDENTITY()"; using (var createEntryCommand = CreateCommand(createEntrySQL, connection)) { createEntryCommand.Parameters.AddWithValue("@CatalogKeyID", catalogKeyID); createEntryCommand.Parameters.AddWithValue("@Metadata", item.Metadata != null ? ((object)item.Metadata.Serialize() ?? DBNull.Value) : DBNull.Value); createEntryCommand.Parameters.AddWithValue("@Version", version); createEntryCommand.Parameters.AddWithValue("@IsValid", item.IsValid); createEntryCommand.Parameters.AddWithValue("@UpdateTime", item.UpdateTime.Year < 1980 ? new DateTimeOffset(DateTime.Now) : item.UpdateTime); var result = createEntryCommand.ExecuteScalar(); if (result != null && result != DBNull.Value) { catalogEntryID = (long)((decimal)result); } } if (catalogEntryID == 0) { throw new RFSystemException(this, "Unable to create new catalog entry."); } // create content switch (item.Key.StoreType) { case RFStoreType.Document: { var document = item as RFDocument; string createDocumentSQL = "INSERT INTO [RIFF].[CatalogDocument] ( [CatalogEntryID], [ContentType], [BinaryContent] ) VALUES ( @CatalogEntryID, @ContentType, @BinaryContent )"; using (var createDocumentCommand = CreateCommand(createDocumentSQL, connection)) { createDocumentCommand.Parameters.AddWithValue("@CatalogEntryID", catalogEntryID); createDocumentCommand.Parameters.AddWithValue("@ContentType", document.Type); if (compressedContent == null || compressedContent.Length == 0) { createDocumentCommand.Parameters.AddWithValue("@BinaryContent", new byte[0]); } else { createDocumentCommand.Parameters.AddWithValue("@BinaryContent", compressedContent); } var affected = createDocumentCommand.ExecuteNonQuery(); if (affected != 1) { throw new RFSystemException(this, "Unable to create document."); } } } break; default: throw new RFSystemException(this, "Unknown store type {0}", item.Key.StoreType); } } catch (Exception ex) { Log.Exception(this, ex, "Error saving entry (inner) {0}", item.Key); } if (!_useTransactions) // lock will be auto released on transaction which prevents other thread coming in before transaction is completed { using (var unlockCommand = new SqlCommand(unlockSQL, connection)) { unlockCommand.ExecuteNonQuery(); } } } if (scope != null) { scope.Complete(); } } if (item.Key.Plane == RFPlane.User) { Log.Info(this, "Saved key {0}/{1}/{2}", item.Key.GetType().Name, item.Key.FriendlyString(), item.Key.GetInstance()); } return(true); } catch (Exception ex) { Log.Exception(this, ex, "Error saving entry (outer) {0}", item.Key); return(false); } }
public override RFCatalogEntry LoadItem(RFCatalogKey itemKey, int version = 0, bool ignoreContent = false) { RFCatalogEntry entry = null; if (itemKey.Plane == RFPlane.User) { //Log.Debug(this, "LoadItem {0}", catalogKey.ToString()); } try { var dataRow = new Dictionary <string, object>(); using (var connection = new SqlConnection(_connectionString)) { connection.Open(); try { long catalogEntryID = 0; using (var command = LoadEntryCommand(itemKey, version, connection)) { using (var reader = command.ExecuteReader(System.Data.CommandBehavior.SingleRow)) { if (reader.HasRows) { reader.Read(); catalogEntryID = reader.GetInt64(7); dataRow.Add("CatalogKeyID", reader.GetInt64(0)); dataRow.Add("KeyType", reader.GetString(1)); dataRow.Add("SerializedKey", reader.GetString(2)); dataRow.Add("Version", reader.GetInt32(3)); dataRow.Add("Metadata", reader.IsDBNull(4) ? String.Empty : reader.GetString(4)); dataRow.Add("IsValid", reader.GetBoolean(5)); dataRow.Add("UpdateTime", reader.GetDateTimeOffset(6)); dataRow.Add("CatalogEntryID", catalogEntryID); dataRow.Add("ContentType", reader.GetString(8)); dataRow.Add("BinaryContent", reader.IsDBNull(9) ? null : reader.GetSqlBinary(9).Value); } } } } catch (SqlException) { throw; } catch (InvalidOperationException) { throw; } catch (Exception ex) { Log.Exception(this, ex, "Error loading entry {0}", itemKey); } } if (dataRow.Any()) { entry = ExtractEntry(itemKey.StoreType, dataRow, ignoreContent); } } catch (SqlException) { throw; } catch (InvalidOperationException) { throw; } catch (Exception ex) { Log.Exception(this, ex, "Error loading entry {0}", itemKey); } return(entry); }