All Downloads are FREE. Search and download functionalities are using the official Maven repository.

rs.data.impl.AbstractDaoFactory Maven / Gradle / Ivy

/**
 * 
 */
package rs.data.impl;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

import javax.transaction.Status;
import javax.transaction.SystemException;
import javax.transaction.Transaction;
import javax.transaction.TransactionManager;

import org.apache.commons.configuration.Configuration;
import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.HierarchicalConfiguration;
import org.apache.commons.configuration.SubnodeConfiguration;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import rs.data.TransactionSupport;
import rs.data.api.DaoFactory;
import rs.data.api.DaoMaster;
import rs.data.api.dao.GeneralDAO;
import rs.data.event.DaoEvent;
import rs.data.event.DaoFactoryEvent;
import rs.data.event.DaoFactoryEvent.Type;
import rs.data.event.DaoFactoryListener;
import rs.data.event.DaoListener;
import rs.data.util.URLTransformer;
import rsbaselib.configuration.Configurable;
import rsbaselib.configuration.ConfigurationUtils;
import rsbaselib.io.FileFinder;

/**
 * The basic implementation of a DAO factory.
 * @author ralph
 *
 */
public abstract class AbstractDaoFactory implements DaoFactory, Configurable {

	// TX Management	
	private Logger log = LoggerFactory.getLogger(getClass());
	
	private ThreadLocal txContext = new ThreadLocal();
	private Set listeners = new HashSet();
	private DaoListener daoListener = new MyDaoListener();
	private Map properties = new HashMap();
	private Map params = new HashMap();
	private TransactionManager txManager;
	private URLTransformer urlTransformer;
	private Map daoMasters = new HashMap();
	
	/**
	 * Constructor.
	 */
	public AbstractDaoFactory() {
	}

	/**
	 * Returns the logger object.
	 * @return the logger object
	 */
	protected Logger getLog() {
		return log;
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public void configure(Configuration cfg) throws ConfigurationException {
		{
			// Read all parameters first
			int index = 0;
			while (index >= 0) {
				try {
					String name = cfg.getString("property("+index+")[@name]");
					if (name == null) break;
					String value = cfg.getString("property("+index+")");
					index++;
					params.put(name, value);
					log.debug(name+"="+value);
				} catch (Exception e) {
					index = -1;
				}
			}
		}
		
		// Load the URL transformer, if it exists:
		try {
			SubnodeConfiguration tCfg = ((HierarchicalConfiguration)cfg).configurationAt("URLTransformer(0)");
			setUrlTransformer((URLTransformer)ConfigurationUtils.load(tCfg, true));
		} catch (Exception e) {
			log.info("No URL Transformer loaded");
		}

		{
			// Tweak the class loader to avoid some unexpected RCP problems
			Thread thread = Thread.currentThread();
			ClassLoader loader = thread.getContextClassLoader();
			thread.setContextClassLoader(this.getClass().getClassLoader());

			// Load DAO masters
			int index = 0;
			while (index >= 0) {
				try {
					SubnodeConfiguration tCfg = ((HierarchicalConfiguration)cfg).configurationAt("DaoMaster("+index+")");
					DaoMaster daoMaster = loadDaoMaster(tCfg);
					String id = cfg.getString("DaoMaster("+index+")[@name]");
					if (id == null) id = "default";
					getLog().debug("DAO Master \""+id+"\": "+daoMaster.getClass().getName());
					setDaoMaster(id, daoMaster);
					index++;
				} catch (IllegalArgumentException e) {
					index = -1;
				} catch (Exception e) {
					getLog().error("Cannit load DaoMaster: ", e);
					index = -1;
				}
			}
			
			// Restore the class loader
			thread.setContextClassLoader(loader);

		}
		
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void beforeConfiguration() {
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void afterConfiguration() {
	}

	/**
	 * Loads an object from a configuration.
	 * The object is configured if it is an instance of {@link Configurable}.
	 * @param config configuration
	 * @return the object
	 */
	public DaoMaster loadDaoMaster(SubnodeConfiguration config) {
		try {
			String className = config.getString("[@class]");
			Class clazz = Class.forName(className);
			DaoMaster rc = (DaoMaster)clazz.newInstance();
			rc.setFactory(this);
			if (rc instanceof Configurable) {
				((Configurable)rc).configure(config);
			}
			return rc;
		} catch (Exception e) {
			throw new RuntimeException("Cannot load class from configuration", e);
		}
	}

	/**
	 * Sets the DAO master.
	 * @param id id of master
	 * @param daoMaster master to bet set
	 */
	public void setDaoMaster(String id, DaoMaster daoMaster) {
		if (daoMasters.containsKey(id)) throw new RuntimeException("DAO Master already exists: "+id);
		daoMasters.put(id, daoMaster);
	}
	
	/**
	 * {@inheritDoc}
	 */
	@Override
	public DaoMaster getDaoMaster(String id) {
		return daoMasters.get(id);
	}

	/**
	 * Creates an DAO from the configuration
	 * @param mainCfg main config object
	 * @param key key to process
	 * @return DAO created
	 */
	protected GeneralDAO createDAO(Configuration mainCfg, String key) throws Exception {
		// Tweak the class loader to avoid some unexpected RCP problems
		Thread thread = Thread.currentThread();
		ClassLoader loader = thread.getContextClassLoader();
		thread.setContextClassLoader(this.getClass().getClassLoader());

		GeneralDAO rc = null;
		try {
			HierarchicalConfiguration hCfg = (HierarchicalConfiguration)mainCfg;
			SubnodeConfiguration cfg = hCfg.configurationAt(key+"(0)");
			
			// Create the class
			String className = cfg.getString("[@class]");
			getLog().debug(key+": "+className);
			@SuppressWarnings("unchecked")
			Class> clazz = (Class>) Class.forName(className);
			rc = clazz.newInstance();
			
			// Set the factory
			rc.setFactory(this);
			
			// Set the DAO Master
			String masterId = cfg.getString("[@daoMaster]");
			if (masterId == null) masterId = "default";
			rc.setDaoMaster(getDaoMaster(masterId));
			
			// Configure it
			if (rc instanceof Configurable) ((Configurable)rc).configure(cfg);
			
			// Add the factory as a listener
			rc.addDaoListener(daoListener);
		} finally {
			// Restore the class loader
			thread.setContextClassLoader(loader);
		}
		return rc;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Object getProperty(String name) {
		return properties.get(name);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void setProperty(String name, Object value) {
		properties.put(name, value);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public String getParameter(String name) {
		return params.get(name);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Iterator getParameterKeys() {
		return params.keySet().iterator();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public URL getParameterUrl(String name) throws MalformedURLException {
		String value = getParameter(name);
		if (value == null) return null;
		URLTransformer transformer = getUrlTransformer();
		if (transformer != null) return transformer.toURL(value);
		return FileFinder.find(value);
	}

	
	/********************* TRANSACTIONS ************************/
	
	/**
	 * Returns the urlTransformer.
	 * @return the urlTransformer
	 */
	public URLTransformer getUrlTransformer() {
		return urlTransformer;
	}

	/**
	 * Sets the urlTransformer.
	 * @param urlTransformer the urlTransformer to set
	 */
	public void setUrlTransformer(URLTransformer urlTransformer) {
		this.urlTransformer = urlTransformer;
	}

	/**
	 * Returns the TX manager used.
	 * If there was no TX manager set before, this method will create a TX manager.
	 * @return the transaction manager
	 */
	@Override
	public TransactionManager getTransactionManager() {
		if (txManager == null) {
			setTransactionManager(createTransactionManager());
		}
		return txManager;
	}

	/**
	 * Creates a new transaction manager.
	 * @return a new transaction manager
	 */
	protected TransactionManager createTransactionManager() {
		try {
			return TransactionSupport.start();
		} catch (Throwable t) {
			throw new RuntimeException("Cannot create Transaction Manager", t);
		}
	}
	
	/**
	 * Sets the given TX manager to be used.
	 * This method will throw an exception if there is already a TX manager active.
	 * @param txManager the TX manager.
	 */
	@Override
	public void setTransactionManager(TransactionManager txManager) {
		if (this.txManager != null) throw new RuntimeException("Transaction Manager already set");
		this.txManager = txManager;
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void begin() {
		TransactionContext context = txContext.get();
		if (context == null) {
			context = new TransactionContext();
			txContext.set(context);
		}

		context.begin();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void commit() {
		txContext.get().commit();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void rollback() {
		txContext.get().rollback();
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public Transaction getTransaction() throws SystemException {
		return getTransactionManager().getTransaction();
	}

	/********************* PROPERTY CHANGES **********************************/

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void addDaoFactoryListener(DaoFactoryListener listener) {
		listeners.add(listener);
	}

	/**
	 * {@inheritDoc}
	 */
	@Override
	public void removeDaoFactoryListener(DaoFactoryListener listener) {
		listeners.remove(listener);
	}

	/**
	 * Fires a factory event.
	 * @param event event to be fired
	 */
	protected void fireDaoFactoryEvent(DaoFactoryEvent event) {
		for (DaoFactoryListener l : listeners) l.handleDaoFactoryEvent(event);
	}
	
	/**
	 * Registers the current context that the model changed underneath.
	 */
	public void modelChanged() {
		TransactionContext context = txContext.get();
		if (context != null) context.setModelChanged(true);
	}
	
	/**
	 * Keeps track of transaction creation.
	 * @author ralph
	 *
	 */
	protected class TransactionContext {
		
		private int beginCount;
		private boolean modelChanged;
		
		public TransactionContext() {
			this.beginCount = 0;
			this.modelChanged = false;
		}

		/**
		 * Starts the transaction (or increments the internal counter).
		 * If there is already a transaction started then nothing will be performed.
		 */
		public void begin() {
			try {
				boolean startTx = true;
				
				// Do we have an active transaction?
				
				Transaction tx = getTransaction();
				if (tx != null) startTx = (tx.getStatus() != Status.STATUS_ACTIVE);
				
				// Start the TX if required
				if (startTx) {
					getTransactionManager().begin();
					beginCount = 1;
					modelChanged = false;
					fireDaoFactoryEvent(new DaoFactoryEvent(AbstractDaoFactory.this, Type.TRANSACTION_STARTED));
				} else {
					beginCount++;
				}
			} catch (Exception e) {
				throw new RuntimeException("Cannot start TX:", e);
			}
		}

		/**
		 * Commits a transaction.
		 * If the last call to {@link #begin()} did not start a new TX then
		 * this method does nothing.
		 */
		public void commit() {
			try {
				if (beginCount == 1) {
					if (isModelChanged()) fireDaoFactoryEvent(new DaoFactoryEvent(AbstractDaoFactory.this, Type.MODEL_CHANGED));
					fireDaoFactoryEvent(new DaoFactoryEvent(AbstractDaoFactory.this, Type.TRANSACTION_COMMITTING));
					getTransaction().commit();
					fireDaoFactoryEvent(new DaoFactoryEvent(AbstractDaoFactory.this, Type.TRANSACTION_COMMITTED));

				}
				beginCount--;
			} catch (Exception e) {
				throw new RuntimeException("Cannot commit transaction: ", e);
			}
			if (beginCount < 0) {
				throw new RuntimeException("No active transaction found");
			}
		}
		
		/**
		 * Rolls back a transaction.
		 * If the last call to {@link #begin()} did not start a new TX then
		 * this method will just mark the transaction for rollback only.
		 */
		public void rollback() {
			try {
				beginCount--;
				if (beginCount == 0) {
					fireDaoFactoryEvent(new DaoFactoryEvent(AbstractDaoFactory.this, Type.TRANSACTION_ROLLING_BACK));
					getTransaction().rollback();
					fireDaoFactoryEvent(new DaoFactoryEvent(AbstractDaoFactory.this, Type.TRANSACTION_ROLLED_BACK));
				} else if (beginCount > 0) {
					getTransaction().setRollbackOnly();
				}
			} catch (Exception e) {
				throw new RuntimeException("Cannot rollback transaction: ", e);
			}
			if (beginCount < 0) {
				throw new RuntimeException("No active transaction found");
			}
		}
		
		/**
		 * Returns the modelChanged.
		 * @return the modelChanged
		 */
		public boolean isModelChanged() {
			return modelChanged;
		}

		/**
		 * Sets the modelChanged.
		 * @param modelChanged the modelChanged to set
		 */
		public void setModelChanged(boolean modelChanged) {
			this.modelChanged = modelChanged;
		}	
	}

	/**
	 * Listener for DAO events.
	 * Mainly just forwards events.
	 * @author ralph
	 *
	 */
	protected class MyDaoListener implements DaoListener {

		/**
		 * {@inheritDoc}
		 */
		@Override
		public void handleDaoEvent(DaoEvent event) {
			modelChanged();
		}
		
	}
	

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy