diff --git a/dspace-api/src/main/java/org/dspace/app/checker/ChecksumChecker.java b/dspace-api/src/main/java/org/dspace/app/checker/ChecksumChecker.java index 7db69c6801..4444478031 100644 --- a/dspace-api/src/main/java/org/dspace/app/checker/ChecksumChecker.java +++ b/dspace-api/src/main/java/org/dspace/app/checker/ChecksumChecker.java @@ -258,6 +258,7 @@ public class ChecksumChecker checker.setDispatcher(dispatcher); checker.setCollector(logger); checker.process(); + System.exit(0); } /** diff --git a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java index dbcc1b4465..a811652181 100755 --- a/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java +++ b/dspace-api/src/main/java/org/dspace/app/itemimport/ItemImport.java @@ -170,6 +170,7 @@ public class ItemImport String mapfile = null; String eperson = null; // db ID or email String[] collections = null; // db ID or handles + int status = 0; if (line.hasOption('h')) { @@ -439,6 +440,7 @@ public class ItemImport c.abort(); e.printStackTrace(); System.out.println(e); + status = 1; } if (mapOut != null) @@ -450,6 +452,7 @@ public class ItemImport { System.out.println("***End of Test Run***"); } + System.exit(status); } private void addItems(Context c, Collection[] mycollections, diff --git a/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterManager.java b/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterManager.java index f4cd0cd908..b3b4c1b6e7 100644 --- a/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterManager.java +++ b/dspace-api/src/main/java/org/dspace/app/mediafilter/MediaFilterManager.java @@ -101,6 +101,8 @@ public class MediaFilterManager // create an options object and populate it CommandLineParser parser = new PosixParser(); + int status = 0; + Options options = new Options(); options.addOption("v", "verbose", false, @@ -217,6 +219,10 @@ public class MediaFilterManager c.complete(); c = null; } + catch (Exception e) + { + status = 1; + } finally { if (c != null) @@ -224,6 +230,7 @@ public class MediaFilterManager c.abort(); } } + System.exit(status); } public static void applyFiltersAllItems(Context c) throws Exception diff --git a/dspace-api/src/main/java/org/dspace/app/mets/METSExport.java b/dspace-api/src/main/java/org/dspace/app/mets/METSExport.java index f2838df4f9..7c1b6f6ca2 100644 --- a/dspace-api/src/main/java/org/dspace/app/mets/METSExport.java +++ b/dspace-api/src/main/java/org/dspace/app/mets/METSExport.java @@ -215,6 +215,7 @@ public class METSExport } context.abort(); + System.exit(0); } /** diff --git a/dspace-api/src/main/java/org/dspace/authorize/FixDefaultPolicies.java b/dspace-api/src/main/java/org/dspace/authorize/FixDefaultPolicies.java index b4a7b057bc..80f1c2f501 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/FixDefaultPolicies.java +++ b/dspace-api/src/main/java/org/dspace/authorize/FixDefaultPolicies.java @@ -138,6 +138,7 @@ public class FixDefaultPolicies } c.complete(); + System.exit(0); } /** diff --git a/dspace-api/src/main/java/org/dspace/authorize/PolicySet.java b/dspace-api/src/main/java/org/dspace/authorize/PolicySet.java index 4fbeffda88..230c85dbb9 100644 --- a/dspace-api/src/main/java/org/dspace/authorize/PolicySet.java +++ b/dspace-api/src/main/java/org/dspace/authorize/PolicySet.java @@ -99,6 +99,7 @@ public class PolicySet groupID, isReplace, false); c.complete(); + System.exit(0); } /** diff --git a/dspace-api/src/main/java/org/dspace/browse/BrowseConsumer.java b/dspace-api/src/main/java/org/dspace/browse/BrowseConsumer.java new file mode 100644 index 0000000000..bc57687e7b --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/browse/BrowseConsumer.java @@ -0,0 +1,162 @@ +/* + * BrowseConsumer.java + * + * Version: $Revision: 1.4 $ + * + * Date: $Date: 2006/04/10 04:11:09 $ + * + * Copyright (c) 2002-2007, Hewlett-Packard Company and Massachusetts + * Institute of Technology. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of the Hewlett-Packard Company nor the name of the + * Massachusetts Institute of Technology nor the names of their + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +package org.dspace.browse; + +import org.apache.log4j.Logger; + +import java.util.Set; +import java.util.HashSet; +import java.util.Iterator; + +import org.dspace.browse.Browse; +import org.dspace.content.Item; +import org.dspace.content.DSpaceObject; +import org.dspace.core.Context; +import org.dspace.core.Constants; +import org.dspace.core.LogManager; + +import org.dspace.event.Consumer; +import org.dspace.event.Event; +import org.dspace.event.EventManager; + +/** + * Class for updating browse system from content events. + * Prototype: only Item events recognized. + * + * XXX FIXME NOTE: The Browse Consumer is INCOMPLETE because the + * deletion of an Item CANNOT be implemented as an event consumer: + * When an Item is deleted, the browse tables must be updated + * immediately, within the same transaction, to maintain referential + * consistency. It cannot be handled in an Event consumer since by + * definition that runs after the transaction is committed. + * Perhaps this can be addressed if the Browse system is replaced. + * + * To handle create/modify events: accumulate Sets of Items to be added + * and updated out of the event stream. Process them in endEvents() + * filter out update requests for Items that were just created. + * + * Recommended filter: Item+Create|Modify|Modify_Metadata:Collection+Add|Remove + * + * @version $Revision: 1.1 $ + */ +public class BrowseConsumer implements Consumer +{ + /** log4j logger */ + private static Logger log = Logger.getLogger(BrowseConsumer.class); + + // items to be added to browse index + private Set toAdd = null; + + // items to be updated in browse index + private Set toUpdate = null; + + + public void initialize() + throws Exception + { + toAdd = new HashSet(); + toUpdate = new HashSet(); + } + + public void consume(Context ctx, Event event) + throws Exception + { + DSpaceObject subj = event.getSubject(ctx); + int et = event.getEventType(); + + // If an Item is added or modified.. + if (subj != null && subj.getType() == Constants.ITEM) + { + if (et == Event.CREATE) + toAdd.add(subj); + else + toUpdate.add(subj); + + // track ADD and REMOVE from collections, that changes browse index. + } else if (subj != null && subj.getType() == Constants.COLLECTION && + event.getObjectType() == Constants.ITEM && + (et == Event.ADD || et == Event.REMOVE)) + { + DSpaceObject obj = event.getObject(ctx); + if (obj != null) + toUpdate.add(obj); + } + else if (subj != null) + log.warn("consume() got unrecognized event: "+event.toString()); + } + + public void end(Context ctx) + throws Exception + { + for (Iterator ai = toAdd.iterator(); ai.hasNext();) + { + Item i = (Item)ai.next(); + Browse.itemAdded(ctx, i); + toUpdate.remove(i); + if (log.isDebugEnabled()) + log.debug("Added browse indices for Item id="+String.valueOf(i.getID())+", hdl="+i.getHandle()); + } + + // don't update an item we've just added. + for (Iterator ui = toUpdate.iterator(); ui.hasNext();) + { + Item i = (Item)ui.next(); + Browse.itemChanged(ctx, i); + if (log.isDebugEnabled()) + log.debug("Updated browse indices for Item id="+String.valueOf(i.getID())+", hdl="+i.getHandle()); + } + + // NOTE: Removed items are necessarily handled inline (ugh). + + // browse updates wrote to the DB, so we have to commit. + ctx.getDBConnection().commit(); + + // clean out toAdd & toUpdate + toAdd.clear(); + toUpdate.clear(); + } + + public void finish(Context ctx) { + + toAdd = toUpdate = null; + return; + } +} diff --git a/dspace-api/src/main/java/org/dspace/browse/InitializeBrowse.java b/dspace-api/src/main/java/org/dspace/browse/InitializeBrowse.java index da574ba406..4eaedab396 100644 --- a/dspace-api/src/main/java/org/dspace/browse/InitializeBrowse.java +++ b/dspace-api/src/main/java/org/dspace/browse/InitializeBrowse.java @@ -65,6 +65,7 @@ public class InitializeBrowse public static void main(String[] argv) { Context context = null; + int status = 0; try { @@ -78,6 +79,7 @@ public class InitializeBrowse } catch (SQLException sqle) { + status = 1; if (context != null) { context.abort(); @@ -86,5 +88,9 @@ public class InitializeBrowse System.err.println("Error: Browse index NOT created"); sqle.printStackTrace(); } + finally + { + System.exit(status); + } } } diff --git a/dspace-api/src/main/java/org/dspace/checker/CheckerConsumer.java b/dspace-api/src/main/java/org/dspace/checker/CheckerConsumer.java new file mode 100644 index 0000000000..210bfcbd0e --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/checker/CheckerConsumer.java @@ -0,0 +1,104 @@ +/* + * CheckerConsumer.java + * + * Version: $Revision$ + * + * Date: $Date$ + * + * Copyright (c) 2002-2007, Hewlett-Packard Company and Massachusetts + * Institute of Technology. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of the Hewlett-Packard Company nor the name of the + * Massachusetts Institute of Technology nor the names of their + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +package org.dspace.checker; + +import org.apache.log4j.Logger; +import org.dspace.core.Context; +import org.dspace.event.Consumer; +import org.dspace.event.Event; + +/** + * Class for removing Checker data for a Bitstreams based on deletion events. + * + * @version $Revision$ + */ +public class CheckerConsumer implements Consumer +{ + /** log4j logger */ + private static Logger log = Logger.getLogger(CheckerConsumer.class); + + private BitstreamInfoDAO bitstreamInfoDAO = new BitstreamInfoDAO(); + + /** + * Initialize - allocate any resources required to operate. + * Called at the start of ANY sequence of event consume() calls. + */ + public void initialize() throws Exception + { + // no-op + } + + /** + * Consume an event + * + * @param ctx the execution context object + * + * @param event the content event + */ + public void consume(Context ctx, Event event) throws Exception + { + + if (event.getEventType() == Event.DELETE) + { + log.debug("Attempting to remove Checker Info"); + bitstreamInfoDAO.deleteBitstreamInfoWithHistory(event.getSubjectID()); + log.debug("Completed removing Checker Info"); + } + } + + /** + * Signal that there are no more events queued in this + * event stream. + */ + public void end(Context ctx) throws Exception + { + // no-op + } + + /** + * Finish - free any allocated resources. + * Called when consumer is being released + */ + public void finish(Context ctx) throws Exception + { + // no-op + } +} diff --git a/dspace-api/src/main/java/org/dspace/content/Bitstream.java b/dspace-api/src/main/java/org/dspace/content/Bitstream.java index 066d94d736..79017bcf46 100644 --- a/dspace-api/src/main/java/org/dspace/content/Bitstream.java +++ b/dspace-api/src/main/java/org/dspace/content/Bitstream.java @@ -52,6 +52,7 @@ import org.dspace.core.ConfigurationManager; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.LogManager; +import org.dspace.event.Event; import org.dspace.storage.bitstore.BitstreamStorageManager; import org.dspace.storage.rdbms.DatabaseManager; import org.dspace.storage.rdbms.TableRow; @@ -81,6 +82,12 @@ public class Bitstream extends DSpaceObject /** The bitstream format corresponding to this bitstream */ private BitstreamFormat bitstreamFormat; + /** Flag set when data is modified, for events */ + private boolean modified; + + /** Flag set when metadata is modified, for events */ + private boolean modifiedMetadata; + /** * Private constructor for creating a Bitstream object based on the contents * of a DB table row. @@ -114,6 +121,9 @@ public class Bitstream extends DSpaceObject // Cache ourselves context.cache(this, row.getIntColumn("bitstream_id")); + + modified = modifiedMetadata = false; + clearDetails(); } /** @@ -190,6 +200,8 @@ public class Bitstream extends DSpaceObject Bitstream bitstream = find(context, bitstreamID); bitstream.setFormat(null); + context.addEvent(new Event(Event.CREATE, Constants.BITSTREAM, bitstreamID, null)); + return bitstream; } @@ -223,6 +235,8 @@ public class Bitstream extends DSpaceObject Bitstream bitstream = find(context, bitstreamID); bitstream.setFormat(null); + context.addEvent(new Event(Event.CREATE, Constants.BITSTREAM, bitstreamID, "REGISTER")); + return bitstream; } @@ -261,6 +275,8 @@ public class Bitstream extends DSpaceObject public void setSequenceID(int sid) { bRow.setColumn("sequence_id", sid); + modifiedMetadata = true; + addDetails("SequenceID"); } /** @@ -283,6 +299,8 @@ public class Bitstream extends DSpaceObject public void setName(String n) { bRow.setColumn("name", n); + modifiedMetadata = true; + addDetails("Name"); } /** @@ -306,6 +324,8 @@ public class Bitstream extends DSpaceObject public void setSource(String n) { bRow.setColumn("source", n); + modifiedMetadata = true; + addDetails("Source"); } /** @@ -328,6 +348,8 @@ public class Bitstream extends DSpaceObject public void setDescription(String n) { bRow.setColumn("description", n); + modifiedMetadata = true; + addDetails("Description"); } /** @@ -374,6 +396,8 @@ public class Bitstream extends DSpaceObject // but we need to find the unknown format! setFormat(null); bRow.setColumn("user_format_description", desc); + modifiedMetadata = true; + addDetails("UserFormatDescription"); } /** @@ -451,6 +475,7 @@ public class Bitstream extends DSpaceObject // Update the ID in the table row bRow.setColumn("bitstream_format_id", bitstreamFormat.getID()); + modified = true; } /** @@ -468,6 +493,18 @@ public class Bitstream extends DSpaceObject log.info(LogManager.getHeader(bContext, "update_bitstream", "bitstream_id=" + getID())); + if (modified) + { + bContext.addEvent(new Event(Event.MODIFY, Constants.BITSTREAM, getID(), null)); + modified = false; + } + if (modifiedMetadata) + { + bContext.addEvent(new Event(Event.MODIFY_METADATA, Constants.BITSTREAM, getID(), getDetails())); + modifiedMetadata = false; + clearDetails(); + } + DatabaseManager.update(bContext, bRow); } @@ -490,6 +527,8 @@ public class Bitstream extends DSpaceObject log.info(LogManager.getHeader(bContext, "delete_bitstream", "bitstream_id=" + getID())); + bContext.addEvent(new Event(Event.DELETE, Constants.BITSTREAM, getID(), String.valueOf(getSequenceID()))); + // Remove from cache bContext.removeCached(this, getID()); @@ -541,7 +580,7 @@ public class Bitstream extends DSpaceObject bRow.getIntColumn("bitstream_id")); // Build a list of Bundle objects - List bundles = new ArrayList(); + List bundles = new ArrayList(); while (tri.hasNext()) { diff --git a/dspace-api/src/main/java/org/dspace/content/Bundle.java b/dspace-api/src/main/java/org/dspace/content/Bundle.java index dc861f69e6..f741d5709a 100644 --- a/dspace-api/src/main/java/org/dspace/content/Bundle.java +++ b/dspace-api/src/main/java/org/dspace/content/Bundle.java @@ -53,6 +53,7 @@ import org.dspace.authorize.AuthorizeManager; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.LogManager; +import org.dspace.event.Event; import org.dspace.storage.rdbms.DatabaseManager; import org.dspace.storage.rdbms.TableRow; import org.dspace.storage.rdbms.TableRowIterator; @@ -80,7 +81,13 @@ public class Bundle extends DSpaceObject private TableRow bundleRow; /** The bitstreams in this bundle */ - private List bitstreams; + private List bitstreams; + + /** Flag set when data is modified, for events */ + private boolean modified; + + /** Flag set when metadata is modified, for events */ + private boolean modifiedMetadata; /** * Construct a bundle object with the given table row @@ -94,7 +101,7 @@ public class Bundle extends DSpaceObject { ourContext = context; bundleRow = row; - bitstreams = new ArrayList(); + bitstreams = new ArrayList(); // Get bitstreams TableRowIterator tri = DatabaseManager.queryTable( @@ -126,6 +133,8 @@ public class Bundle extends DSpaceObject // Cache ourselves context.cache(this, row.getIntColumn("bundle_id")); + + modified = modifiedMetadata = false; } /** @@ -192,6 +201,8 @@ public class Bundle extends DSpaceObject log.info(LogManager.getHeader(context, "create_bundle", "bundle_id=" + row.getIntColumn("bundle_id"))); + context.addEvent(new Event(Event.CREATE, Constants.BUNDLE, row.getIntColumn("bundle_id"), null)); + return new Bundle(context, row); } @@ -225,6 +236,7 @@ public class Bundle extends DSpaceObject public void setName(String name) { bundleRow.setColumn("name", name); + modifiedMetadata = true; } /** @@ -246,6 +258,7 @@ public class Bundle extends DSpaceObject public void setPrimaryBitstreamID(int bitstreamID) { bundleRow.setColumn("primary_bitstream_id", bitstreamID); + modified = true; } /** @@ -309,7 +322,7 @@ public class Bundle extends DSpaceObject */ public Item[] getItems() throws SQLException { - List items = new ArrayList(); + List items = new ArrayList(); // Get items TableRowIterator tri = DatabaseManager.queryTable( @@ -421,6 +434,8 @@ public class Bundle extends DSpaceObject // Add the bitstream object bitstreams.add(b); + ourContext.addEvent(new Event(Event.ADD, Constants.BUNDLE, getID(), Constants.BITSTREAM, b.getID(), String.valueOf(b.getSequenceID()))); + // copy authorization policies from bundle to bitstream // FIXME: multiple inclusion is affected by this... AuthorizeManager.inheritPolicies(ourContext, this, b); @@ -475,6 +490,8 @@ public class Bundle extends DSpaceObject } } + ourContext.addEvent(new Event(Event.REMOVE, Constants.BUNDLE, getID(), Constants.BITSTREAM, b.getID(), String.valueOf(b.getSequenceID()))); + // Delete the mapping row DatabaseManager.updateQuery(ourContext, "DELETE FROM bundle2bitstream WHERE bundle_id= ? "+ @@ -505,6 +522,17 @@ public class Bundle extends DSpaceObject log.info(LogManager.getHeader(ourContext, "update_bundle", "bundle_id=" + getID())); + if (modified) + { + ourContext.addEvent(new Event(Event.MODIFY, Constants.BUNDLE, getID(), null)); + modified = false; + } + if (modifiedMetadata) + { + ourContext.addEvent(new Event(Event.MODIFY_METADATA, Constants.BUNDLE, getID(), null)); + modifiedMetadata = false; + } + DatabaseManager.update(ourContext, bundleRow); } @@ -518,6 +546,8 @@ public class Bundle extends DSpaceObject log.info(LogManager.getHeader(ourContext, "delete_bundle", "bundle_id=" + getID())); + ourContext.addEvent(new Event(Event.DELETE, Constants.BUNDLE, getID(), getName())); + // Remove from cache ourContext.removeCached(this, getID()); diff --git a/dspace-api/src/main/java/org/dspace/content/Collection.java b/dspace-api/src/main/java/org/dspace/content/Collection.java index 60f79e0658..a12a422d22 100644 --- a/dspace-api/src/main/java/org/dspace/content/Collection.java +++ b/dspace-api/src/main/java/org/dspace/content/Collection.java @@ -41,7 +41,9 @@ package org.dspace.content; import java.io.IOException; import java.io.InputStream; -import java.sql.*; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.MissingResourceException; @@ -57,9 +59,8 @@ import org.dspace.core.Context; import org.dspace.core.I18nUtil; import org.dspace.core.LogManager; import org.dspace.eperson.Group; +import org.dspace.event.Event; import org.dspace.handle.HandleManager; -import org.dspace.history.HistoryManager; -import org.dspace.search.DSIndexer; import org.dspace.storage.rdbms.DatabaseManager; import org.dspace.storage.rdbms.TableRow; import org.dspace.storage.rdbms.TableRowIterator; @@ -99,6 +100,12 @@ public class Collection extends DSpaceObject /** Our Handle */ private String handle; + /** Flag set when data is modified, for events */ + private boolean modified; + + /** Flag set when metadata is modified, for events */ + private boolean modifiedMetadata; + /** * Groups corresponding to workflow steps - NOTE these start from one, so * workflowGroups[0] corresponds to workflow_step_1. @@ -162,6 +169,9 @@ public class Collection extends DSpaceObject // Cache ourselves context.cache(this, row.getIntColumn("collection_id")); + + modified = modifiedMetadata = false; + clearDetails(); } /** @@ -250,8 +260,7 @@ public class Collection extends DSpaceObject myPolicy.setGroup(anonymousGroup); myPolicy.update(); - HistoryManager.saveHistory(context, c, HistoryManager.CREATE, context - .getCurrentUser(), context.getExtraLogInfo()); + context.addEvent(new Event(Event.CREATE, Constants.COLLECTION, c.getID(), c.handle)); log.info(LogManager.getHeader(context, "create_collection", "collection_id=" + row.getIntColumn("collection_id")) @@ -275,7 +284,7 @@ public class Collection extends DSpaceObject TableRowIterator tri = DatabaseManager.queryTable(context, "collection", "SELECT * FROM collection ORDER BY name"); - List collections = new ArrayList(); + List collections = new ArrayList(); while (tri.hasNext()) { @@ -350,9 +359,20 @@ public class Collection extends DSpaceObject return collectionRow.getIntColumn("collection_id"); } + /** + * @see org.dspace.content.DSpaceObject#getHandle() + */ public String getHandle() { - return handle; + if(handle == null) { + try { + handle = HandleManager.findHandle(this.ourContext, this); + } catch (SQLException e) { + // TODO Auto-generated catch block + //e.printStackTrace(); + } + } + return handle; } /** @@ -397,6 +417,13 @@ public class Collection extends DSpaceObject } } collectionRow.setColumn(field, value); + modifiedMetadata = true; + addDetails(field); + } + + public String getName() + { + return getMetadata("name"); } /** @@ -469,6 +496,7 @@ public class Collection extends DSpaceObject + newLogo.getID())); } + modified = true; return logo; } @@ -527,6 +555,7 @@ public class Collection extends DSpaceObject { collectionRow.setColumn("workflow_step_" + step, g.getID()); } + modified = true; } /** @@ -571,6 +600,7 @@ public class Collection extends DSpaceObject AuthorizeManager.addPolicy(ourContext, this, Constants.ADD, submitters); + modified = true; return submitters; } @@ -624,6 +654,7 @@ public class Collection extends DSpaceObject admins); } + modified = true; return admins; } @@ -704,6 +735,7 @@ public class Collection extends DSpaceObject { collectionRow.setColumn("license", license); } + modified = true; } /** @@ -742,6 +774,7 @@ public class Collection extends DSpaceObject "collection_id=" + getID() + ",template_item_id=" + template.getID())); } + modified = true; } /** @@ -773,6 +806,7 @@ public class Collection extends DSpaceObject template.delete(); template = null; } + ourContext.addEvent(new Event(Event.MODIFY, Constants.COLLECTION, getID(), "remove_template_item")); } /** @@ -801,6 +835,8 @@ public class Collection extends DSpaceObject row.setColumn("item_id", item.getID()); DatabaseManager.update(ourContext, row); + + ourContext.addEvent(new Event(Event.ADD, Constants.COLLECTION, getID(), Constants.ITEM, item.getID(), item.getHandle())); } /** @@ -826,6 +862,8 @@ public class Collection extends DSpaceObject "AND item_id= ? ", getID(), item.getID()); + ourContext.addEvent(new Event(Event.REMOVE, Constants.COLLECTION, getID(), Constants.ITEM, item.getID(), item.getHandle())); + // Is the item an orphan? TableRowIterator tri = DatabaseManager.query(ourContext, "SELECT * FROM collection2item WHERE item_id= ? ", @@ -864,17 +902,22 @@ public class Collection extends DSpaceObject // Check authorisation canEdit(); - HistoryManager.saveHistory(ourContext, this, HistoryManager.MODIFY, - ourContext.getCurrentUser(), ourContext.getExtraLogInfo()); - log.info(LogManager.getHeader(ourContext, "update_collection", "collection_id=" + getID())); DatabaseManager.update(ourContext, collectionRow); - // reindex this collection (could be smarter, to only do when name - // changes) - DSIndexer.reIndexContent(ourContext, this); + if (modified) + { + ourContext.addEvent(new Event(Event.MODIFY, Constants.COLLECTION, getID(), null)); + modified = false; + } + if (modifiedMetadata) + { + ourContext.addEvent(new Event(Event.MODIFY_METADATA, Constants.COLLECTION, getID(), getDetails())); + modifiedMetadata = false; + clearDetails(); + } } public boolean canEditBoolean() throws java.sql.SQLException @@ -928,15 +971,11 @@ public class Collection extends DSpaceObject log.info(LogManager.getHeader(ourContext, "delete_collection", "collection_id=" + getID())); - // remove from index - DSIndexer.unIndexContent(ourContext, this); + ourContext.addEvent(new Event(Event.DELETE, Constants.COLLECTION, getID(), getHandle())); // Remove from cache ourContext.removeCached(this, getID()); - HistoryManager.saveHistory(ourContext, this, HistoryManager.REMOVE, - ourContext.getCurrentUser(), ourContext.getExtraLogInfo()); - // remove subscriptions - hmm, should this be in Subscription.java? DatabaseManager.updateQuery(ourContext, "DELETE FROM subscription WHERE collection_id= ? ", @@ -1062,7 +1101,7 @@ public class Collection extends DSpaceObject getID()); // Build a list of Community objects - List communities = new ArrayList(); + List communities = new ArrayList(); while (tri.hasNext()) { @@ -1162,7 +1201,7 @@ public class Collection extends DSpaceObject public static Collection[] findAuthorized(Context context, Community comm, int actionID) throws java.sql.SQLException { - List myResults = new ArrayList(); + List myResults = new ArrayList(); Collection[] myCollections = null; diff --git a/dspace-api/src/main/java/org/dspace/content/Community.java b/dspace-api/src/main/java/org/dspace/content/Community.java index d3f0560eae..4a9368218d 100644 --- a/dspace-api/src/main/java/org/dspace/content/Community.java +++ b/dspace-api/src/main/java/org/dspace/content/Community.java @@ -55,9 +55,8 @@ import org.dspace.core.Context; import org.dspace.core.I18nUtil; import org.dspace.core.LogManager; import org.dspace.eperson.Group; +import org.dspace.event.Event; import org.dspace.handle.HandleManager; -import org.dspace.history.HistoryManager; -import org.dspace.search.DSIndexer; import org.dspace.storage.rdbms.DatabaseManager; import org.dspace.storage.rdbms.TableRow; import org.dspace.storage.rdbms.TableRowIterator; @@ -89,6 +88,12 @@ public class Community extends DSpaceObject /** Handle, if any */ private String handle; + /** Flag set when data is modified, for events */ + private boolean modified; + + /** Flag set when metadata is modified, for events */ + private boolean modifiedMetadata; + /** * Construct a community object from a database row. * @@ -118,6 +123,9 @@ public class Community extends DSpaceObject // Cache ourselves context.cache(this, row.getIntColumn("community_id")); + + modified = modifiedMetadata = false; + clearDetails(); } /** @@ -198,8 +206,11 @@ public class Community extends DSpaceObject myPolicy.setGroup(anonymousGroup); myPolicy.update(); - HistoryManager.saveHistory(context, c, HistoryManager.CREATE, context - .getCurrentUser(), context.getExtraLogInfo()); + context.addEvent(new Event(Event.CREATE, Constants.COMMUNITY, c.getID(), c.handle)); + + // if creating a top-level Community, simulate an ADD event at the Site. + if (parent == null) + context.addEvent(new Event(Event.ADD, Constants.SITE, Site.SITE_ID, Constants.COMMUNITY, c.getID(), c.handle)); log.info(LogManager.getHeader(context, "create_community", "community_id=" + row.getIntColumn("community_id")) @@ -222,7 +233,7 @@ public class Community extends DSpaceObject TableRowIterator tri = DatabaseManager.queryTable(context, "community", "SELECT * FROM community ORDER BY name"); - List communities = new ArrayList(); + List communities = new ArrayList(); while (tri.hasNext()) { @@ -268,7 +279,7 @@ public class Community extends DSpaceObject + "(SELECT child_comm_id FROM community2community) " + "ORDER BY name"); - List topCommunities = new ArrayList(); + List topCommunities = new ArrayList(); while (tri.hasNext()) { @@ -306,9 +317,20 @@ public class Community extends DSpaceObject return communityRow.getIntColumn("community_id"); } + /** + * @see org.dspace.content.DSpaceObject#getHandle() + */ public String getHandle() { - return handle; + if(handle == null) { + try { + handle = HandleManager.findHandle(this.ourContext, this); + } catch (SQLException e) { + // TODO Auto-generated catch block + //e.printStackTrace(); + } + } + return handle; } /** @@ -353,6 +375,13 @@ public class Community extends DSpaceObject } } communityRow.setColumn(field, value); + modifiedMetadata = true; + addDetails(field); + } + + public String getName() + { + return getMetadata("name"); } /** @@ -418,6 +447,7 @@ public class Community extends DSpaceObject + newLogo.getID())); } + modified = true; return logo; } @@ -429,16 +459,22 @@ public class Community extends DSpaceObject // Check authorisation canEdit(); - HistoryManager.saveHistory(ourContext, this, HistoryManager.MODIFY, - ourContext.getCurrentUser(), ourContext.getExtraLogInfo()); - log.info(LogManager.getHeader(ourContext, "update_community", "community_id=" + getID())); DatabaseManager.update(ourContext, communityRow); - // now re-index this Community - DSIndexer.reIndexContent(ourContext, this); + if (modified) + { + ourContext.addEvent(new Event(Event.MODIFY, Constants.COMMUNITY, getID(), null)); + modified = false; + } + if (modifiedMetadata) + { + ourContext.addEvent(new Event(Event.MODIFY_METADATA, Constants.COMMUNITY, getID(), getDetails())); + modifiedMetadata = false; + clearDetails(); + } } /** @@ -449,7 +485,7 @@ public class Community extends DSpaceObject */ public Collection[] getCollections() throws SQLException { - List collections = new ArrayList(); + List collections = new ArrayList(); // Get the table rows TableRowIterator tri = DatabaseManager.queryTable( @@ -496,7 +532,7 @@ public class Community extends DSpaceObject */ public Community[] getSubcommunities() throws SQLException { - List subcommunities = new ArrayList(); + List subcommunities = new ArrayList(); // Get the table rows TableRowIterator tri = DatabaseManager.queryTable( @@ -585,7 +621,7 @@ public class Community extends DSpaceObject */ public Community[] getAllParents() throws SQLException { - List parentList = new ArrayList(); + List parentList = new ArrayList(); Community parent = getParentCommunity(); while (parent != null) @@ -649,6 +685,8 @@ public class Community extends DSpaceObject mappingRow.setColumn("community_id", getID()); mappingRow.setColumn("collection_id", c.getID()); + ourContext.addEvent(new Event(Event.ADD, Constants.COMMUNITY, getID(), Constants.COLLECTION, c.getID(), c.getHandle())); + DatabaseManager.update(ourContext, mappingRow); } // close the TableRowIterator to free up resources @@ -702,6 +740,8 @@ public class Community extends DSpaceObject mappingRow.setColumn("parent_comm_id", getID()); mappingRow.setColumn("child_comm_id", c.getID()); + ourContext.addEvent(new Event(Event.ADD, Constants.COMMUNITY, getID(), Constants.COMMUNITY, c.getID(), c.getHandle())); + DatabaseManager.update(ourContext, mappingRow); } // close the TableRowIterator to free up resources @@ -728,6 +768,8 @@ public class Community extends DSpaceObject "DELETE FROM community2collection WHERE community_id= ? "+ "AND collection_id= ? ", getID(), c.getID()); + ourContext.addEvent(new Event(Event.REMOVE, Constants.COMMUNITY, getID(), Constants.COLLECTION, c.getID(), c.getHandle())); + // Is the community an orphan? TableRowIterator tri = DatabaseManager.query(ourContext, "SELECT * FROM community2collection WHERE collection_id= ? ", @@ -774,6 +816,8 @@ public class Community extends DSpaceObject "DELETE FROM community2community WHERE parent_comm_id= ? " + " AND child_comm_id= ? ", getID(),c.getID()); + ourContext.addEvent(new Event(Event.REMOVE, Constants.COMMUNITY, getID(), Constants.COMMUNITY, c.getID(), c.getHandle())); + // Is the subcommunity an orphan? TableRowIterator tri = DatabaseManager.query(ourContext, "SELECT * FROM community2community WHERE child_comm_id= ? ", @@ -834,11 +878,7 @@ public class Community extends DSpaceObject log.info(LogManager.getHeader(ourContext, "delete_community", "community_id=" + getID())); - // remove from the search index - DSIndexer.unIndexContent(ourContext, this); - - HistoryManager.saveHistory(ourContext, this, HistoryManager.REMOVE, - ourContext.getCurrentUser(), ourContext.getExtraLogInfo()); + ourContext.addEvent(new Event(Event.DELETE, Constants.COMMUNITY, getID(), getHandle())); // Remove from cache ourContext.removeCached(this, getID()); diff --git a/dspace-api/src/main/java/org/dspace/content/DSpaceObject.java b/dspace-api/src/main/java/org/dspace/content/DSpaceObject.java index ad7d5651c4..14ecd5fb6e 100644 --- a/dspace-api/src/main/java/org/dspace/content/DSpaceObject.java +++ b/dspace-api/src/main/java/org/dspace/content/DSpaceObject.java @@ -39,11 +39,53 @@ */ package org.dspace.content; +import java.sql.SQLException; + +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; + /** * Abstract base class for DSpace objects */ public abstract class DSpaceObject { + // accumulate information to add to "detail" element of content Event, + // e.g. to document metadata fields touched, etc. + private StringBuffer eventDetails = null; + + /** + * Reset the cache of event details. + */ + protected void clearDetails() + { + eventDetails = null; + } + + /** + * Add a string to the cache of event details. Automatically + * separates entries with a comma. + * Subclass can just start calling addDetails, since it creates + * the cache if it needs to. + * @param detail detail string to add. + */ + protected void addDetails(String d) + { + if (eventDetails == null) + eventDetails = new StringBuffer(d); + else + eventDetails.append(", ").append(d); + } + + /** + * @returns summary of event details, or null if there are none. + */ + protected String getDetails() + { + return (eventDetails == null ? null : eventDetails.toString()); + } + /** * Get the type of this object, found in Constants * @@ -65,4 +107,40 @@ public abstract class DSpaceObject * one */ public abstract String getHandle(); + + /** + * Get a proper name for the object. This may return null. + * Name should be suitable for display in a user interface. + * + * @return Name for the object, or null if it doesn't have + * one + */ + abstract public String getName(); + + /** + * Generic find for when the precise type of a DSO is not known, just the + * a pair of type number and database ID. + * + * @param context - the context + * @param type - type number + * @param id - id within table of type'd objects + * @return the object found, or null if it does not exist. + * @throws SQLException only upon failure accessing the database. + */ + public static DSpaceObject find(Context context, int type, int id) + throws SQLException + { + switch (type) + { + case Constants.BITSTREAM : return Bitstream.find(context, id); + case Constants.BUNDLE : return Bundle.find(context, id); + case Constants.ITEM : return Item.find(context, id); + case Constants.COLLECTION: return Collection.find(context, id); + case Constants.COMMUNITY : return Community.find(context, id); + case Constants.GROUP : return Group.find(context, id); + case Constants.EPERSON : return EPerson.find(context, id); + case Constants.SITE : return Site.find(context, id); + } + return null; + } } diff --git a/dspace-api/src/main/java/org/dspace/content/InstallItem.java b/dspace-api/src/main/java/org/dspace/content/InstallItem.java index 28bdbdd3f2..72bfa083dd 100644 --- a/dspace-api/src/main/java/org/dspace/content/InstallItem.java +++ b/dspace-api/src/main/java/org/dspace/content/InstallItem.java @@ -46,7 +46,6 @@ import org.dspace.authorize.AuthorizeException; import org.dspace.core.ConfigurationManager; import org.dspace.core.Context; import org.dspace.handle.HandleManager; -import org.dspace.search.DSIndexer; /** * Support to install item in the archive @@ -145,9 +144,6 @@ public class InstallItem // save changes ;-) item.update(); - // add item to search and browse indices - DSIndexer.indexContent(c, item); - // remove in-progress submission is.deleteWrapper(); diff --git a/dspace-api/src/main/java/org/dspace/content/Item.java b/dspace-api/src/main/java/org/dspace/content/Item.java index 7e75a8baff..b522868628 100644 --- a/dspace-api/src/main/java/org/dspace/content/Item.java +++ b/dspace-api/src/main/java/org/dspace/content/Item.java @@ -61,11 +61,10 @@ import org.dspace.core.ConfigurationManager; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.LogManager; +import org.dspace.event.Event; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.handle.HandleManager; -import org.dspace.history.HistoryManager; -import org.dspace.search.DSIndexer; import org.dspace.storage.rdbms.DatabaseManager; import org.dspace.storage.rdbms.TableRow; import org.dspace.storage.rdbms.TableRowIterator; @@ -104,10 +103,10 @@ public class Item extends DSpaceObject private EPerson submitter; /** The bundles in this item - kept in sync with DB */ - private List bundles; + private List bundles; /** The Dublin Core metadata - a list of DCValue objects. */ - private List dublinCore; + private List dublinCore; /** Handle, if any */ private String handle; @@ -118,6 +117,12 @@ public class Item extends DSpaceObject */ private boolean dublinCoreChanged; + /** + * True if anything else was changed since last update() + * (to drive event mechanism) + */ + private boolean modified; + /** * Construct an item with the given table row * @@ -132,7 +137,9 @@ public class Item extends DSpaceObject ourContext = context; itemRow = row; dublinCoreChanged = false; - dublinCore = new ArrayList(); + modified = false; + dublinCore = new ArrayList(); + clearDetails(); // Get Dublin Core metadata TableRowIterator tri = DatabaseManager.queryTable(ourContext, "MetadataValue", @@ -247,8 +254,7 @@ public class Item extends DSpaceObject i.update(); context.setIgnoreAuthorization(false); - HistoryManager.saveHistory(context, i, HistoryManager.CREATE, context - .getCurrentUser(), context.getExtraLogInfo()); + context.addEvent(new Event(Event.CREATE, Constants.ITEM, i.getID(), null)); log.info(LogManager.getHeader(context, "create_item", "item_id=" + row.getIntColumn("item_id"))); @@ -312,7 +318,15 @@ public class Item extends DSpaceObject */ public String getHandle() { - return handle; + if(handle == null) { + try { + handle = HandleManager.findHandle(this.ourContext, this); + } catch (SQLException e) { + // TODO Auto-generated catch block + //e.printStackTrace(); + } + } + return handle; } /** @@ -364,6 +378,7 @@ public class Item extends DSpaceObject public void setArchived(boolean isArchived) { itemRow.setColumn("in_archive", isArchived); + modified = true; } /** @@ -375,6 +390,7 @@ public class Item extends DSpaceObject public void setOwningCollection(Collection c) { itemRow.setColumn("owning_collection", c.getID()); + modified = true; } /** @@ -493,13 +509,9 @@ public class Item extends DSpaceObject String lang) { // Build up list of matching values - List values = new ArrayList(); - Iterator i = dublinCore.iterator(); - - while (i.hasNext()) + List values = new ArrayList(); + for (DCValue dcv : dublinCore) { - DCValue dcv = (DCValue) i.next(); - if (match(schema, element, qualifier, lang, dcv)) { // We will return a copy of the object in case it is altered @@ -661,6 +673,8 @@ public class Item extends DSpaceObject dcv.value = null; } dublinCore.add(dcv); + addDetails(schema+"."+element+((qualifier==null)? "": "."+qualifier)); + } if (values.length > 0) @@ -752,13 +766,9 @@ public class Item extends DSpaceObject String lang) { // We will build a list of values NOT matching the values to clear - List values = new ArrayList(); - Iterator i = dublinCore.iterator(); - - while (i.hasNext()) + List values = new ArrayList(); + for (DCValue dcv : dublinCore) { - DCValue dcv = (DCValue) i.next(); - if (!match(schema, element, qualifier, lang, dcv)) { values.add(dcv); @@ -885,6 +895,7 @@ public class Item extends DSpaceObject { itemRow.setColumnNull("submitter_id"); } + modified = true; } /** @@ -895,7 +906,7 @@ public class Item extends DSpaceObject */ public Collection[] getCollections() throws SQLException { - List collections = new ArrayList(); + List collections = new ArrayList(); // Get collection table rows TableRowIterator tri = DatabaseManager.queryTable(ourContext,"collection", @@ -940,7 +951,7 @@ public class Item extends DSpaceObject */ public Community[] getCommunities() throws SQLException { - List communities = new ArrayList(); + List communities = new ArrayList(); // Get community table rows TableRowIterator tri = DatabaseManager.queryTable(ourContext,"community", @@ -990,7 +1001,7 @@ public class Item extends DSpaceObject { if (bundles == null) { - bundles = new ArrayList(); + bundles = new ArrayList(); // Get bundles TableRowIterator tri = DatabaseManager.queryTable(ourContext, "bundle", "SELECT bundle.* FROM bundle, item2bundle WHERE " + @@ -1035,7 +1046,7 @@ public class Item extends DSpaceObject */ public Bundle[] getBundles(String name) throws SQLException { - List matchingBundles = new ArrayList(); + List matchingBundles = new ArrayList(); // now only keep bundles with matching names Bundle[] bunds = getBundles(); @@ -1121,6 +1132,8 @@ public class Item extends DSpaceObject mappingRow.setColumn("item_id", getID()); mappingRow.setColumn("bundle_id", b.getID()); DatabaseManager.update(ourContext, mappingRow); + + ourContext.addEvent(new Event(Event.ADD, Constants.ITEM, getID(), Constants.BUNDLE, b.getID(), b.getName())); } /** @@ -1161,6 +1174,8 @@ public class Item extends DSpaceObject "AND bundle_id= ? ", getID(), b.getID()); + ourContext.addEvent(new Event(Event.REMOVE, Constants.ITEM, getID(), Constants.BUNDLE, b.getID(), b.getName())); + // If the bundle is orphaned, it's removed TableRowIterator tri = DatabaseManager.query(ourContext, "SELECT * FROM item2bundle WHERE bundle_id= ? ", @@ -1238,7 +1253,7 @@ public class Item extends DSpaceObject */ public Bitstream[] getNonInternalBitstreams() throws SQLException { - List bitstreamList = new ArrayList(); + List bitstreamList = new ArrayList(); // Go through the bundles and bitstreams picking out ones which aren't // of internal formats @@ -1359,9 +1374,6 @@ public class Item extends DSpaceObject AuthorizeManager.authorizeAction(ourContext, this, Constants.WRITE); } - HistoryManager.saveHistory(ourContext, this, HistoryManager.MODIFY, - ourContext.getCurrentUser(), ourContext.getExtraLogInfo()); - log.info(LogManager.getHeader(ourContext, "update_item", "item_id=" + getID())); @@ -1419,7 +1431,7 @@ public class Item extends DSpaceObject // Keys are Strings: "element" or "element.qualifier" // Values are Integers indicating number of values written for a // element/qualifier - Map elementCount = new HashMap(); + Map elementCount = new HashMap(); DatabaseManager.update(ourContext, itemRow); @@ -1430,12 +1442,8 @@ public class Item extends DSpaceObject removeMetadataFromDatabase(); // Add in-memory DC - Iterator i = dublinCore.iterator(); - - while (i.hasNext()) + for (DCValue dcv : dublinCore) { - DCValue dcv = (DCValue) i.next(); - // Get the DC Type int schemaID; MetadataSchema schema = MetadataSchema.find(ourContext,dcv.schema); @@ -1497,11 +1505,16 @@ public class Item extends DSpaceObject metadata.create(ourContext); } + ourContext.addEvent(new Event(Event.MODIFY_METADATA, Constants.ITEM, getID(), getDetails())); dublinCoreChanged = false; + clearDetails(); } - // Update browse indices - Browse.itemChanged(ourContext, this); + if (modified) + { + ourContext.addEvent(new Event(Event.MODIFY, Constants.ITEM, getID(), null)); + modified = false; + } } /** @@ -1560,13 +1573,7 @@ public class Item extends DSpaceObject // Update item in DB update(); - // Invoke History system - HistoryManager.saveHistory(ourContext, this, HistoryManager.MODIFY, e, - ourContext.getExtraLogInfo()); - - // Remove from indicies - Browse.itemRemoved(ourContext, getID()); - DSIndexer.unIndexContent(ourContext, this); + ourContext.addEvent(new Event(Event.MODIFY, Constants.ITEM, getID(), "WITHDRAW")); // and all of our authorization policies // FIXME: not very "multiple-inclusion" friendly @@ -1621,14 +1628,7 @@ public class Item extends DSpaceObject // Update item in DB update(); - // Invoke History system - HistoryManager.saveHistory(ourContext, this, HistoryManager.MODIFY, e, - ourContext.getExtraLogInfo()); - - // Add to indicies - // Remove - update() already performs this - // Browse.itemAdded(ourContext, this); - DSIndexer.indexContent(ourContext, this); + ourContext.addEvent(new Event(Event.MODIFY, Constants.ITEM, getID(), "REINSTATE")); // authorization policies if (colls.length > 0) @@ -1656,8 +1656,7 @@ public class Item extends DSpaceObject */ void delete() throws SQLException, AuthorizeException, IOException { - HistoryManager.saveHistory(ourContext, this, HistoryManager.REMOVE, - ourContext.getCurrentUser(), ourContext.getExtraLogInfo()); + ourContext.addEvent(new Event(Event.DELETE, Constants.ITEM, getID(), getHandle())); log.info(LogManager.getHeader(ourContext, "delete_item", "item_id=" + getID())); @@ -1665,12 +1664,19 @@ public class Item extends DSpaceObject // Remove from cache ourContext.removeCached(this, getID()); - // Remove from indices, if appropriate + // Remove from browse indices, if appropriate + /** XXX FIXME + ** Although all other Browse index updates are managed through + ** Event consumers, removing an Item *must* be done *here* (inline) + ** because otherwise, tables are left in an inconsistent state + ** and the DB transaction will fail. + ** Any fix would involve too much work on Browse code that + ** is likely to be replaced soon anyway. --lcs, Aug 2006 + **/ if (isArchived()) { // Remove from Browse indices Browse.itemRemoved(ourContext, getID()); - DSIndexer.unIndexContent(ourContext, this); } // Delete the Dublin Core @@ -1687,15 +1693,6 @@ public class Item extends DSpaceObject // remove all of our authorization policies AuthorizeManager.removeAllPolicies(ourContext, this); - // Remove any Handle - // FIXME: This is sort of a "tentacle" - HandleManager should provide - // a way of doing this. Plus, deleting a Handle may have ramifications - // that need considering. - DatabaseManager.updateQuery(ourContext, - "DELETE FROM handle WHERE resource_type_id= ? " + - "AND resource_id= ? ", - Constants.ITEM,getID()); - // Finally remove item row DatabaseManager.delete(ourContext, itemRow); } @@ -1982,4 +1979,9 @@ public class Item extends DSpaceObject return false; } + public String getName() + { + DCValue t[] = getMetadata("dc", "title", null, Item.ANY); + return (t.length >= 1) ? t[0].value : null; + } } diff --git a/dspace-api/src/main/java/org/dspace/content/Site.java b/dspace-api/src/main/java/org/dspace/content/Site.java new file mode 100644 index 0000000000..479660f1ed --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/Site.java @@ -0,0 +1,141 @@ +/* + * Site.java + * + * Version: $Revision: 1.8 $ + * + * Date: $Date: 2005/04/20 14:22:34 $ + * + * Copyright (c) 2002-2005, Hewlett-Packard Company and Massachusetts + * Institute of Technology. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of the Hewlett-Packard Company nor the name of the + * Massachusetts Institute of Technology nor the names of their + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ +package org.dspace.content; + +import java.sql.SQLException; +import java.net.URI; +import java.io.IOException; + + +import org.dspace.core.ConfigurationManager; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.core.LogManager; +import org.dspace.eperson.EPerson; +import org.dspace.eperson.Group; +import org.dspace.event.Event; +import org.dspace.authorize.AuthorizeException; + +/** + * Represents the root of the DSpace Archive. + * By default, the handle suffix "0" represents the Site, e.g. "1721.1/0" + */ +public class Site extends DSpaceObject +{ + /** "database" identifier of the site */ + public static final int SITE_ID = 0; + + // cache for Handle that is persistent ID for entire site. + private static String handle = null; + + private static Site theSite = null; + + /** + * Get the type of this object, found in Constants + * + * @return type of the object + */ + public int getType() + { + return Constants.SITE; + } + + /** + * Get the internal ID (database primary key) of this object + * + * @return internal ID of object + */ + public int getID() + { + return SITE_ID; + } + + /** + * Get the Handle of the object. This may return null + * + * @return Handle of the object, or null if it doesn't have + * one + */ + public String getHandle() + { + return getSiteHandle(); + } + + /** + * Static method to return site Handle without creating a Site. + * @returns handle of the Site. + */ + public static String getSiteHandle() + { + if (handle == null) + handle = ConfigurationManager.getProperty("handle.prefix")+"/"+ + String.valueOf(SITE_ID); + return handle; + } + + /** + * Get Site object corresponding to db id (which is ignroed). + * @param context the context. + * @param id integer database id, ignored. + * @returns Site object. + */ + public static DSpaceObject find(Context context, int id) + throws SQLException + { + if (theSite == null) + theSite = new Site(); + return theSite; + } + + void delete() + throws SQLException, AuthorizeException, IOException + { + } + + public void update() + throws SQLException, AuthorizeException, IOException + { + } + + public String getName() + { + return ConfigurationManager.getProperty("dspace.name"); + } +} diff --git a/dspace-api/src/main/java/org/dspace/content/WorkspaceItem.java b/dspace-api/src/main/java/org/dspace/content/WorkspaceItem.java index 3a005898d8..c2611c7c8e 100644 --- a/dspace-api/src/main/java/org/dspace/content/WorkspaceItem.java +++ b/dspace-api/src/main/java/org/dspace/content/WorkspaceItem.java @@ -52,7 +52,6 @@ import org.dspace.core.Context; import org.dspace.core.LogManager; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; -import org.dspace.history.HistoryManager; import org.dspace.storage.rdbms.DatabaseManager; import org.dspace.storage.rdbms.TableRow; import org.dspace.storage.rdbms.TableRowIterator; @@ -286,9 +285,6 @@ public class WorkspaceItem implements InProgressSubmission WorkspaceItem wi = new WorkspaceItem(c, row); - HistoryManager.saveHistory(c, wi, HistoryManager.CREATE, c - .getCurrentUser(), c.getExtraLogInfo()); - return wi; } @@ -463,8 +459,6 @@ public class WorkspaceItem implements InProgressSubmission public void update() throws SQLException, AuthorizeException, IOException { // Authorisation is checked by the item.update() method below - HistoryManager.saveHistory(ourContext, this, HistoryManager.MODIFY, - ourContext.getCurrentUser(), ourContext.getExtraLogInfo()); log.info(LogManager.getHeader(ourContext, "update_workspace_item", "workspace_item_id=" + getID())); @@ -499,9 +493,6 @@ public class WorkspaceItem implements InProgressSubmission + "original submitter to delete a workspace item"); } - HistoryManager.saveHistory(ourContext, this, HistoryManager.REMOVE, - ourContext.getCurrentUser(), ourContext.getExtraLogInfo()); - log.info(LogManager.getHeader(ourContext, "delete_workspace_item", "workspace_item_id=" + getID() + "item_id=" + item.getID() + "collection_id=" + collection.getID())); @@ -536,9 +527,6 @@ public class WorkspaceItem implements InProgressSubmission // Check authorisation. We check permissions on the enclosed item. AuthorizeManager.authorizeAction(ourContext, item, Constants.WRITE); - HistoryManager.saveHistory(ourContext, this, HistoryManager.REMOVE, - ourContext.getCurrentUser(), ourContext.getExtraLogInfo()); - log.info(LogManager.getHeader(ourContext, "delete_workspace_item", "workspace_item_id=" + getID() + "item_id=" + item.getID() + "collection_id=" + collection.getID())); diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/StreamDisseminationCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/StreamDisseminationCrosswalk.java new file mode 100644 index 0000000000..ac879392d6 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/StreamDisseminationCrosswalk.java @@ -0,0 +1,95 @@ +/* + * StreamDisseminationCrosswalk + * + * Version: $Revision: 1.4 $ + * + * Date: $Date: 2006/04/10 04:11:09 $ + * + * Copyright (c) 2002-2005, Hewlett-Packard Company and Massachusetts + * Institute of Technology. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of the Hewlett-Packard Company nor the name of the + * Massachusetts Institute of Technology nor the names of their + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +package org.dspace.content.crosswalk; + +import java.io.OutputStream; +import java.io.IOException; +import java.sql.SQLException; + +import org.dspace.core.Constants; +import org.dspace.content.DSpaceObject; +import org.dspace.core.Context; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.crosswalk.CrosswalkException; + + +/** + * A class implementing this interface crosswalk metadata directly + * from a DSpace Object to an output stream, in a specific format. + *

+ * Stream-oriented crosswalks are intended to be used for metadata + * formats which are either (a) not XML-based, or (b) too bulky for the + * DOM-ish in-memory model developed for the METS and IMSCP packagers. + * The METS packagers (all subclasses of AbstractMETSDisseminator / AbstractMETSIngester + * are equipped to call these crosswalks as well as the XML-based ones, + * just refer to the desired crosswalk by its plugin name. + * + * @author Larry Stone + * @version $Revision: 1.0 $ + */ +public interface StreamDisseminationCrosswalk +{ + /** + * Predicate: Can this disseminator crosswalk the given object. + * + * @param dso dspace object, e.g. an Item. + * @return true when disseminator is capable of producing metadata. + */ + public boolean canDisseminate(Context context, DSpaceObject dso); + + /** + * Execute crosswalk on the given object, sending output to the stream. + * + * @param context the DSpace context + * @param dso the DSpace Object whose metadata to export. + * @param out output stream to write to + * + * @throws CrosswalkInternalException (CrosswalkException) failure of the crosswalk itself. + * @throws CrosswalkObjectNotSupported (CrosswalkException) Cannot crosswalk this kind of DSpace object. + * @throws IOException I/O failure in services this calls + * @throws SQLException Database failure in services this calls + * @throws AuthorizeException current user not authorized for this operation. + */ + public void disseminate(Context context, DSpaceObject dso, OutputStream out) + throws CrosswalkException, IOException, SQLException, AuthorizeException; + + public String getMIMEType(); +} diff --git a/dspace-api/src/main/java/org/dspace/content/crosswalk/StreamIngestionCrosswalk.java b/dspace-api/src/main/java/org/dspace/content/crosswalk/StreamIngestionCrosswalk.java new file mode 100644 index 0000000000..595a1fb154 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/content/crosswalk/StreamIngestionCrosswalk.java @@ -0,0 +1,85 @@ +/* + * StreamIngestionCrosswalk + * + * Version: $Revision: 1.4 $ + * + * Date: $Date: 2006/04/10 04:11:09 $ + * + * Copyright (c) 2002-2005, Hewlett-Packard Company and Massachusetts + * Institute of Technology. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of the Hewlett-Packard Company nor the name of the + * Massachusetts Institute of Technology nor the names of their + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +package org.dspace.content.crosswalk; + +import java.io.InputStream; +import java.io.IOException; +import java.sql.SQLException; + +import org.dspace.core.Constants; +import org.dspace.content.DSpaceObject; +import org.dspace.core.Context; +import org.dspace.authorize.AuthorizeException; +import org.dspace.content.crosswalk.CrosswalkException; + + +/** + * A class implementing this interface can crosswalk metadata directly + * from a stream (assumed to be in a specific format) to the object. + *

+ * Stream-oriented crosswalks are intended to be used for metadata + * formats which are either (a) not XML-based, or (b) too bulky for the + * DOM-ish in-memory model developed for the METS and IMSCP packagers. + * The METS packagers (all subclasses of AbstractMETSDisseminator / AbstractMETSIngester + * are equipped to call these crosswalks as well as the XML-based ones, + * just refer to the desired crosswalk by its plugin name. + * + * @author Larry Stone + * @version $Revision: 1.0 $ + */ +public interface StreamIngestionCrosswalk +{ + /** + * Execute crosswalk on the given object, taking input from the stream. + * + * @param context the DSpace context + * @param dso the DSpace Object whose metadata is being ingested. + * @param out input stream containing the metadata. + * + * @throws CrosswalkInternalException (CrosswalkException) failure of the crosswalk itself. + * @throws CrosswalkObjectNotSupported (CrosswalkException) Cannot crosswalk this kind of DSpace object. + * @throws IOException I/O failure in services this calls + * @throws SQLException Database failure in services this calls + * @throws AuthorizeException current user not authorized for this operation. + */ + public void ingest(Context context, DSpaceObject dso, InputStream in, String MIMEType) + throws CrosswalkException, IOException, SQLException, AuthorizeException; +} diff --git a/dspace-api/src/main/java/org/dspace/core/Context.java b/dspace-api/src/main/java/org/dspace/core/Context.java index f9e59e9990..67744010ec 100644 --- a/dspace-api/src/main/java/org/dspace/core/Context.java +++ b/dspace-api/src/main/java/org/dspace/core/Context.java @@ -51,6 +51,9 @@ import java.util.Map; import org.apache.log4j.Logger; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; +import org.dspace.event.Event; +import org.dspace.event.EventManager; +import org.dspace.event.Dispatcher; import org.dspace.storage.rdbms.DatabaseManager; /** @@ -68,7 +71,6 @@ import org.dspace.storage.rdbms.DatabaseManager; * The context object is also used as a cache for CM API objects. * * - * @author Robert Tansley * @version $Revision$ */ public class Context @@ -95,6 +97,12 @@ public class Context /** Group IDs of special groups user is a member of */ private List specialGroups; + + /** Content events */ + private List events = null; + + /** Event dispatcher name */ + private String dispName = null; /** * Construct a new context object. A database connection is opened. No user @@ -241,7 +249,7 @@ public class Context try { // Commit any changes made as part of the transaction - connection.commit(); + commit(); } finally { @@ -259,12 +267,79 @@ public class Context * if there was an error completing the database transaction * or closing the connection */ - public void commit() throws SQLException - { + public void commit() throws SQLException { // Commit any changes made as part of the transaction - connection.commit(); + Dispatcher dispatcher = null; + + try { + if (events != null) { + + if (dispName == null) { + dispName = EventManager.DEFAULT_DISPATCHER; + } + + dispatcher = EventManager.getDispatcher(dispName); + + connection.commit(); + dispatcher.dispatch(this); + } else { + connection.commit(); + } + + } finally { + events = null; + if(dispatcher != null) + { + /* + * TODO return dispatcher via internal method dispatcher.close(); + * and remove the returnDispatcher method from EventManager. + */ + EventManager.returnDispatcher(dispName, dispatcher); + } + } + } + /** + * Select an event dispatcher, null selects the default + * + */ + public void setDispatcher(String dispatcher) + { + if (log.isDebugEnabled()) + { + log.debug(this.toString() + ": setDispatcher(\"" + dispatcher + "\")"); + } + dispName = dispatcher; + } + + /** + * Add an event to be dispatched when this context is committed. + * + * @param event + */ + public void addEvent(Event event) + { + if (events == null) + { + events = new ArrayList(); + } + + events.add(event); + } + + /** + * Get the current event list. If there is a separate list of events from + * already-committed operations combine that with current list. + * + * @return List of all available events. + */ + public List getEvents() + { + return events; + } + + /** * Close the context, without committing any of the changes performed using * this context. The database connection is freed. No exception is thrown if @@ -287,10 +362,12 @@ public class Context { DatabaseManager.freeConnection(connection); connection = null; + events = null; } } /** + * * Find out if this context is valid. Returns false if this * context has been aborted or completed. * diff --git a/dspace-api/src/main/java/org/dspace/core/Utils.java b/dspace-api/src/main/java/org/dspace/core/Utils.java index 3a554800c7..6a9a82105c 100644 --- a/dspace-api/src/main/java/org/dspace/core/Utils.java +++ b/dspace-api/src/main/java/org/dspace/core/Utils.java @@ -52,6 +52,13 @@ import java.text.ParseException; import java.util.Random; import java.util.regex.Matcher; import java.util.regex.Pattern; +import java.util.Date; +import java.util.Calendar; +import java.util.GregorianCalendar; +import java.text.SimpleDateFormat; +import java.text.ParseException; + +import org.apache.log4j.Logger; /** * Utility functions for DSpace. @@ -61,6 +68,9 @@ import java.util.regex.Pattern; */ public class Utils { + /** log4j logger */ + private static Logger log = Logger.getLogger(Utils.class); + private static final Pattern DURATION_PATTERN = Pattern .compile("(\\d+)([smhdwy])"); @@ -82,6 +92,31 @@ public class Utils private static VMID vmid = new VMID(); + // for parseISO8601Date + private static SimpleDateFormat parseFmt[] = + { + // first try at parsing, has milliseconds (note General time zone) + new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSSz"), + + // second try at parsing, no milliseconds (note General time zone) + new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ssz"), + + + // finally, try without any timezone (defaults to current TZ) + new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSS"), + + new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss") + }; + + // for formatISO8601Date + // output canonical format (note RFC22 time zone, easier to hack) + private static SimpleDateFormat outFmtSecond = new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ssZ"); + + // output format with millsecond precision + private static SimpleDateFormat outFmtMillisec = new SimpleDateFormat("yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSSZ"); + + private static Calendar outCal = GregorianCalendar.getInstance(); + /** Private Constructor */ private Utils() { @@ -350,4 +385,64 @@ public class Utils return qint * multiplier; } + + /** + * Translates timestamp from an ISO 8601-standard format, which + * is commonly used in XML and RDF documents. + * This method is synchronized because it depends on a non-reentrant + * static DateFormat (more efficient than creating a new one each call). + * + * @param s the input string + * @return Date object, or null if there is a problem translating. + */ + public static synchronized Date parseISO8601Date(String s) + { + // attempt to normalize the timezone to something we can parse; + // SimpleDateFormat can't handle "Z" + char tzSign = s.charAt(s.length()-6); + if (s.endsWith("Z")) + s = s.substring(0, s.length()-1) + "GMT+00:00"; + + // check for trailing timezone + else if (tzSign == '-' || tzSign == '+') + s = s.substring(0, s.length()-6) + "GMT" + s.substring(s.length()-6); + + // try to parse without millseconds + ParseException lastError = null; + for (int i = 0; i < parseFmt.length; ++i) + { + try + { + return parseFmt[i].parse(s); + } + catch (ParseException e) + { + lastError = e; + } + } + if (lastError != null) + log.error("Error parsing date:", lastError); + return null; + } + + /** + * Convert a Date to String in the ISO 8601 standard format. + * The RFC822 timezone is almost right, still need to insert ":". + * This method is synchronized because it depends on a non-reentrant + * static DateFormat (more efficient than creating a new one each call). + * + * @param d the input Date + * @return String containing formatted date. + */ + public static synchronized String formatISO8601Date(Date d) + { + String result; + outCal.setTime(d); + if (outCal.get(Calendar.MILLISECOND) == 0) + result = outFmtSecond.format(d); + else + result = outFmtMillisec.format(d); + int rl = result.length(); + return result.substring(0, rl-2) + ":" + result.substring(rl-2); + } } diff --git a/dspace-api/src/main/java/org/dspace/eperson/EPerson.java b/dspace-api/src/main/java/org/dspace/eperson/EPerson.java index 090abe0555..451f3e9d57 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/EPerson.java +++ b/dspace-api/src/main/java/org/dspace/eperson/EPerson.java @@ -52,7 +52,7 @@ import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.LogManager; import org.dspace.core.Utils; -import org.dspace.history.HistoryManager; +import org.dspace.event.Event; import org.dspace.storage.rdbms.DatabaseManager; import org.dspace.storage.rdbms.TableRow; import org.dspace.storage.rdbms.TableRowIterator; @@ -89,6 +89,12 @@ public class EPerson extends DSpaceObject /** The row in the table representing this eperson */ private TableRow myRow; + /** Flag set when data is modified, for events */ + private boolean modified; + + /** Flag set when metadata is modified, for events */ + private boolean modifiedMetadata; + /** * Construct an EPerson * @@ -104,6 +110,8 @@ public class EPerson extends DSpaceObject // Cache ourselves context.cache(this, row.getIntColumn("eperson_id")); + modified = modifiedMetadata = false; + clearDetails(); } /** @@ -426,8 +434,7 @@ public class EPerson extends DSpaceObject log.info(LogManager.getHeader(context, "create_eperson", "eperson_id=" + e.getID())); - HistoryManager.saveHistory(context, e, HistoryManager.REMOVE, context - .getCurrentUser(), context.getExtraLogInfo()); + context.addEvent(new Event(Event.CREATE, Constants.EPERSON, e.getID(), null)); return e; } @@ -446,9 +453,6 @@ public class EPerson extends DSpaceObject "You must be an admin to delete an EPerson"); } - HistoryManager.saveHistory(myContext, this, HistoryManager.REMOVE, - myContext.getCurrentUser(), myContext.getExtraLogInfo()); - // check for presence of eperson in tables that // have constraints on eperson_id Vector constraintList = getDeleteConstraints(); @@ -460,9 +464,14 @@ public class EPerson extends DSpaceObject throw new EPersonDeletionException(constraintList); } + myContext.addEvent(new Event(Event.DELETE, Constants.EPERSON, getID(), getEmail())); + // Remove from cache myContext.removeCached(this, getID()); + // XXX FIXME: This sidesteps the object model code so it won't + // generate REMOVE events on the affected Groups. + // Remove any group memberships first DatabaseManager.updateQuery(myContext, "DELETE FROM EPersonGroup2EPerson WHERE eperson_id= ? ", @@ -549,6 +558,7 @@ public class EPerson extends DSpaceObject } myRow.setColumn("email", s); + modified = true; } /** @@ -575,6 +585,7 @@ public class EPerson extends DSpaceObject } myRow.setColumn("netid", s); + modified = true; } /** @@ -621,6 +632,7 @@ public class EPerson extends DSpaceObject public void setFirstName(String firstname) { myRow.setColumn("firstname", firstname); + modified = true; } /** @@ -642,6 +654,7 @@ public class EPerson extends DSpaceObject public void setLastName(String lastname) { myRow.setColumn("lastname", lastname); + modified = true; } /** @@ -653,6 +666,7 @@ public class EPerson extends DSpaceObject public void setCanLogIn(boolean login) { myRow.setColumn("can_log_in", login); + modified = true; } /** @@ -674,6 +688,7 @@ public class EPerson extends DSpaceObject public void setRequireCertificate(boolean isrequired) { myRow.setColumn("require_certificate", isrequired); + modified = true; } /** @@ -695,6 +710,7 @@ public class EPerson extends DSpaceObject public void setSelfRegistered(boolean sr) { myRow.setColumn("self_registered", sr); + modified = true; } /** @@ -737,6 +753,8 @@ public class EPerson extends DSpaceObject public void setMetadata(String field, String value) { myRow.setColumn(field, value); + modifiedMetadata = true; + addDetails(field); } /** @@ -751,6 +769,7 @@ public class EPerson extends DSpaceObject String encoded = Utils.getMD5(s); myRow.setColumn("password", encoded); + modified = true; } /** @@ -786,8 +805,17 @@ public class EPerson extends DSpaceObject log.info(LogManager.getHeader(myContext, "update_eperson", "eperson_id=" + getID())); - HistoryManager.saveHistory(myContext, this, HistoryManager.MODIFY, - myContext.getCurrentUser(), myContext.getExtraLogInfo()); + if (modified) + { + myContext.addEvent(new Event(Event.MODIFY, Constants.EPERSON, getID(), null)); + modified = false; + } + if (modifiedMetadata) + { + myContext.addEvent(new Event(Event.MODIFY_METADATA, Constants.EPERSON, getID(), getDetails())); + modifiedMetadata = false; + clearDetails(); + } } /** @@ -830,7 +858,7 @@ public class EPerson extends DSpaceObject */ public Vector getDeleteConstraints() throws SQLException { - Vector tableList = new Vector(); + Vector tableList = new Vector(); // check for eperson in item table TableRowIterator tri = DatabaseManager.query(myContext, @@ -872,4 +900,10 @@ public class EPerson extends DSpaceObject // explaining to the user why the eperson cannot be deleted. return tableList; } + + public String getName() + { + return getEmail(); + } + } diff --git a/dspace-api/src/main/java/org/dspace/eperson/Group.java b/dspace-api/src/main/java/org/dspace/eperson/Group.java index e9afa0cf64..ab0947451b 100644 --- a/dspace-api/src/main/java/org/dspace/eperson/Group.java +++ b/dspace-api/src/main/java/org/dspace/eperson/Group.java @@ -47,6 +47,7 @@ import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; +import java.io.IOException; import org.apache.log4j.Logger; import org.dspace.authorize.AuthorizeException; @@ -56,6 +57,7 @@ import org.dspace.core.ConfigurationManager; import org.dspace.core.Constants; import org.dspace.core.Context; import org.dspace.core.LogManager; +import org.dspace.event.Event; import org.dspace.storage.rdbms.DatabaseManager; import org.dspace.storage.rdbms.TableRow; import org.dspace.storage.rdbms.TableRowIterator; @@ -83,9 +85,9 @@ public class Group extends DSpaceObject private TableRow myRow; /** lists of epeople and groups in the group */ - private List epeople = new ArrayList(); + private List epeople = new ArrayList(); - private List groups = new ArrayList(); + private List groups = new ArrayList(); /** lists that need to be written out again */ private boolean epeopleChanged = false; @@ -95,6 +97,9 @@ public class Group extends DSpaceObject /** is this just a stub, or is all data loaded? */ private boolean isDataLoaded = false; + /** Flag set when metadata is modified, for events */ + private boolean modifiedMetadata; + /** * Construct a Group from a given context and tablerow * @@ -108,6 +113,9 @@ public class Group extends DSpaceObject // Cache ourselves context.cache(this, row.getIntColumn("eperson_group_id")); + + modifiedMetadata = false; + clearDetails(); } /** @@ -213,6 +221,8 @@ public class Group extends DSpaceObject log.info(LogManager.getHeader(context, "create_group", "group_id=" + g.getID())); + context.addEvent(new Event(Event.CREATE, Constants.GROUP, g.getID(), null)); + return g; } @@ -245,6 +255,8 @@ public class Group extends DSpaceObject public void setName(String name) { myRow.setColumn("name", name); + modifiedMetadata = true; + addDetails("name"); } /** @@ -264,6 +276,8 @@ public class Group extends DSpaceObject epeople.add(e); epeopleChanged = true; + + myContext.addEvent(new Event(Event.ADD, Constants.GROUP, getID(), Constants.EPERSON, e.getID(), e.getEmail())); } /** @@ -283,6 +297,8 @@ public class Group extends DSpaceObject groups.add(g); groupsChanged = true; + + myContext.addEvent(new Event(Event.ADD, Constants.GROUP, getID(), Constants.GROUP, g.getID(), g.getName())); } /** @@ -298,6 +314,7 @@ public class Group extends DSpaceObject if (epeople.remove(e)) { epeopleChanged = true; + myContext.addEvent(new Event(Event.REMOVE, Constants.GROUP, getID(), Constants.EPERSON, e.getID(), e.getEmail())); } } @@ -313,6 +330,7 @@ public class Group extends DSpaceObject if (groups.remove(g)) { groupsChanged = true; + myContext.addEvent(new Event(Event.REMOVE, Constants.GROUP, getID(), Constants.GROUP, g.getID(), g.getName())); } } @@ -397,9 +415,9 @@ public class Group extends DSpaceObject public static Group[] allMemberGroups(Context c, EPerson e) throws SQLException { - List groupList = new ArrayList(); + List groupList = new ArrayList(); - Set myGroups = allMemberGroupIDs(c, e); + Set myGroups = allMemberGroupIDs(c, e); // now convert those Integers to Groups Iterator i = myGroups.iterator(); @@ -419,7 +437,7 @@ public class Group extends DSpaceObject * @return Set of Integer groupIDs * @throws SQLException */ - public static Set allMemberGroupIDs(Context c, EPerson e) + public static Set allMemberGroupIDs(Context c, EPerson e) throws SQLException { // two queries - first to get groups eperson is a member of @@ -429,7 +447,7 @@ public class Group extends DSpaceObject "SELECT * FROM epersongroup2eperson WHERE eperson_id= ?", e.getID()); - Set groupIDs = new HashSet(); + Set groupIDs = new HashSet(); while (tri.hasNext()) { @@ -513,9 +531,9 @@ public class Group extends DSpaceObject public static EPerson[] allMembers(Context c, Group g) throws SQLException { - List epersonList = new ArrayList(); + List epersonList = new ArrayList(); - Set myEpeople = allMemberIDs(c, g); + Set myEpeople = allMemberIDs(c, g); // now convert those Integers to EPerson objects Iterator i = myEpeople.iterator(); @@ -538,19 +556,19 @@ public class Group extends DSpaceObject * @return Set of Integer epersonIDs * @throws SQLException */ - public static Set allMemberIDs(Context c, Group g) + public static Set allMemberIDs(Context c, Group g) throws SQLException { // two queries - first to get all groups which are a member of this group // second query gets all members of each group in the first query - Set epeopleIDs = new HashSet(); + Set epeopleIDs = new HashSet(); // Get all groups which are a member of this group TableRowIterator tri = DatabaseManager.queryTable(c, "group2groupcache", "SELECT * FROM group2groupcache WHERE parent_id= ? ", g.getID()); - Set groupIDs = new HashSet(); + Set groupIDs = new HashSet(); while (tri.hasNext()) { @@ -611,7 +629,7 @@ public class Group extends DSpaceObject private static boolean epersonInGroup(Context c, int groupID, EPerson e) throws SQLException { - Set groupIDs = Group.allMemberGroupIDs(c, e); + Set groupIDs = Group.allMemberGroupIDs(c, e); return groupIDs.contains(new Integer(groupID)); } @@ -868,6 +886,9 @@ public class Group extends DSpaceObject public void delete() throws SQLException { // FIXME: authorizations + + myContext.addEvent(new Event(Event.DELETE, Constants.GROUP, getID(), getName())); + // Remove from cache myContext.removeCached(this, getID()); @@ -964,6 +985,13 @@ public class Group extends DSpaceObject // FIXME: Check authorisation DatabaseManager.update(myContext, myRow); + if (modifiedMetadata) + { + myContext.addEvent(new Event(Event.MODIFY_METADATA, Constants.GROUP, getID(), getDetails())); + modifiedMetadata = false; + clearDetails(); + } + // Redo eperson mappings if they've changed if (epeopleChanged) { @@ -1062,7 +1090,7 @@ public class Group extends DSpaceObject TableRowIterator tri = DatabaseManager.queryTable(myContext, "group2group", "SELECT * FROM group2group"); - Map parents = new HashMap(); + Map> parents = new HashMap>(); while (tri.hasNext()) { @@ -1074,7 +1102,7 @@ public class Group extends DSpaceObject // if parent doesn't have an entry, create one if (!parents.containsKey(parentID)) { - Set children = new HashSet(); + Set children = new HashSet(); // add child id to the list children.add(childID); @@ -1084,7 +1112,7 @@ public class Group extends DSpaceObject { // parent has an entry, now add the child to the parent's record // of children - Set children = (Set) parents.get(parentID); + Set children = parents.get(parentID); children.add(childID); } } @@ -1103,7 +1131,7 @@ public class Group extends DSpaceObject { Integer parentID = (Integer) i.next(); - Set myChildren = getChildren(parents, parentID); + Set myChildren = getChildren(parents, parentID); Iterator j = myChildren.iterator(); @@ -1112,7 +1140,7 @@ public class Group extends DSpaceObject // child of a parent Integer childID = (Integer) j.next(); - ((Set) parents.get(parentID)).add(childID); + ((Set) parents.get(parentID)).add(childID); } } @@ -1127,7 +1155,7 @@ public class Group extends DSpaceObject { Integer parent = (Integer) pi.next(); - Set children = (Set) parents.get(parent); + Set children = parents.get(parent); Iterator ci = children.iterator(); // child iterator while (ci.hasNext()) @@ -1158,16 +1186,16 @@ public class Group extends DSpaceObject * the parent you're interested in * @return Map whose keys are all of the children of a parent */ - private Set getChildren(Map parents, Integer parent) + private Set getChildren(Map> parents, Integer parent) { - Set myChildren = new HashSet(); + Set myChildren = new HashSet(); // degenerate case, this parent has no children if (!parents.containsKey(parent)) return myChildren; // got this far, so we must have children - Set children = (Set) parents.get(parent); + Set children = parents.get(parent); // now iterate over all of the children Iterator i = children.iterator(); diff --git a/dspace-api/src/main/java/org/dspace/event/BasicDispatcher.java b/dspace-api/src/main/java/org/dspace/event/BasicDispatcher.java new file mode 100644 index 0000000000..4b651702b3 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/event/BasicDispatcher.java @@ -0,0 +1,183 @@ +/* + * BasicDispatcher.java + * + * Version: $Revision$ + * + * Date: $Date$ + * + * Copyright (c) 2002-2007, Hewlett-Packard Company and Massachusetts + * Institute of Technology. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of the Hewlett-Packard Company nor the name of the + * Massachusetts Institute of Technology nor the names of their + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ +package org.dspace.event; + +import java.util.Iterator; +import java.util.List; + +import org.apache.log4j.Logger; +import org.dspace.core.Context; +import org.dspace.core.Utils; + +/** + * BasicDispatcher implements the primary task of a Dispatcher: it delivers a + * filtered list of events, synchronously, to a configured list of consumers. It + * may be extended for more elaborate behavior. + * + * @version $Revision$ + */ +public class BasicDispatcher extends Dispatcher +{ + + public BasicDispatcher(String name) + { + super(name); + } + + /** log4j category */ + private static Logger log = Logger.getLogger(BasicDispatcher.class); + + public void addConsumerProfile(ConsumerProfile cp) + throws IllegalArgumentException + { + if (consumers.containsKey(cp.getName())) + throw new IllegalArgumentException( + "This dispatcher already has a consumer named \"" + + cp.getName() + "\""); + + consumers.put(cp.getName(), cp); + + if (log.isDebugEnabled()) + { + int n = 0; + for (Iterator i = cp.getFilters().iterator(); i.hasNext(); ++n) + { + int f[] = (int[]) i.next(); + log.debug("Adding Consumer=\"" + cp.getName() + "\", instance=" + + cp.getConsumer().toString() + ", filter[" + + String.valueOf(n) + "]=(ObjMask=" + + String.valueOf(f[Event.SUBJECT_MASK]) + + ", EventMask=" + String.valueOf(f[Event.EVENT_MASK]) + + ")"); + } + } + } + + /** + * Dispatch all events added to this Context according to configured + * consumers. + * + * @param ctx + * the execution context + */ + public void dispatch(Context ctx) + { + if (!consumers.isEmpty()) + { + List events = ctx.getEvents(); + + if (events == null) + { + return; + } + + if (log.isDebugEnabled()) + log.debug("Processing queue of " + + String.valueOf(events.size()) + " events."); + + // transaction identifier applies to all events created in + // this context for the current transaction. Prefix it with + // some letters so RDF readers don't mistake it for an integer. + String tid = "TX" + Utils.generateKey(); + + for (Iterator ei = events.iterator(); ei.hasNext();) + { + Event event = (Event) ei.next(); + event.setDispatcher(getIdentifier()); + event.setTransactionID(tid); + + if (log.isDebugEnabled()) + log.debug("Iterating over " + + String.valueOf(consumers.values().size()) + + " consumers..."); + + for (Iterator ci = consumers.values().iterator(); ci.hasNext();) + { + ConsumerProfile cp = (ConsumerProfile) ci.next(); + + if (event.pass(cp.getFilters())) + { + if (log.isDebugEnabled()) + log.debug("Sending event to \"" + cp.getName() + + "\": " + event.toString()); + + try + { + cp.getConsumer().consume(ctx, event); + + // Record that the event has been consumed by this + // consumer + event.setBitSet(cp.getName()); + } + catch (Exception e) + { + log.error("Consumer(\"" + cp.getName() + + "\").consume threw: " + e.toString(), e); + } + } + + } + } + + // Call end on the consumers that got synchronous events. + for (Iterator ci = consumers.values().iterator(); ci.hasNext();) + { + ConsumerProfile cp = (ConsumerProfile) ci.next(); + if (cp != null) + { + if (log.isDebugEnabled()) + log.debug("Calling end for consumer \"" + cp.getName() + + "\""); + + try + { + cp.getConsumer().end(ctx); + } + catch (Exception e) + { + log.error("Error in Consumer(\"" + cp.getName() + + "\").end: " + e.toString(), e); + } + } + } + } + } + +} diff --git a/dspace-api/src/main/java/org/dspace/event/Consumer.java b/dspace-api/src/main/java/org/dspace/event/Consumer.java new file mode 100644 index 0000000000..eaf83b639e --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/event/Consumer.java @@ -0,0 +1,91 @@ +/* + * Consumer.java + * + * Version: $Revision$ + * + * Date: $Date$ + * + * Copyright (c) 2002-2007, Hewlett-Packard Company and Massachusetts + * Institute of Technology. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of the Hewlett-Packard Company nor the name of the + * Massachusetts Institute of Technology nor the names of their + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +package org.dspace.event; + +import org.dspace.core.Context; + +/** + * Interface for content event consumers. Note that the consumer cannot tell if + * it is invoked synchronously or asynchronously; the consumer interface and + * sequence of calls is the same for both. Asynchronous consumers may see more + * consume() calls between the start and end of the event stream, if they are + * invoked asynchronously, once in a long time period, rather than synchronously + * after every Context.commit(). + * + * @version $Revision$ + */ +public interface Consumer +{ + /** + * Initialize - allocate any resources required to operate. This may include + * initializing any pooled JMS resources. Called ONCE when created by the + * dispatcher pool. This should be used to set up expensive resources that + * will remain for the lifetime of the consumer. + */ + public void initialize() throws Exception; + + /** + * Consume an event; events may get filtered at the dispatcher level, hiding + * it from the consumer. This behavior is based on the dispatcher/consumer + * configuration. Should include logic to initialize any resources required + * for a batch of events. + * + * @param ctx + * the execution context object + * + * @param event + * the content event + */ + public void consume(Context ctx, Event event) throws Exception; + + /** + * Signal that there are no more events queued in this event stream and + * event processing for the preceding consume calls should be finished up. + */ + public void end(Context ctx) throws Exception; + + /** + * Finish - free any allocated resources. Called when consumer (via it's + * parent dispatcher) is going to be destroyed by the dispatcher pool. + */ + public void finish(Context ctx) throws Exception; + +} diff --git a/dspace-api/src/main/java/org/dspace/event/ConsumerProfile.java b/dspace-api/src/main/java/org/dspace/event/ConsumerProfile.java new file mode 100644 index 0000000000..221dff8a59 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/event/ConsumerProfile.java @@ -0,0 +1,179 @@ +/* + * ConsumerProfile.java + * + * Version: $Revision$ + * + * Date: $Date$ + * + * Copyright (c) 2002-2007, Hewlett-Packard Company and Massachusetts + * Institute of Technology. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of the Hewlett-Packard Company nor the name of the + * Massachusetts Institute of Technology nor the names of their + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ +package org.dspace.event; + +import java.util.ArrayList; +import java.util.List; + +import org.apache.log4j.Logger; +import org.dspace.core.ConfigurationManager; + +/** + * An instance of this class contains the configuration profile of a specific, + * named Consumer, in the context of a specific + * Dispatcher. This + * includes the name, the class to instantiate and event filters. Note that all + * characteristics are "global" and the same for all dispatchers. + * + * @version $Revision$ + */ +public class ConsumerProfile +{ + /** log4j category */ + private static Logger log = Logger.getLogger(EventManager.class); + + /** Name matching the key in DSpace Configuration */ + private String name; + + /** Instance of configured consumer class */ + private Consumer consumer; + + /** Filters - each is an array of 2 bitmasks, action mask and subject mask */ + private List filters; + + // Prefix of keys in DSpace Configuration. + private final String CONSUMER_PREFIX = "event.consumer."; + + /** + * Constructor. + */ + private ConsumerProfile(String name) + { + this.name = name; + } + + /** + * Factory method, create new profile from configuration. + * + * @param name + * configuration name of the consumer profile + * @returns a new ConsumerProfile; never null. + */ + public static ConsumerProfile makeConsumerProfile(String name) + throws IllegalArgumentException, ClassNotFoundException, + InstantiationException, IllegalAccessException + { + ConsumerProfile result = new ConsumerProfile(name); + result.readConfiguration(); + return result; + } + + // Get class and filters from DSpace Configuration. + private void readConfiguration() throws IllegalArgumentException, + ClassNotFoundException, InstantiationException, + IllegalAccessException + { + String className = ConfigurationManager.getProperty(CONSUMER_PREFIX + + name + ".class"); + String filterString = ConfigurationManager.getProperty(CONSUMER_PREFIX + + name + ".filters"); + + if (className == null) + throw new IllegalArgumentException( + "No class configured for consumer named: " + name); + if (filterString == null) + throw new IllegalArgumentException( + "No filters configured for consumer named: " + name); + + consumer = (Consumer) Class.forName(className.trim()).newInstance(); + + // Each "filter" is + : ... + filters = new ArrayList(); + String part[] = filterString.trim().split(":"); + for (int j = 0; j < part.length; ++j) + { + String fpart[] = part[j].split("\\+"); + if (fpart.length != 2) + log + .error("Bad Filter clause in consumer stanza in Configuration entry for " + + CONSUMER_PREFIX + + name + + ".consumers: " + + part[j]); + else + { + int filter[] = new int[2]; + filter[0] = filter[1] = 0; + String objectNames[] = fpart[0].split("\\|"); + for (int k = 0; k < objectNames.length; ++k) + { + int ot = Event.parseObjectType(objectNames[k]); + if (ot == 0) + log + .error("Bad ObjectType in Consumer Stanza in Configuration entry for " + + CONSUMER_PREFIX + + name + + ".consumers: " + objectNames[k]); + else + filter[Event.SUBJECT_MASK] |= ot; + } + String eventNames[] = fpart[1].split("\\|"); + for (int k = 0; k < eventNames.length; ++k) + { + int et = Event.parseEventType(eventNames[k]); + if (et == 0) + log + .error("Bad EventType in Consumer Stanza in Configuration entry for " + + CONSUMER_PREFIX + + name + + ".consumers: " + eventNames[k]); + else + filter[Event.EVENT_MASK] |= et; + } + filters.add(filter); + } + } + } + + public Consumer getConsumer() + { + return consumer; + } + + public List getFilters() + { + return filters; + } + + public String getName() + { + return name; + } +} diff --git a/dspace-api/src/main/java/org/dspace/event/Dispatcher.java b/dspace-api/src/main/java/org/dspace/event/Dispatcher.java new file mode 100644 index 0000000000..754eb7fccb --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/event/Dispatcher.java @@ -0,0 +1,110 @@ +/* + * Dispatcher.java + * + * Version: $Revision$ + * + * Date: $Date$ + * + * Copyright (c) 2002-2007, Hewlett-Packard Company and Massachusetts + * Institute of Technology. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of the Hewlett-Packard Company nor the name of the + * Massachusetts Institute of Technology nor the names of their + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +package org.dspace.event; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.dspace.core.Context; + +/** + * Interface for event dispatchers. The primary role of a dispatcher is to + * deliver a set of events to a configured list of consumers. It may also + * transform, consolidate, and otherwise optimize the event stream prior to + * delivering events to its consumers. + * + * @version $Revision$ + */ +public abstract class Dispatcher +{ + protected String name; + + /** unique identifer of this dispatcher - cached hash of its text Name */ + protected int identifier; + + /** + * Map of consumers by their configured name. + */ + protected Map consumers = new HashMap(); + + protected Dispatcher(String name) + { + super(); + this.name = name; + this.identifier = name.hashCode(); + } + + public Collection getConsumers() + { + return consumers.values(); + } + + /** + * @returns unique integer that identifies this Dispatcher configuration. + */ + public int getIdentifier() + { + return identifier; + } + + /** + * Add a consumer to the end of the list. + * + * @param consumer + * the event consumer to add + * @param filter + * the event filter to apply + */ + public abstract void addConsumerProfile(ConsumerProfile cp) + throws IllegalArgumentException; + + /** + * Dispatch all events added to this Context according to configured + * consumers. + * + * @param ctx + * the execution context object + */ + public abstract void dispatch(Context ctx); + +} diff --git a/dspace-api/src/main/java/org/dspace/event/Event.java b/dspace-api/src/main/java/org/dspace/event/Event.java new file mode 100644 index 0000000000..f7455970df --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/event/Event.java @@ -0,0 +1,614 @@ +/* + * Event.java + * + * Version: $Revision$ + * + * Date: $Date$ + * + * Copyright (c) 2002-2007, Hewlett-Packard Company and Massachusetts + * Institute of Technology. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of the Hewlett-Packard Company nor the name of the + * Massachusetts Institute of Technology nor the names of their + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +package org.dspace.event; + +import java.io.Serializable; +import java.sql.SQLException; +import java.util.BitSet; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import org.apache.log4j.Logger; +import org.dspace.content.DSpaceObject; +import org.dspace.core.Constants; +import org.dspace.core.Context; + +/** + * An Event object represents a single action that changed one object in the + * DSpace data model. An "atomic" action at the application or business-logic + * API level may spawn many of these events. + *

+ * This class includes tools to help set and use the contents of the event. Note + * that it describes DSpace data object types in two ways: by the type + * identifiers in the Constants class, and also by an Event-specific bitmask + * (used by its internal filters). All public API calls use the Constants + * version of the data model types. + *

+ * Note that the type of the event itself is actually descriptive of the + * action it performs: ADD, MODIFY, etc. The most significant + * elements of the event are: + *

+ *
- (Action) Type
- Subject -- DSpace object to which the action + * applies, e.g. the Collection to which an ADD adds a member.
- Object -- + * optional, when present it is the other object effected by an action, e.g. the + * Item ADDed to a Collection by an ADD.
- detail -- a textual summary of + * what changed, content and its significance varies by the combination of + * action and subject type.
- timestamp -- exact millisecond timestamp at + * which event was logged. + * + * @version $Revision$ + */ +public class Event implements Serializable +{ + /** ---------- Constants ------------- * */ + + /** Event (Action) types */ + public static final int CREATE = 1 << 0; // create new object + + public static final int MODIFY = 1 << 1; // modify object + + public static final int MODIFY_METADATA = 1 << 2; // modify object + + public static final int ADD = 1 << 3; // add content to container + + public static final int REMOVE = 1 << 4; // remove content from container + + public static final int DELETE = 1 << 5; // destroy object + + /** Index of filter parts in their array: */ + public static final int SUBJECT_MASK = 0; // mask of subject types + + public static final int EVENT_MASK = 1; // mask of event type + + // XXX NOTE: keep this up to date with any changes to event (action) types. + private static final String eventTypeText[] = { "CREATE", "MODIFY", + "MODIFY_METADATA", "ADD", "REMOVE", "DELETE" }; + + /** XXX NOTE: These constants must be kept synchronized * */ + /** XXX NOTE: with ALL_OBJECTS_MASK *AND* objTypeToMask hash * */ + private static final int NONE = 0; + + private static final int BITSTREAM = 1 << Constants.BITSTREAM; // 0 + + private static final int BUNDLE = 1 << Constants.BUNDLE; // 1 + + private static final int ITEM = 1 << Constants.ITEM; // 2 + + private static final int COLLECTION = 1 << Constants.COLLECTION; // 3 + + private static final int COMMUNITY = 1 << Constants.COMMUNITY; // 4 + + private static final int SITE = 1 << Constants.SITE; // 5 + + private static final int GROUP = 1 << Constants.GROUP; // 6 + + private static final int EPERSON = 1 << Constants.EPERSON; // 7 + + private static final int ALL_OBJECTS_MASK = BITSTREAM | BUNDLE | ITEM + | COLLECTION | COMMUNITY | SITE | GROUP | EPERSON; + + private static Map objTypeToMask = new HashMap(); + + private static Map objMaskToType = new HashMap(); + static + { + objTypeToMask.put(new Integer(Constants.BITSTREAM), new Integer( + BITSTREAM)); + objMaskToType.put(new Integer(BITSTREAM), new Integer( + Constants.BITSTREAM)); + + objTypeToMask.put(new Integer(Constants.BUNDLE), new Integer(BUNDLE)); + objMaskToType.put(new Integer(BUNDLE), new Integer(Constants.BUNDLE)); + + objTypeToMask.put(new Integer(Constants.ITEM), new Integer(ITEM)); + objMaskToType.put(new Integer(ITEM), new Integer(Constants.ITEM)); + + objTypeToMask.put(new Integer(Constants.COLLECTION), new Integer( + COLLECTION)); + objMaskToType.put(new Integer(COLLECTION), new Integer( + Constants.COLLECTION)); + + objTypeToMask.put(new Integer(Constants.COMMUNITY), new Integer( + COMMUNITY)); + objMaskToType.put(new Integer(COMMUNITY), new Integer( + Constants.COMMUNITY)); + + objTypeToMask.put(new Integer(Constants.SITE), new Integer(SITE)); + objMaskToType.put(new Integer(SITE), new Integer(Constants.SITE)); + + objTypeToMask.put(new Integer(Constants.GROUP), new Integer(GROUP)); + objMaskToType.put(new Integer(GROUP), new Integer(Constants.GROUP)); + + objTypeToMask.put(new Integer(Constants.EPERSON), new Integer(EPERSON)); + objMaskToType.put(new Integer(EPERSON), new Integer(Constants.EPERSON)); + } + + /** ---------- Event Fields ------------- * */ + + /** identifier of Dispatcher that created this event (hash of its name) */ + private int dispatcher; + + /** event (action) type - above enumeration */ + private int eventType; + + /** object-type of SUBJECT - see above enumeration */ + private int subjectType; + + /** content model identifier */ + private int subjectID; + + /** object-type of SUBJECT - see above enumeration */ + private int objectType = NONE; + + /** content model identifier */ + private int objectID = -1; + + /** timestamp */ + private long timeStamp; + + /** "detail" - arbitrary field for relevant detail, */ + /** e.g. former handle for DELETE event since obj is no longer available. */ + /** + * FIXME This field is not a complete view of the DSpaceObject that was + * modified. Providing these objects to the consumer (e.g. by storing + * lifecycle versions of the changed objects in the context) would provide + * for more complex consumer abilities that are beyond our purview. + */ + private String detail; + + /** unique key to bind together events from one context's transaction */ + private String transactionID; + + /** identity of authenticated user, i.e. context.getCurrentUser() */ + /** only needed in the event for marshalling for asynch event messages */ + private int currentUser = -1; + + /** copy of context's "extraLogInfo" filed, used only for */ + /** marshalling for asynch event messages */ + private String extraLogInfo = null; + + private BitSet consumedBy = new BitSet(); + + /** log4j category */ + private static Logger log = Logger.getLogger(Event.class); + + /** + * Constructor. + * + * @param eventType + * action type, e.g. Event.ADD + * @param subjectType + * DSpace Object Type of subject e.g. Constants.ITEM. + * @param subjectID + * database ID of subject instance. + * @param detail + * detail information that depends on context. + */ + public Event(int eventType, int subjectType, int subjectID, String detail) + { + this.eventType = eventType; + this.subjectType = coreTypeToMask(subjectType); + this.subjectID = subjectID; + timeStamp = System.currentTimeMillis(); + this.detail = detail; + } + + /** + * Constructor. + * + * @param eventType + * action type, e.g. Event.ADD + * @param subjectType + * DSpace Object Type of subject e.g. Constants.ITEM. + * @param subjectID + * database ID of subject instance. + * @param objectType + * DSpace Object Type of object e.g. Constants.BUNDLE. + * @param objectID + * database ID of object instance. + * @param detail + * detail information that depends on context. + * @param + */ + public Event(int eventType, int subjectType, int subjectID, int objectType, + int objectID, String detail) + { + this.eventType = eventType; + this.subjectType = coreTypeToMask(subjectType); + this.subjectID = subjectID; + this.objectType = coreTypeToMask(objectType); + this.objectID = objectID; + timeStamp = System.currentTimeMillis(); + this.detail = detail; + } + + /** + * Compare two events. Ignore any difference in the timestamps. Also ignore + * transactionID since that is not always set initially. + * + * @param other + * the event to compare this one to + * @returns true if events are "equal", false otherwise. + */ + public boolean equals(Event other) + { + return (this.detail == null ? other.detail == null : this.detail + .equals(other.detail)) + && this.eventType == other.eventType + && this.subjectType == other.subjectType + && this.subjectID == other.subjectID + && this.objectType == other.objectType + && this.objectID == other.objectID; + } + + /** + * Set the identifier of the dispatcher that first processed this event. + * + * @param id + * the unique (hash code) value characteristic of the dispatcher. + */ + public void setDispatcher(int id) + { + dispatcher = id; + } + + // translate a "core.Constants" object type value to local bitmask value. + private static int coreTypeToMask(int core) + { + Integer mask = (Integer) objTypeToMask.get(new Integer(core)); + if (mask == null) + return -1; + else + return mask.intValue(); + } + + // translate bitmask object-type to "core.Constants" object type. + private static int maskTypeToCore(int mask) + { + Integer core = (Integer) objMaskToType.get(new Integer(mask)); + if (core == null) + return -1; + else + return core.intValue(); + } + + /** + * Get the DSpace object which is the "object" of an event. + * + * @returns DSpaceObject or null if none can be found or no object was set. + */ + public DSpaceObject getObject(Context context) throws SQLException + { + int type = getObjectType(); + int id = getObjectID(); + if (type < 0 || id < 0) + return null; + else + return DSpaceObject.find(context, type, id); + } + + /** + * Syntactic sugar to get the DSpace object which is the "subject" of an + * event. + * + * @returns DSpaceObject or null if none can be found. + */ + public DSpaceObject getSubject(Context context) throws SQLException + { + return DSpaceObject.find(context, getSubjectType(), getSubjectID()); + } + + /** + * @returns database ID of subject of this event. + */ + public int getSubjectID() + { + return subjectID; + } + + /** + * @returns database ID of object of this event, or -1 if none was set. + */ + public int getObjectID() + { + return objectID; + } + + /** + * @returns type number (e.g. Constants.ITEM) of subject of this event. + */ + public int getSubjectType() + { + return maskTypeToCore(subjectType); + } + + /** + * @returns type number (e.g. Constants.ITEM) of object of this event, or -1 + * if none was set. + */ + public int getObjectType() + { + return maskTypeToCore(objectType); + } + + /** + * @returns type of subject of this event as a String, e.g. for logging. + */ + public String getSubjectTypeAsString() + { + int i = log2(subjectType); + if (i >= 0 && i < Constants.typeText.length) + return Constants.typeText[i]; + else + return "(Unknown)"; + } + + /** + * @returns type of object of this event as a String, e.g. for logging. + */ + public String getObjectTypeAsString() + { + int i = log2(objectType); + if (i >= 0 && i < Constants.typeText.length) + return Constants.typeText[i]; + else + return "(Unknown)"; + } + + /** + * Translate a textual DSpace Object type name into an event subject-type + * mask. NOTE: This returns a BIT-MASK, not a numeric type value; the mask + * is only used within the event system. + * + * @param s + * text name of object type. + * @returns numeric value of object type or 0 for error. + */ + public static int parseObjectType(String s) + { + if (s.equals("*") | s.equalsIgnoreCase("all")) + return ALL_OBJECTS_MASK; + else + { + int id = Constants.getTypeID(s.toUpperCase()); + if (id >= 0) + return 1 << id; + } + return 0; + } + + /** + * @returns event-type (i.e. action) this event, one of the masks like + * Event.ADD defined above. + */ + public int getEventType() + { + return eventType; + } + + /** + * Get the text name of event (action) type. + * + * @returns event-type (i.e. action) this event as a String, e.g. for + * logging. + */ + public String getEventTypeAsString() + { + int i = log2(eventType); + if (i >= 0 && i < eventTypeText.length) + return eventTypeText[i]; + else + return "(Unknown)"; + } + + /** + * Interpret named event type. + * + * @param text + * name of event type. + * @returns numeric value of event type or 0 for error. + */ + public static int parseEventType(String s) + { + if (s.equals("*") | s.equalsIgnoreCase("all")) + { + int result = 0; + for (int i = 0; i < eventTypeText.length; ++i) + result |= (1 << i); + return result; + } + + for (int i = 0; i < eventTypeText.length; ++i) + if (eventTypeText[i].equalsIgnoreCase(s)) + return 1 << i; + return 0; + } + + /** + * @returns timestamp at which event occurred, as a count of milliseconds + * since the epoch (standard Java format). + */ + public long getTimeStamp() + { + return timeStamp; + } + + /** + * @returns hashcode identifier of name of Dispatcher which first dispatched + * this event. (Needed by asynch dispatch code.) + */ + public int getDispatcher() + { + return dispatcher; + } + + /** + * @returns value of detail element of the event. + */ + public String getDetail() + { + return detail; + } + + /** + * @returns value of transactionID element of the event. + */ + public String getTransactionID() + { + return transactionID; + } + + /** + * Sets value of transactionID element of the event. + * + * @param tid + * new value of transactionID. + */ + public void setTransactionID(String tid) + { + transactionID = tid; + } + + public void setCurrentUser(int uid) + { + currentUser = uid; + } + + public int getCurrentUser() + { + return currentUser; + } + + public void setExtraLogInfo(String info) + { + extraLogInfo = info; + } + + public String getExtraLogInfo() + { + return extraLogInfo; + } + + /** + * @param filters + * list of filter masks; each one is an Array of two ints. + * @returns true if this event would be passed through the given filter + * list. + */ + public boolean pass(List filters) + { + boolean result = false; + + for (Iterator fi = filters.iterator(); fi.hasNext();) + { + int filter[] = (int[]) fi.next(); + if ((subjectType & filter[SUBJECT_MASK]) != 0 + && (eventType & filter[EVENT_MASK]) != 0) + result = true; + } + + if (log.isDebugEnabled()) + log.debug("Filtering event: " + "eventType=" + + String.valueOf(eventType) + ", subjectType=" + + String.valueOf(subjectType) + ", result=" + + String.valueOf(result)); + + return result; + } + + // dumb integer "log base 2", returns -1 if there are no 1's in number. + private static int log2(int n) + { + for (int i = 0; i < 32; ++i) + if (n == 1) + return i; + else + n = n >> 1; + return -1; + } + + /** + * Keeps track of which consumers the event has been consumed by. Should be + * called by a dispatcher when calling consume(Context ctx, String name, + * Event event) on an event. + * + * @param consumerName + */ + public void setBitSet(String consumerName) + { + consumedBy.set(EventManager.getConsumerIndex(consumerName)); + + } + + public BitSet getBitSet() + { + return consumedBy; + } + + /** + * @returns Detailed string representation of contents of this event, to + * help in logging and debugging. + */ + public String toString() + { + return "org.dspace.event.Event(eventType=" + + this.getEventTypeAsString() + + ", SubjectType=" + + this.getSubjectTypeAsString() + + ", SubjectID=" + + String.valueOf(subjectID) + + ", ObjectType=" + + this.getObjectTypeAsString() + + ", ObjectID=" + + String.valueOf(objectID) + + ", TimeStamp=" + + String.valueOf(timeStamp) + + ", dispatcher=" + + String.valueOf(dispatcher) + + ", detail=" + + (detail == null ? "[null]" : "\"" + detail + "\"") + + ", transactionID=" + + (transactionID == null ? "[null]" : "\"" + transactionID + + "\"") + ")"; + } +} diff --git a/dspace-api/src/main/java/org/dspace/event/EventManager.java b/dspace-api/src/main/java/org/dspace/event/EventManager.java new file mode 100644 index 0000000000..47d3676354 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/event/EventManager.java @@ -0,0 +1,389 @@ +/* + * EventManager.java + * + * Version: $Revision$ + * + * Date: $Date$ + * + * Copyright (c) 2002-2007, Hewlett-Packard Company and Massachusetts + * Institute of Technology. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of the Hewlett-Packard Company nor the name of the + * Massachusetts Institute of Technology nor the names of their + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ +package org.dspace.event; + +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; + +import org.apache.commons.pool.KeyedObjectPool; +import org.apache.commons.pool.KeyedPoolableObjectFactory; +import org.apache.commons.pool.PoolUtils; +import org.apache.commons.pool.impl.GenericKeyedObjectPool; +import org.apache.log4j.Logger; +import org.dspace.core.ConfigurationManager; +import org.dspace.core.Context; + +/** + * Class for managing the content event environment. The EventManager mainly + * acts as a factory for Dispatchers, which are used by the Context to send + * events to consumers. It also contains generally useful utility methods. + * + * Version: $Revision$ + */ +public class EventManager +{ + /** log4j category */ + private static Logger log = Logger.getLogger(EventManager.class); + + // The name of the default dispatcher assigned to every new context unless + // overridden + public static final String DEFAULT_DISPATCHER = "default"; + + private static DispatcherPoolFactory dispatcherFactory = null; + + private static GenericKeyedObjectPool.Config poolConfig = null; + + // Keyed FIFO Pool of event dispatchers + private static KeyedObjectPool dispatcherPool = null; + + private static HashMap consumerIndicies = null; + + private static final String CONSUMER_PFX = "event.consumer."; + + public EventManager() + { + initPool(); + log.info("Event Dispatcher Pool Initialized"); + } + + private static void initPool() + { + + if (dispatcherPool == null) + { + + // TODO EVENT Some of these pool configuration + // parameters can live in dspace.cfg or a + // separate configuration file + + // TODO EVENT Eviction parameters should be set + + poolConfig = new GenericKeyedObjectPool.Config(); + poolConfig.maxActive = 100; + poolConfig.maxIdle = 5; + poolConfig.maxTotal = 100; + + try + { + dispatcherFactory = new DispatcherPoolFactory(); + dispatcherPool = PoolUtils + .synchronizedPool(new GenericKeyedObjectPool( + dispatcherFactory, poolConfig)); + + enumerateConsumers(); + + } + catch (Exception e) + { + e.printStackTrace(); + } + + } + } + + /** + * Get dispatcher for configuration named by "name". Returns cached instance + * if one exists. + */ + public static Dispatcher getDispatcher(String name) + { + if (dispatcherPool == null) + { + initPool(); + } + + if (name == null) + name = DEFAULT_DISPATCHER; + + try + { + return (Dispatcher) dispatcherPool.borrowObject(name); + } + catch (Exception e) + { + throw new RuntimeException("Unable to aquire dispatcher named " + + name, e); + } + + } + + public static void returnDispatcher(String key, Dispatcher disp) + { + try + { + dispatcherPool.returnObject(key, disp); + } + catch (Exception e) + { + log.error(e.getMessage(), e); + } + } + + protected static int getConsumerIndex(String consumerClass) + { + Integer index = (Integer) consumerIndicies.get(consumerClass); + return index != null ? index.intValue() : -1; + + } + + private static void enumerateConsumers() + { + Enumeration propertyNames = ConfigurationManager.propertyNames(); + int bitSetIndex = 0; + + if (consumerIndicies == null) + { + consumerIndicies = new HashMap(); + } + + while (propertyNames.hasMoreElements()) + { + String ckey = ((String) propertyNames.nextElement()).trim(); + + if (ckey.startsWith(CONSUMER_PFX) && ckey.endsWith(".class")) + { + String consumerName = ckey.substring(CONSUMER_PFX.length(), + ckey.length() - 6); + + consumerIndicies.put(consumerName, (Integer) bitSetIndex); + bitSetIndex++; + } + } + } + + static class DispatcherPoolFactory implements KeyedPoolableObjectFactory + { + + // Prefix of keys in DSpace Configuration + private static final String PROP_PFX = "event.dispatcher."; + + // Cache of event dispatchers, keyed by name, for re-use. + private static Map dispatchers = new HashMap(); + + public DispatcherPoolFactory() + { + parseEventConfig(); + log.info(""); + } + + public Object makeObject(Object dispatcherName) throws Exception + { + + Dispatcher dispatcher = null; + String dispClass = dispatchers.get(dispatcherName); + + if (dispClass != null) + { + try + { + // all this to call a constructor with an argument + final Class argTypes[] = { String.class }; + Constructor dc = Class.forName(dispClass).getConstructor( + argTypes); + Object args[] = new Object[1]; + args[0] = dispatcherName; + dispatcher = (Dispatcher) dc.newInstance(args); + + // OK, now get its list of consumers/filters + String consumerKey = PROP_PFX + dispatcherName + + ".consumers"; + String consumerList = ConfigurationManager + .getProperty(consumerKey); + if (consumerList == null) + { + throw new RuntimeException( + "No Configuration entry found for consumer list of event Dispatcher: \"" + + consumerKey + "\""); + } + + // Consumer list format: + // :, ... + String[] consumerStanza = consumerList.trim().split( + "\\s*,\\s*"); + + // I think this should be a fatal error.. --lcs + if (consumerStanza.length < 1) + { + throw new RuntimeException( + "Cannot initialize Dispatcher, malformed Configuration value for " + + consumerKey); + } + + ConsumerProfile consumerProfile = null; + + // parts: 0 is name, part 1 is mode. + for (int i = 0; i < consumerStanza.length; i++) + { + consumerProfile = ConsumerProfile + .makeConsumerProfile(consumerStanza[i]); + consumerProfile.getConsumer().initialize(); + + dispatcher.addConsumerProfile(consumerProfile); + } + } + catch (NoSuchMethodException e) + { + throw new RuntimeException( + "Constructor not found for event dispatcher=" + + dispatcherName, e); + } + catch (InvocationTargetException e) + { + throw new RuntimeException( + "Error creating event dispatcher=" + dispatcherName, + e); + } + catch (ClassNotFoundException e) + { + throw new RuntimeException( + "Dispatcher/Consumer class not found for event dispatcher=" + + dispatcherName, e); + } + catch (InstantiationException e) + { + throw new RuntimeException( + "Dispatcher/Consumer instantiation failure for event dispatcher=" + + dispatcherName, e); + } + catch (IllegalAccessException e) + { + throw new RuntimeException( + "Dispatcher/Consumer access failure for event dispatcher=" + + dispatcherName, e); + } + } + else + { + throw new RuntimeException( + "Requested Dispatcher Does Not Exist In DSpace Configuration!"); + } + + return dispatcher; + + } + + public void activateObject(Object arg0, Object arg1) throws Exception + { + // No-op + return; + + } + + public void destroyObject(Object key, Object dispatcher) + throws Exception + { + Context ctx = new Context(); + + for (Iterator ci = ((Dispatcher) dispatcher).getConsumers() + .iterator(); ci.hasNext();) + { + ConsumerProfile cp = (ConsumerProfile) ci.next(); + if (cp != null) + cp.getConsumer().finish(ctx); + } + return; + + } + + public void passivateObject(Object arg0, Object arg1) throws Exception + { + // No-op + return; + + } + + public boolean validateObject(Object arg0, Object arg1) + { + // No-op + return false; + } + + /** + * Looks through the configuration for dispatcher configurations and + * loads one of each into a HashMap. This Map will be used to clone new + * objects when the pool needs them. + * + * Looks for configuration properties like: + * + *

+         *  # class of dispatcher "default"
+         *  event.dispatcher.default = org.dspace.event.BasicDispatcher
+         *  # list of consumers followed by filters for each, format is
+         *  #   <consumerClass>:<filter>[:<anotherFilter>..] , ...
+         *  #  and each filter is expressed as:
+         *  #    <objectType>[|<objectType> ...] + <eventType>[|<eventType> ..]
+         *  org.dspace.event.TestConsumer:all+all, \
+         *  org.dspace.eperson.SubscribeConsumer:Item+CREATE|DELETE:Collection+ADD, ...
+         * 
+ * + */ + private void parseEventConfig() + { + Enumeration propertyNames = ConfigurationManager.propertyNames(); + while (propertyNames.hasMoreElements()) + { + String ckey = ((String) propertyNames.nextElement()).trim(); + + if (ckey.startsWith(PROP_PFX) && ckey.endsWith(".class")) + { + String name = ckey.substring(PROP_PFX.length(), ckey + .length() - 6); + String dispatcherClass = ConfigurationManager + .getProperty(ckey); + + // Can we grab all of the consumers configured for this + // dispatcher + // and store them also? Then there is no + // ConfigurationManager call + // upon other makeObject(key) requests resulting in a faster + // pool + // get. + + dispatchers.put(name, dispatcherClass); + + } + } + } + } +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/event/TestConsumer.java b/dspace-api/src/main/java/org/dspace/event/TestConsumer.java new file mode 100644 index 0000000000..f7a729f6bf --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/event/TestConsumer.java @@ -0,0 +1,141 @@ +/* + * TestConsumer.java + * + * Version: $Revision$ + * + * Date: $Date$ + * + * Copyright (c) 2002-2007, Hewlett-Packard Company and Massachusetts + * Institute of Technology. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of the Hewlett-Packard Company nor the name of the + * Massachusetts Institute of Technology nor the names of their + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +package org.dspace.event; + +import java.io.PrintStream; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; + +import org.apache.log4j.Logger; +import org.dspace.core.ConfigurationManager; +import org.dspace.core.Context; +import org.dspace.eperson.EPerson; + +/** + * Demonstration and test consumer for the event system. This consumer only + * makes an entry in the log, and on an output stream, for each event it + * receives. It also logs when consume() and end() get called. It is intended + * for testing, exploring, and debugging the event system. + * + * @version $Revision$ + */ +public class TestConsumer implements Consumer +{ + // Log4j logger + private static Logger log = Logger.getLogger(TestConsumer.class); + + // Send diagnostic output here - set to null to turn it off. + private static PrintStream out = ConfigurationManager + .getBooleanProperty("testConsumer.verbose") ? System.out : null; + + static final DateFormat df = new SimpleDateFormat( + "dd-MMM-yyyy HH:mm:ss.SSS Z"); + + public void initialize() throws Exception + { + log.info("EVENT: called TestConsumer.initialize();"); + if (out != null) + out.println("TestConsumer.initialize();"); + } + + /** + * Consume a content event - display it in detail. + * + * @param ctx + * DSpace context + * @param event + * Content event + */ + public void consume(Context ctx, Event event) throws Exception + { + EPerson ep = ctx.getCurrentUser(); + String user = (ep == null) ? "(none)" : ep.getEmail(); + String detail = event.getDetail(); + + String msg = "EVENT: called TestConsumer.consume(): EventType=" + + event.getEventTypeAsString() + + ", SubjectType=" + + event.getSubjectTypeAsString() + + ", SubjectID=" + + String.valueOf(event.getSubjectID()) + + ", ObjectType=" + + event.getObjectTypeAsString() + + ", ObjectID=" + + String.valueOf(event.getObjectID()) + + ", TimeStamp=" + + df.format(new Date(event.getTimeStamp())) + + ", user=\"" + + user + + "\"" + + ", extraLog=\"" + + ctx.getExtraLogInfo() + + "\"" + + ", dispatcher=" + + String.valueOf(event.getDispatcher()) + + ", detail=" + + (detail == null ? "[null]" : "\"" + detail + "\"") + + ", transactionID=" + + (event.getTransactionID() == null ? "[null]" : "\"" + + event.getTransactionID() + "\"") + ", context=" + + ctx.toString(); + log.info(msg); + if (out != null) + out.println("TestConsumer.consume(): " + msg); + } + + public void end(Context ctx) throws Exception + { + log.info("EVENT: called TestConsumer.end();"); + if (out != null) + out.println("TestConsumer.end();"); + + } + + public void finish(Context ctx) throws Exception + { + log.info("EVENT: called TestConsumer.finish();"); + if (out != null) + out.println("TestConsumer.finish();"); + + } + +} diff --git a/dspace-api/src/main/java/org/dspace/search/SearchConsumer.java b/dspace-api/src/main/java/org/dspace/search/SearchConsumer.java new file mode 100644 index 0000000000..46fd551a0d --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/search/SearchConsumer.java @@ -0,0 +1,282 @@ +/* + * SearchConsumer.java + * + * Location: $URL$ + * + * Version: $Revision$ + * + * Date: $Date$ + * + * Copyright (c) 2002-2007, Hewlett-Packard Company and Massachusetts + * Institute of Technology. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * - Neither the name of the Hewlett-Packard Company nor the name of the + * Massachusetts Institute of Technology nor the names of their + * contributors may be used to endorse or promote products derived from + * this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR + * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE + * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + */ + +package org.dspace.search; + +import java.sql.SQLException; +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import org.apache.log4j.Logger; +import org.dspace.content.Bundle; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.event.Consumer; +import org.dspace.event.Event; + +/** + * Class for updating search indices from content events. + * + * @version $Revision$ + */ +public class SearchConsumer implements Consumer +{ + /** log4j logger */ + private static Logger log = Logger.getLogger(SearchConsumer.class); + + // collect Items, Collections, Communities newly created. + private Set objectsCreated = null; + + // collect Items, Collections, Communities that need reindexing + private Set objectsToUpdate = null; + + // handles to delete since IDs are not useful by now. + private Set handlesToDelete = null; + + public void initialize() throws Exception + { + // No-op + + } + + /** + * Consume a content event -- just build the sets of objects to add (new) to + * the index, update, and delete. + * + * @param ctx + * DSpace context + * @param event + * Content event + */ + public void consume(Context ctx, Event event) throws Exception + { + + if (objectsCreated == null) + { + objectsCreated = new HashSet(); + objectsToUpdate = new HashSet(); + handlesToDelete = new HashSet(); + } + + int st = event.getSubjectType(); + if (!(st == Constants.ITEM || st == Constants.BUNDLE + || st == Constants.COLLECTION || st == Constants.COMMUNITY)) + { + log + .warn("SearchConsumer should not have been given this kind of Subject in an event, skipping: " + + event.toString()); + return; + } + DSpaceObject dso = null; + // EVENT FIXME This call to getSubjectOfEvent is catching a SQLException + // but other Consumers don't + try + { + dso = event.getSubject(ctx); + } + catch (SQLException se) + { + } + + // If event subject is a Bundle and event was Add or Remove, + // transform the event to be a Modify on the owning Item. + // It could be a new bitstream in the TEXT bundle which + // would change the index. + int et = event.getEventType(); + if (st == Constants.BUNDLE) + { + if ((et == Event.ADD || et == Event.REMOVE) && dso != null + && ((Bundle) dso).getName().equals("TEXT")) + { + st = Constants.ITEM; + et = Event.MODIFY; + dso = ((Bundle) dso).getItems()[0]; + if (log.isDebugEnabled()) + log.debug("Transforming Bundle event into MODIFY of Item " + + dso.getHandle()); + } + else + return; + } + + switch (et) + { + case Event.CREATE: + if (dso == null) + log.warn("CREATE event, could not get object for " + + event.getSubjectTypeAsString() + " id=" + + String.valueOf(event.getSubjectID()) + + ", perhaps it has been deleted."); + else + objectsCreated.add(dso); + break; + case Event.MODIFY: + case Event.MODIFY_METADATA: + if (dso == null) + log.warn("MODIFY event, could not get object for " + + event.getSubjectTypeAsString() + " id=" + + String.valueOf(event.getSubjectID()) + + ", perhaps it has been deleted."); + else + objectsToUpdate.add(dso); + break; + case Event.DELETE: + String detail = event.getDetail(); + if (detail == null) + log.warn("got null detail on DELETE event, skipping it."); + else + handlesToDelete.add(detail); + break; + default: + log + .warn("SearchConsumer should not have been given a event of type=" + + event.getEventTypeAsString() + + " on subject=" + + event.getSubjectTypeAsString()); + break; + } + } + + /** + * Process sets of objects to add, update, and delete in index. Correct for + * interactions between the sets -- e.g. objects which were deleted do not + * need to be added or updated, new objects don't also need an update, etc. + */ + public void end(Context ctx) throws Exception + { + // add new created items to index, unless they were deleted. + for (Iterator ii = objectsCreated.iterator(); ii.hasNext();) + { + DSpaceObject ic = (DSpaceObject) ii.next(); + if (ic.getType() != Constants.ITEM || ((Item) ic).isArchived()) + { + // if handle is NOT in list of deleted objects, index it: + String hdl = ic.getHandle(); + if (hdl != null && !handlesToDelete.contains(hdl)) + { + try + { + DSIndexer.indexContent(ctx, ic); + if (log.isDebugEnabled()) + log.debug("Indexed NEW " + + Constants.typeText[ic.getType()] + + ", id=" + String.valueOf(ic.getID()) + + ", handle=" + hdl); + } + catch (Exception e) + { + log.error("Failed while indexing new object: ", e); + objectsCreated = null; + objectsToUpdate = null; + handlesToDelete = null; + } + } + } + // remove it from modified list since we just indexed it. + objectsToUpdate.remove(ic); + } + + // update the changed Items not deleted because they were on create list + for (Iterator ii = objectsToUpdate.iterator(); ii.hasNext();) + { + DSpaceObject iu = (DSpaceObject) ii.next(); + if (iu.getType() != Constants.ITEM || ((Item) iu).isArchived()) + { + // if handle is NOT in list of deleted objects, index it: + String hdl = iu.getHandle(); + if (hdl != null && !handlesToDelete.contains(hdl)) + { + try + { + DSIndexer.reIndexContent(ctx, iu); + if (log.isDebugEnabled()) + log.debug("RE-Indexed " + + Constants.typeText[iu.getType()] + + ", id=" + String.valueOf(iu.getID()) + + ", handle=" + hdl); + } + catch (Exception e) + { + log.error("Failed while RE-indexing object: ", e); + objectsCreated = null; + objectsToUpdate = null; + handlesToDelete = null; + } + } + } + } + + for (Iterator ii = handlesToDelete.iterator(); ii.hasNext();) + { + String hdl = (String) ii.next(); + try + { + DSIndexer.unIndexContent(ctx, hdl); + if (log.isDebugEnabled()) + log.debug("UN-Indexed Item, handle=" + hdl); + } + catch (Exception e) + { + log.error("Failed while UN-indexing object: " + hdl, e); + objectsCreated = new HashSet(); + objectsToUpdate = new HashSet(); + handlesToDelete = new HashSet(); + } + + } + + // "free" the resources + objectsCreated = null; + objectsToUpdate = null; + handlesToDelete = null; + } + + public void finish(Context ctx) throws Exception + { + // No-op + + } + +} diff --git a/dspace-api/src/main/java/org/dspace/workflow/WorkflowItem.java b/dspace-api/src/main/java/org/dspace/workflow/WorkflowItem.java index bc4a8eca44..049ba576ae 100644 --- a/dspace-api/src/main/java/org/dspace/workflow/WorkflowItem.java +++ b/dspace-api/src/main/java/org/dspace/workflow/WorkflowItem.java @@ -52,7 +52,6 @@ import org.dspace.content.Item; import org.dspace.core.Context; import org.dspace.core.LogManager; import org.dspace.eperson.EPerson; -import org.dspace.history.HistoryManager; import org.dspace.storage.rdbms.DatabaseManager; import org.dspace.storage.rdbms.TableRow; import org.dspace.storage.rdbms.TableRowIterator; @@ -356,9 +355,6 @@ public class WorkflowItem implements InProgressSubmission // Update ourselves DatabaseManager.update(ourContext, wfRow); - - HistoryManager.saveHistory(ourContext, this, HistoryManager.MODIFY, - ourContext.getCurrentUser(), ourContext.getExtraLogInfo()); } /** @@ -370,9 +366,6 @@ public class WorkflowItem implements InProgressSubmission // Remove from cache ourContext.removeCached(this, getID()); - HistoryManager.saveHistory(ourContext, this, HistoryManager.REMOVE, - ourContext.getCurrentUser(), ourContext.getExtraLogInfo()); - // delete any pending tasks WorkflowManager.deleteTasks(ourContext, this); diff --git a/dspace-api/src/main/java/org/dspace/workflow/WorkflowManager.java b/dspace-api/src/main/java/org/dspace/workflow/WorkflowManager.java index c5c3e62bb8..ff6a0b1b7e 100644 --- a/dspace-api/src/main/java/org/dspace/workflow/WorkflowManager.java +++ b/dspace-api/src/main/java/org/dspace/workflow/WorkflowManager.java @@ -68,7 +68,6 @@ import org.dspace.core.LogManager; import org.dspace.eperson.EPerson; import org.dspace.eperson.Group; import org.dspace.handle.HandleManager; -import org.dspace.history.HistoryManager; import org.dspace.storage.rdbms.DatabaseManager; import org.dspace.storage.rdbms.TableRow; import org.dspace.storage.rdbms.TableRowIterator; @@ -195,10 +194,6 @@ public class WorkflowManager wfi.setMultipleTitles(wsi.hasMultipleTitles()); wfi.setPublishedBefore(wsi.isPublishedBefore()); - // Write history creation event - HistoryManager.saveHistory(c, wfi, HistoryManager.CREATE, c - .getCurrentUser(), c.getExtraLogInfo()); - // remove the WorkspaceItem wsi.deleteWrapper();