Java tutorial
/* * Copyright (c) 2009 - 2010. School of Information Technology and Electrical * Engineering, The University of Queensland. This software is being developed * for the "Phenomics Ontoogy Driven Data Management Project (PODD)" project. * PODD is a National e-Research Architecture Taskforce (NeAT) project * co-funded by ANDS and ARCS. * * PODD is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * PODD is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with PODD. If not, see <http://www.gnu.org/licenses/>. */ package podd.dataaccess.fedora; import static podd.util.common.Constants.STATE_DELETED; import info.aduna.collections.iterators.CloseableIterator; import java.util.Set; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.hibernate.criterion.Criterion; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import podd.dataaccess.DAO; import podd.dataaccess.EntityDAO; import podd.dataaccess.EntityVersionUpdater; import podd.dataaccess.Idempotent; import podd.dataaccess.exception.DataAccessException; import podd.model.entity.PoddEntity; import podd.repository.exceptions.RepositoryObjectHandlingException; import podd.repository.object.ObjectManipulator; import podd.repository.rdf.EntityGenerationException; import podd.repository.rdf.EntityRDFFactory; import podd.util.caching.CacheStore; import podd.util.fedora.FedoraRepositoryUtil; import podd.util.iterator.CloseableEntityIterator; import podd.util.iterator.CloseableFedoraLoadingIterator; import podd.util.iterator.ObjectGetterIterator; /** * @author Yuan-Fang Li * @version $Id$ */ @SuppressWarnings({ "unchecked" }) @Transactional(propagation = Propagation.REQUIRED, readOnly = false) public abstract class AbstractFedoraDAOImpl<T extends PoddEntity> implements FedoraDAO<T> { protected Logger logger = LoggerFactory.getLogger(getClass()); protected EntityDAO pidDao; protected FedoraRepositoryUtil helper; protected EntityRDFFactory rdfFactory; protected ObjectManipulator objectManipulator; protected EntityVersionUpdater versionUpdater; protected CacheStore cacheStore; protected AbstractFedoraDAOImpl(FedoraRepositoryUtil helper, EntityRDFFactory rdfFactory, ObjectManipulator objectManipulator, EntityVersionUpdater versionUpdater, EntityDAO pidDao, CacheStore cacheStore) { this.pidDao = pidDao; this.helper = helper; this.rdfFactory = rdfFactory; this.cacheStore = cacheStore; this.versionUpdater = versionUpdater; this.objectManipulator = objectManipulator; } /** * Loading of the object is a three-step operation. (1) Load the partial object from DB using its ID. * (2) If the object is found, check the cache store using its PID. (3) If the object is in cache, returns it, * or load it from the Fedora Commons repository. * * @param id The Long-typed ID of the object to be returned. * @return The object with the given ID. * @throws DataAccessException */ @Override @Idempotent public T load(Long id) throws DataAccessException { T entity = (T) pidDao.load(id); populateEntityWithFedoraObjects(entity); return entity; } protected void updateCache(T entity) { if (null != entity) { cacheStore.put(entity.getPid(), entity); } } @Override public void purgeCache(String pid) { cacheStore.purge(pid); } @Override public T merge(T object) throws DataAccessException { return (T) pidDao.merge(object); } /** * Loading a domain entity from Fedora Commons given its PID. We determine the type * of the entity by consulting the PIDHolderDAO object, which queries a database table * to find the type information of the object. Then we use reflection to create the instance. * * @param pid The PID of the entity, in the form of <code>namespace:localName</code>, * conforming to Fedora Commons' convention. * @return The entity of the given PID. * @throws DataAccessException When either the PID is not found in the relational database table * or there is an error retrieving the entity from Fedora Commons. */ @Override @Idempotent @SuppressWarnings({ "RedundantTypeArguments" }) public T load(String pid) throws DataAccessException { T entity = (T) pidDao.load(pid); populateEntityWithFedoraObjects(entity); return entity; } protected void doReload(T entity) throws DataAccessException { updateEntityAndVersion(entity); } /** * A two-step operation: (1) save the object itsely and recursively any descendent object in both * DB and Fedora Commons (object only, no <code>PODD</code> datastream yet); and (2) update the object in * Fedora Commons to add the <code>PODD</code>. * @param object The object to be saved. * @throws DataAccessException */ @Override @Idempotent public void save(T object) throws DataAccessException { try { doSave(object); doUpdate(object); } catch (RuntimeException e) { logger.error("Caught runtime exception: " + e.toString()); throw e; } catch (Exception e) { throw new DataAccessException( "Exception occurred while saving " + object.getClass() + ": " + object.getPid(), e); } } protected String doSave(T object) throws Exception { pidDao.save(object); return saveInRepo(object); } protected abstract String saveInRepo(T object) throws Exception; protected CloseableIterator<T> doLoadByLocalName(String localName, Class<T> clazzz) throws DataAccessException { final CloseableIterator<PoddEntity> iterator = pidDao.getByLocalName(localName); return new CloseableFedoraLoadingIterator(iterator, this, clazzz); } protected void rollBackUnsucessfulSave(PoddEntity object) throws DataAccessException { final String pid = object.getPid(); pidDao.delete(object); if (helper.containsObject(pid)) { try { helper.purgeObject(pid, "Rolling back unsuccessful save: " + pid); } catch (RepositoryObjectHandlingException e) { throw new DataAccessException("Error purging entity: " + pid + ".", e); } } } protected void doUpdate(T object) throws Exception { T entity = rdfFactory.getRDFFromEntity(object); pidDao.update(entity); versionUpdater.updateVersion(entity); updateCache(object); } /** * Attempt to load the fedora objects from the cache */ @Override public void populateEntityWithFedoraObjects(T entity) throws DataAccessException { if (entity != null && entity.getPid() != null) { T cachedPO = (T) cacheStore.get(entity.getPid()); if (cachedPO != null) { // Extract the Fedora Objects out of the cached podd object and copy them over FedoraCopier.copyFedoraReferences(cachedPO, entity); } else { updateEntityAndVersion(entity); // Add the fully loaded object to the cache updateCache(entity); //cacheStore.put(entity.getPid(), entity); } } } /** * Update a PODD object. Note that for performance reasons, descendents of this object are NOT * automatically saved/updated. They should be saved/updated separately prior to updating this object. * * @param object The object whose values are to be updated in the storgage. * @throws DataAccessException */ @Override public void update(T object) throws DataAccessException { try { doUpdate(object); } catch (Exception e) { throw new DataAccessException("Exception occurred updating object " + object.getLocalName(), e); } } /** * We're taking a simple approach here. We depend on the PID of the object to determine * whether it is in the repository. If the PID is set (not null), we assume that the object * already exists in the repository, and we will attempt to update it. If the PID is null, * we will assume that the object has not yet been saved in the repository. We will proceed * and save it. * * @param object The object to be saved or updated. * @throws DataAccessException */ @Override public void saveOrUpdate(T object) throws DataAccessException { try { T newObject = ensureExistsInRepository(object); update(newObject); } catch (Exception e) { throw new DataAccessException("Exception occurred updating object.", e); } } protected boolean existsInDB(T object) throws DataAccessException { String pid = object.getPid(); return null != pid && null != pidDao.load(pid); } protected boolean existsInRepo(T object) throws DataAccessException { String pid = object.getPid(); return null != pid && helper.containsObject(pid); } private T ensureExistsInRepository(T object) throws Exception { if (!existsInDB(object)) { doSave(object); } else if (!existsInRepo(object)) { saveInRepo(object); } return object; } /** * Mark an object to be "deleted" in DB instead of really deleting it. Also purge it from the cache store. * * @param object The object to be deleted. * @return The object that needs to be deleted. * @throws DataAccessException */ @Override @Idempotent public T delete(T object) throws DataAccessException { try { final String pid = object.getPid(); T newObject = (T) pidDao.markDeleted(object); objectManipulator.setObjectState(object, STATE_DELETED, "deleting object [" + pid + "] on request of " + object.getCreator().getUserName()); purgeCache(object.getPid()); return newObject; } catch (RuntimeException e) { logger.info("Runtime exception caught: " + e.toString()); throw e; } catch (Exception e) { throw new DataAccessException("Exception occurred deleting object.", e); } } /** * Force deletion of the object rather than marking it as deleted * @param object * @return * @throws DataAccessException */ @Override @Idempotent public T forceDelete(T object) throws DataAccessException { try { pidDao.delete(object); purgeCache(object.getPid()); return object; } catch (RuntimeException e) { logger.info("Runtime exception caught: " + e.toString()); throw e; } catch (Exception e) { throw new DataAccessException("Exception occurred deleting object.", e); } } @Override @Idempotent public void refresh(T object) throws DataAccessException { doRefresh(object); updateCache(object); } protected void doRefresh(T object) throws DataAccessException { pidDao.refresh(object); updateEntityAndVersion(object); } protected void updateEntityAndVersion(T object) throws DataAccessException { if (null != object) { try { T newObject = rdfFactory.updateEntity(object); versionUpdater.updateVersion(newObject); } catch (EntityGenerationException e) { throw new DataAccessException(e); } } } protected CloseableIterator<T> doGetAll(String nameSpace, DAO<T> dao) throws RepositoryObjectHandlingException, DataAccessException, NoSuchMethodException { CloseableIterator<String> iterator = new ObjectGetterIterator<PoddEntity, String>( pidDao.getByPIDPrefix(nameSpace), "pid", PoddEntity.class); return new CloseableEntityIterator<T>(iterator, dao); } protected <V extends T> CloseableIterator<V> getAll(Class<V> clazz, Set<Criterion> criteria, DAO<V> dao) throws NoSuchMethodException, DataAccessException { CloseableIterator<String> iterator = new ObjectGetterIterator<V, String>(pidDao.getAll(clazz, criteria), "pid", clazz); return new CloseableEntityIterator<V>(iterator, dao); } @Override public void reattach(T entity) { pidDao.reattach(entity); } }