/* * #! * Ontopia Engine * #- * Copyright (C) 2001 - 2013 The Ontopia Project * #- * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * !# */ package net.ontopia.topicmaps.impl.rdbms; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Map; import net.ontopia.infoset.core.LocatorIF; import net.ontopia.persistence.proxy.IdentityIF; import net.ontopia.persistence.proxy.PersistentIF; import net.ontopia.persistence.proxy.QueryCollection; import net.ontopia.persistence.proxy.TransactionIF; import net.ontopia.topicmaps.core.AssociationIF; import net.ontopia.topicmaps.core.OccurrenceIF; import net.ontopia.topicmaps.core.TMObjectIF; import net.ontopia.topicmaps.core.TopicIF; import net.ontopia.topicmaps.core.TopicMapIF; import net.ontopia.topicmaps.core.TopicNameIF; import net.ontopia.topicmaps.impl.utils.TopicMapTransactionIF; import net.ontopia.topicmaps.core.TransactionNotActiveException; import net.ontopia.topicmaps.core.ReadOnlyException; import net.ontopia.topicmaps.impl.rdbms.index.IndexManager; import net.ontopia.topicmaps.impl.utils.AbstractTopicMapTransaction; import net.ontopia.topicmaps.impl.utils.EventListenerIF; import net.ontopia.topicmaps.impl.utils.EventManagerIF; import net.ontopia.topicmaps.impl.utils.ObjectTreeManager; import net.ontopia.topicmaps.impl.utils.TopicModificationManager; import net.ontopia.utils.CollectionFactory; import net.ontopia.utils.OntopiaRuntimeException; import net.ontopia.utils.OntopiaUnsupportedException; /** * INTERNAL: The rdbms topic map transaction implementation.<p> */ public class RDBMSTopicMapTransaction extends AbstractTopicMapTransaction implements EventManagerIF { protected TransactionIF txn; protected boolean readonly; protected long actual_id; protected SubjectIdentityCache sicache; protected RoleTypeCache rtcache; protected RoleTypeAssocTypeCache rtatcache; protected Map listeners; protected ObjectTreeManager otree; protected TopicModificationManager topicmods; TopicEvents te; RDBMSTopicMapTransaction(RDBMSTopicMapStore store, long topicmap_id, boolean readonly) { this.store = store; this.readonly = readonly; // Begin new transaction this.txn = store.getStorage().createTransaction(readonly); this.txn.begin(); // Create or look up topic map object if (topicmap_id > 0) { // Get hold of topic map object topicmap = (TopicMapIF)txn.getObject(txn.getAccessRegistrar().createIdentity(TopicMap.class, topicmap_id)); if (topicmap == null) throw new OntopiaRuntimeException("Topic map with id '" + topicmap_id + " not found."); } else { if (readonly) throw new ReadOnlyException(); else { // Create new topic map object and register with database topicmap = new TopicMap(txn); txn.create((PersistentIF)topicmap); } } IdentityIF identity = ((PersistentIF)topicmap)._p_getIdentity(); this.actual_id = ((Long)(identity.getKey(0))).longValue(); // Register store with topic map if (readonly) ((ReadOnlyTopicMap)topicmap).setTransaction(this); else ((TopicMap)topicmap).setTransaction(this); // Activate transaction (note: must be activated at this point, because of dependencies) this.active = true; // Initialize collection factory this.cfactory = new CollectionFactory(); // Initialize listeners this.listeners = cfactory.makeSmallMap(); // Register object tree event listener with store event manager this.otree = new ObjectTreeManager(this, cfactory); this.topicmods = new TopicModificationManager(this, cfactory); this.te = new TopicEvents(store); this.te.registerListeners(this); this.topicmods.addListener(this.te, TopicIF.EVENT_MODIFIED); // QueryCache: subject identity cache this.sicache = new SubjectIdentityCache(this, cfactory); this.sicache.registerListeners(this, otree); // QueryCache: role type cache this.rtcache = new RoleTypeCache(this, this, otree); // QueryCache: role type assoc type cache this.rtatcache = new RoleTypeAssocTypeCache(this, this, otree); // Create new index manager this.imanager = new IndexManager(this, cfactory); // Create topic map builder this.builder = new TopicMapBuilder(txn, topicmap); } public long getActualId() { return actual_id; } public ObjectTreeManager getObjectTreeManager() { return otree; } public void commit() { if (!readonly) { synchronized (this) { super.commit(); // commit proxy transaction txn.commit(); // reset query caches sicache.commit(); rtcache.commit(); rtatcache.commit(); // notify cluster txn.getStorageAccess().getStorage().notifyCluster(); // commmit listeners processEvent(this, TopicMapTransactionIF.EVENT_COMMIT, null, null); } } else { txn.commit(); } } public void abort() { if (!readonly) { synchronized (this) { super.abort(); // abort listeners processEvent(this, TopicMapTransactionIF.EVENT_ABORT, null, null); } } } public void abort(boolean invalidate) { if (!readonly || invalidate) { synchronized (this) { super.abort(invalidate); // Invalidate transaction invalid = (invalid || invalidate); // Abort proxy transaction if (txn.isActive()) txn.abort(); if (invalidate) { if (active) { // Close proxy transaction txn.close(); // Deactivate topic map transaction active = false; } } else { // Reset query caches sicache.abort(); rtcache.abort(); rtatcache.abort(); } } } else if (readonly) { txn.abort(); } } public boolean validate() { // if transaction has been aborted the store is invalid if (invalid) return false; // check proxy transaction return txn.validate(); } public TopicMapTransactionIF createNested() { // Nested transactions are not supported throw new OntopiaUnsupportedException("Nested transactions not supported."); } /** * INTERNAL: Returns the proxy transaction used by the topic map * transaction. */ public TransactionIF getTransaction() { if (!isActive()) throw new TransactionNotActiveException("Transaction is not active."); return txn; } // --------------------------------------------------------------------------- // EventManagerIF implementation // --------------------------------------------------------------------------- public void addListener(EventListenerIF listener, String event) { // Adding itself causes infinite loops. if (listener == this) return; // Initialize event entry synchronized (listeners) { // Add listener to list of event entry listeners. This is not // very elegant, but it works. if (!listeners.containsKey(event)) listeners.put(event, new Object[0]); Collection event_listeners = new ArrayList(Arrays.asList((Object[])listeners.get(event))); event_listeners.add(listener); listeners.put(event, event_listeners.toArray()); } } public void removeListener(EventListenerIF listener, String event) { synchronized (listeners) { if (listeners.containsKey(event)) { // Remove listener from list of event entry listeners. This is // not very elegant, but it works. Collection event_listeners = new ArrayList(Arrays.asList((Object[])listeners.get(event))); event_listeners.remove(listener); if (event_listeners.isEmpty()) listeners.remove(event); else listeners.put(event, event_listeners.toArray()); } } } public void processEvent(Object object, String event, Object new_value, Object old_value) { // Look up event listeners Object[] event_listeners = (Object[])listeners.get(event); if (event_listeners != null) { // Loop over event listeners int size = event_listeners.length; for (int i=0; i < size; i++) // Notify listener ((EventListenerIF)event_listeners[i]).processEvent(object, event, new_value, old_value); } } // --------------------------------------------------------------------------- // Prefetch: roles by type and association type // --------------------------------------------------------------------------- public void prefetchRolesByType(Collection players, TopicIF rtype, TopicIF atype) { this.rtatcache.prefetchRolesByType(players, rtype, atype); } // --------------------------------------------------------------------------- // Subject identity cache // --------------------------------------------------------------------------- public TMObjectIF getObjectByItemIdentifier(LocatorIF locator) { if (locator == null) throw new NullPointerException("null is not a valid argument."); // Get from subject identity cache return sicache.getObjectByItemIdentifier(locator); } public TopicIF getTopicBySubjectLocator(LocatorIF locator) { if (locator == null) throw new NullPointerException("null is not a valid argument."); // Get from subject identity cache return sicache.getTopicBySubjectLocator(locator); } public TopicIF getTopicBySubjectIdentifier(LocatorIF locator) { if (locator == null) throw new NullPointerException("null is not a valid argument."); // Get from subject identity cache return sicache.getTopicBySubjectIdentifier(locator); } // --------------------------------------------------------------------------- // Role type cache // --------------------------------------------------------------------------- public Collection getRolesByType(TopicIF player, TopicIF rtype) { return rtcache.getRolesByType(player, rtype); } // --------------------------------------------------------------------------- // Role type and association type cache // --------------------------------------------------------------------------- public Collection getRolesByType(TopicIF player, TopicIF rtype, TopicIF atype) { return rtatcache.getRolesByType(player, rtype, atype); } // --------------------------------------------------------------------------- // Optimized shortcuts // --------------------------------------------------------------------------- public Collection<OccurrenceIF> getOccurrencesByType(TopicIF topic, TopicIF type) { return new QueryCollection<OccurrenceIF>(txn, "TopicIF.getOccurrencesByType_size", new Object[] {getTopicMap(), topic, type}, "TopicIF.getOccurrencesByType", new Object[] {getTopicMap(), topic, type}); } public Collection<TopicNameIF> getTopicNamesByType(TopicIF topic, TopicIF type) { return new QueryCollection<TopicNameIF>(txn, "TopicIF.getTopicNamesByType_size", new Object[] {getTopicMap(), topic, type}, "TopicIF.getTopicNamesByType", new Object[] {getTopicMap(), topic, type}); } public Collection<AssociationIF> getAssocations(TopicIF topic) { return new QueryCollection<AssociationIF>(txn, "TopicIF.getAssociations_size", new Object[] {getTopicMap(), getTopicMap(), topic}, "TopicIF.getAssociations", new Object[] {getTopicMap(), getTopicMap(), topic}); } public Collection<AssociationIF> getAssociationsByType(TopicIF topic, TopicIF type) { return new QueryCollection<AssociationIF>(txn, "TopicIF.getAssociationsByType_size", new Object[] {getTopicMap(), type, getTopicMap(), topic}, "TopicIF.getAssociationsByType", new Object[] {getTopicMap(), type, getTopicMap(), topic}); } public String toString() { return "[rdbms.Transaction, " + actual_id + ", " + readonly + "]"; } }