/* * $Ids$ * * Copyright 2006 University of Dundee. All rights reserved. * Use is subject to license terms supplied in LICENSE.txt */ /*------------------------------------------------------------------------------ * * Written by: Josh Moore <josh.moore@gmx.de> * *------------------------------------------------------------------------------ */ package ome.logic; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.Future; import ome.annotations.RolesAllowed; import ome.api.IUpdate; import ome.api.ServiceInterface; import ome.api.local.LocalQuery; import ome.api.local.LocalUpdate; import ome.conditions.ApiUsageException; import ome.conditions.ValidationException; import ome.model.IObject; import ome.model.meta.EventLog; import ome.parameters.Parameters; import ome.services.eventlogs.EventLogLoader; import ome.services.fulltext.FullTextBridge; import ome.services.fulltext.FullTextIndexer; import ome.services.fulltext.FullTextThread; import ome.services.sessions.SessionManager; import ome.services.util.Executor; import ome.tools.hibernate.ReloadFilter; import ome.tools.hibernate.UpdateFilter; import ome.util.Utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.hibernate.Session; import org.springframework.dao.InvalidDataAccessApiUsageException; import org.springframework.orm.hibernate3.SessionFactoryUtils; import org.springframework.transaction.annotation.Transactional; /** * implementation of the IUpdate service interface * * @author Josh Moore, <a href="mailto:josh.moore@gmx.de">josh.moore@gmx.de</a> * @version 1.0 <small> (<b>Internal version:</b> $Rev$ $Date$) </small> * @since OMERO 3.0 */ @Transactional(readOnly = false) public class UpdateImpl extends AbstractLevel1Service implements LocalUpdate { private final Logger log = LoggerFactory.getLogger(UpdateImpl.class); protected transient LocalQuery localQuery; protected transient Executor executor; protected transient SessionManager sessionManager; protected transient FullTextBridge fullTextBridge; public final void setQueryService(LocalQuery query) { getBeanHelper().throwIfAlreadySet(this.localQuery, query); this.localQuery = query; } public void setExecutor(Executor executor) { getBeanHelper().throwIfAlreadySet(this.executor, executor); this.executor = executor; } public void setSessionManager(SessionManager sessionManager) { getBeanHelper().throwIfAlreadySet(this.sessionManager, sessionManager); this.sessionManager = sessionManager; } public void setFullTextBridge(FullTextBridge fullTextBridge) { getBeanHelper().throwIfAlreadySet(this.fullTextBridge, fullTextBridge); this.fullTextBridge = fullTextBridge; } public Class<? extends ServiceInterface> getServiceInterface() { return IUpdate.class; }; // ~ LOCAL PUBLIC METHODS // ========================================================================= @RolesAllowed("user") public void flush() { session().flush(); } // ~ INTERFACE METHODS // ========================================================================= @RolesAllowed("user") public void saveObject(IObject graph) { doAction(graph, new UpdateAction<IObject>() { @Override public IObject run(IObject value, UpdateFilter filter, Session s) { return internalMerge(value, filter, s); } }); } @RolesAllowed("user") public IObject saveAndReturnObject(IObject graph) { return doAction(graph, new UpdateAction<IObject>() { @Override public IObject run(IObject value, UpdateFilter filter, Session s) { return internalMerge(value, filter, s); } }); } @RolesAllowed("user") public void saveCollection(Collection graph) { doAction(graph, new UpdateAction<Collection>() { @Override public Collection run(Collection value, UpdateFilter filter, Session s) { for (Object o : value) { IObject obj = (IObject) o; obj = internalMerge(obj, filter, s); } return null; } }); } @RolesAllowed("user") public IObject[] saveAndReturnArray(IObject[] graph) { return doAction(graph, new UpdateAction<IObject[]>() { @Override public IObject[] run(IObject[] value, UpdateFilter filter, Session s) { IObject[] copy = new IObject[value.length]; for (int i = 0; i < value.length; i++) { copy[i] = internalMerge(value[i], filter, s); } return copy; } }); } @RolesAllowed("user") public List<Long> saveAndReturnIds(IObject[] graph) { if (graph == null || graph.length == 0) { return Collections.emptyList(); // EARLY EXIT! } final List<Long> ids = new ArrayList<Long>(graph.length); final ReloadFilter filter = new ReloadFilter(session()); doAction(graph, filter, new UpdateAction<IObject[]>() { @Override public IObject[] run(IObject[] value, UpdateFilter filter, Session s) { for (int i = 0; i < value.length; i++) { ids.add(i, internalSave(value[i], (ReloadFilter) filter, s)); } return null; } }); return ids; } @RolesAllowed("user") public void saveArray(IObject[] graph) { doAction(graph, new UpdateAction<IObject[]>() { @Override public IObject[] run(IObject[] value, UpdateFilter filter, Session s) { IObject[] copy = new IObject[value.length]; for (int i = 0; i < value.length; i++) { copy[i] = internalMerge(value[i], filter, s); } return copy; } }); } @RolesAllowed("user") public void deleteObject(IObject row) { if (row == null) { return; } if (row.getId() == null) { throw new ApiUsageException( "Non-managed IObject entity cannot be deleted. Must have an id."); } try { doAction(row, new UpdateAction<IObject>() { @Override public IObject run(IObject value, UpdateFilter filter, Session s) { internalDelete(value, filter, s); return null; } }); } catch (InvalidDataAccessApiUsageException idaaue) { throw new ApiUsageException("Cannot delete " + row + "\n" + "Original message: " + idaaue.getMessage() + "\n" + "Consider using IDelete instead."); } } @RolesAllowed("system") public void indexObject(IObject row) { if (row == null || row.getId() == null) { throw new ValidationException( "Non-managed object cannot be indexed."); } CreationLogLoader logs = new CreationLogLoader(localQuery, row); FullTextIndexer fti = new FullTextIndexer(logs); fti.setApplicationContext(this.executor.getContext()); final FullTextThread ftt = new FullTextThread(sessionManager, executor, fti, this.fullTextBridge, true); Future<Object> future = executor.submit(Executors.callable(ftt)); executor.get(future); } // ~ Internals // ========================================================= private void beforeUpdate(Object argument, UpdateFilter filter) { if (argument == null) { throw new IllegalArgumentException( "Argument to save cannot be null."); } if (getBeanHelper().getLogger().isDebugEnabled()) { getBeanHelper().getLogger().debug(" Saving event before merge. "); } } /** * Note if we use anything other than merge here, functionality from * {@link ome.security.basic.MergeEventListener} needs to be moved to * {@link UpdateFilter} or to another event listener. */ protected Long internalSave(IObject obj, ReloadFilter filter, Session session) { if (getBeanHelper().getLogger().isDebugEnabled()) { getBeanHelper().getLogger().debug(" Internal save. "); } IObject result = (IObject) filter.filter(null, obj); Long id = (Long) session.save(result); return id; } /** * Note if we use anything other than merge here, functionality from * {@link ome.security.basic.MergeEventListener} needs to be moved to * {@link UpdateFilter} or to another event listener. */ protected IObject internalMerge(IObject obj, UpdateFilter filter, Session session) { if (getBeanHelper().getLogger().isDebugEnabled()) { getBeanHelper().getLogger().debug(" Internal merge. "); } IObject result = (IObject) filter.filter(null, obj); result = (IObject) session.merge(result); return result; } protected void internalDelete(IObject obj, UpdateFilter filter, Session session) { if (getBeanHelper().getLogger().isDebugEnabled()) { getBeanHelper().getLogger().debug(" Internal delete. "); } session.delete(session.load(Utils.trueClass(obj.getClass()), obj .getId())); } private void afterUpdate(UpdateFilter filter, Session session) { if (getBeanHelper().getLogger().isDebugEnabled()) { getBeanHelper().getLogger().debug(" Post-save cleanup. "); } // Clean up session.flush(); filter.unloadReplacedObjects(); } private <T> T doAction(final T graph, final UpdateAction<T> action) { final UpdateFilter filter = new UpdateFilter(); return doAction(graph, filter, action); } private <T> T doAction(final T graph, final UpdateFilter filter, final UpdateAction<T> action) { final Session session = session(); T retVal; beforeUpdate(graph, filter); retVal = action.run(graph, filter, session); afterUpdate(filter, session); return retVal; } private abstract class UpdateAction<T> { public abstract T run(T value, UpdateFilter filter, Session s); } private Session session() { return SessionFactoryUtils.getSession(getSessionFactory(), false); } } /** * {@link EventLogLoader} which loads a single instance. */ class CreationLogLoader extends EventLogLoader { final private LocalQuery query; private IObject obj; public CreationLogLoader(LocalQuery query, IObject obj) { this.obj = obj; this.query = query; setQueryService(query); } @Override public EventLog query() { if (obj == null) { return null; } else { EventLog el = query.findByQuery("select el from EventLog el " + "where el.action = 'INSERT' and " + "el.entityType = :type and " + "el.entityId = :id", new Parameters() .addString("type", obj.getClass().getName()).addId( obj.getId())); obj = null; return el; } } @Override public long more() { return 0; } }