podd.dataaccess.fedora.AbstractFedoraDAOImpl.java Source code

Java tutorial

Introduction

Here is the source code for podd.dataaccess.fedora.AbstractFedoraDAOImpl.java

Source

/*
 * 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);
    }

}