Index: src/org/dspace/browse/Browse.java =================================================================== --- src/org/dspace/browse/Browse.java (revision 4884) +++ src/org/dspace/browse/Browse.java (revision 5020) @@ -59,11 +59,15 @@ import org.dspace.content.Collection; import org.dspace.content.Community; import org.dspace.content.DCValue; +import org.dspace.content.DSpaceObject; import org.dspace.content.Item; import org.dspace.content.ItemComparator; import org.dspace.content.ItemIterator; import org.dspace.core.ConfigurationManager; import org.dspace.core.Context; +import org.dspace.core.PluginManager; +import org.dspace.mask.MaskType; +import org.dspace.mask.MaskService; import org.dspace.storage.rdbms.DatabaseManager; import org.dspace.storage.rdbms.TableRow; @@ -485,14 +489,24 @@ public static void itemAdded(Context context, Item item) throws SQLException { + log.info( "Adding item " + item ); + + if ( isMasked( item ) ) + { + log.info( "Item " + item.getHandle() + " is masked from browse." ); + return; + } + // add all parent communities to communities2item table - Community[] parents = item.getCommunities(); + Community[] parents = item.getCommunities(); for (int j = 0; j < parents.length; j++) { + Community community = parents[j]; + TableRow row = DatabaseManager.create(context, "Communities2Item"); row.setColumn("item_id", item.getID()); - row.setColumn("community_id", parents[j].getID()); + row.setColumn("community_id", community.getID()); DatabaseManager.update(context, row); } @@ -586,6 +600,23 @@ } /** + * Check to see if the Item is masked from being browsable. + *
+ * If the item itelf or its owning collection are masked, return true. + * If all communities the item belongs to are masked, return true. + * Otherwise return false. + * + * @param item the dspace item + * @return true if the object should be masked from browse + */ + private static boolean isMasked( Item item ) throws SQLException + { + MaskService maskSvc = (MaskService)PluginManager.getSinglePlugin( MaskService.class ); + String itemHandle = item.getHandle(); + return maskSvc.isMasked( itemHandle, MaskType.BROWSE ); + } + + /** * Index all items in DSpace. This method may be resource-intensive. * * @param context @@ -595,12 +626,13 @@ * If a database error occurs */ public static int indexAll(Context context) throws SQLException - { + { indexRemoveAll(context); int count = 0; ItemIterator iterator = Item.findAll(context); + while (iterator.hasNext()) { itemAdded(context, iterator.next()); Index: src/org/dspace/browse/InitializeBrowse.java =================================================================== --- src/org/dspace/browse/InitializeBrowse.java (revision 4884) +++ src/org/dspace/browse/InitializeBrowse.java (revision 5020) @@ -41,6 +41,8 @@ import java.sql.SQLException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.dspace.core.Context; /** @@ -51,6 +53,8 @@ */ public class InitializeBrowse { + private static Log log = LogFactory.getLog( InitializeBrowse.class ); + /** Private Constructor */ private InitializeBrowse() { @@ -68,13 +72,13 @@ try { - System.out.print("Indexing all Items in DSpace...."); + log.info("Indexing all Items in DSpace...."); context = new Context(); Browse.indexAll(context); context.complete(); - System.out.println(" ... Done"); + log.info(" ... Done"); } catch (SQLException sqle) { @@ -83,7 +87,7 @@ context.abort(); } - System.err.println("Error: Browse index NOT created"); + log.error("Error: Browse index NOT created", sqle); sqle.printStackTrace(); } } Index: src/org/dspace/search/DSIndexer.java =================================================================== --- src/org/dspace/search/DSIndexer.java (revision 4884) +++ src/org/dspace/search/DSIndexer.java (revision 5020) @@ -74,7 +74,10 @@ import org.dspace.core.Context; import org.dspace.core.Email; import org.dspace.core.LogManager; +import org.dspace.core.PluginManager; import org.dspace.handle.HandleManager; +import org.dspace.mask.MaskType; +import org.dspace.mask.MaskService; import edu.jhmi.welch.dspace.DspaceSelector; @@ -556,6 +559,14 @@ for(ItemIterator i = Item.findAll(context);i.hasNext();) { Item item = (Item) i.next(); + if ( isMasked( item ) ) + { + if ( log.isInfoEnabled() ) + { + log.info( "Item [" + item.getHandle() + "] is masked from search indexing." ); + } + continue; + } indexContent(context,item,force); item.decache(); } @@ -563,6 +574,14 @@ Collection[] collections = Collection.findAll(context); for (int i = 0; i < collections.length; i++) { + if ( isMasked( collections[i] ) ) + { + if ( log.isInfoEnabled() ) + { + log.info( "Collection [" + collections[i].getHandle() + "] is masked from search indexing." ); + } + continue; + } indexContent(context,collections[i],force); context.removeCached(collections[i], collections[i].getID()); @@ -571,6 +590,14 @@ Community[] communities = Community.findAll(context); for (int i = 0; i < communities.length; i++) { + if ( isMasked( communities[i] ) ) + { + if ( log.isInfoEnabled() ) + { + log.info( "Community [" + communities[i].getHandle() + "] is masked from search indexing." ); + } + continue; + } indexContent(context,communities[i],force); context.removeCached(communities[i], communities[i].getID()); } @@ -1122,5 +1149,12 @@ return doc; } + + private static boolean isMasked( DSpaceObject dso ) throws SQLException + { + MaskService maskSvc = (MaskService)PluginManager.getSinglePlugin( MaskService.class ); + String handle = dso.getHandle(); + return maskSvc.isMasked( handle, MaskType.INDEX ); + } } Index: src/org/dspace/mask/PropertyMaskService.java =================================================================== --- src/org/dspace/mask/PropertyMaskService.java (revision 0) +++ src/org/dspace/mask/PropertyMaskService.java (revision 5020) @@ -0,0 +1,316 @@ +package org.dspace.mask; + +import java.sql.SQLException; +import java.util.BitSet; +import java.util.StringTokenizer; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.content.DSpaceObject; +import org.dspace.content.Item; +import org.dspace.core.ConfigurationManager; +import org.dspace.core.Context; +import org.dspace.handle.HandleManager; + +/** + * A {@link MaskService} which is initialized by + * properties from the {@link ConfigurationManager}. + */ +public class PropertyMaskService implements MaskService +{ + private static final String TOKEN = ","; + private Log log = LogFactory.getLog( PropertyMaskService.class ); + private final InMemoryMaskManager mgr; + + public PropertyMaskService() + { + this( new InMemoryMaskManager() ); + } + + /** + * TODO: Instead of InMemoryMaskManager, should be MaskManager. Or update MaskManager contract. + * @param mgr + */ + public PropertyMaskService( InMemoryMaskManager mgr ) + { + if ( mgr == null ) + { + throw new IllegalArgumentException( "Mask Manager cannot be null" ); + } + this.mgr = mgr; + + log.info( "Configuring the Property based Mask Service..." ); + + // Get properties + String browse = ConfigurationManager.getProperty( "mask.browse" ); + if ( browse != null && !browse.trim().equals( "" ) ) + { + addMask( browse.trim(), MaskType.BROWSE ); + } + + String index = ConfigurationManager.getProperty( "mask.index" ); + if ( index != null && !browse.trim().equals( "" ) ) + { + addMask( index.trim(), MaskType.INDEX ); + } + + String harvest = ConfigurationManager.getProperty( "mask.harvest" ); + if ( harvest != null && !harvest.trim().equals( "" ) ) + { + addMask( harvest.trim(), MaskType.HARVEST ); + } + + log.info( "Property based Mask Service configuration complete." ); + } + + /** + * TODO: Extract MaskType handlers. Handlers should be independent of the mask service itself. + * TODO: Negative cache. If an item isn't masked, that should be cached for a certain period. + */ + public boolean isMasked( String handle, MaskType type ) + { + // The handle may be null for Item objects which have + // just been submitted. + if ( handle == null || handle.trim().equals( "" ) ) + { + // assume false + return false; + } + + // Check the MaskManager: it may already know that + // the handle is masked. + if ( isMaskedInternal( handle, type ) ) + { + return true; + } + + // If the MaskManager didn't know the handle was + // masked, it could be that the handle is an + // Item which is a member of a masked collection + // or community. + + // assume that the item is masked. + boolean isMasked = true; + // temp context for HandleManager + Context tempContext = null; + try + { + tempContext = new Context(); + DSpaceObject dso = HandleManager.resolveToObject( tempContext, handle ); + + // if the handle doesn't resolve to an object, return false + if ( dso == null ) + { + return false; + } + + + switch ( type ) + { + case BROWSE: + { + isMasked = handleBrowse( dso ); + break; + } + + case INDEX: + { + isMasked = handleIndex( dso ); + break; + } + + case HARVEST: + { + isMasked = handleHarvest( dso ); + break; + } + default: + { + log.warn( "Unknown mask type: [" + type + "]. Assuming handle is not masked." ); + isMasked = false; + break; + } + } + + // Don't forget to clean up + tempContext.complete(); + } + catch ( SQLException e ) + { + isMasked = false; + log.error( "Unable to determine if handle [" + handle + "] is masked from [" + type + "]: " + + e.getMessage(), e ); + } + finally + { + if ( tempContext != null && tempContext.isValid() ) + { + tempContext.abort(); + } + } + + return isMasked; + } + + private boolean handleBrowse( DSpaceObject dso ) throws SQLException + { + Item i = null; + if ( dso instanceof Item ) + { + i = (Item)dso; + } + else + { + if ( log.isDebugEnabled() ) + { + log.debug( "Can only check Item masks. Assuming this DSO is not masked. Received a DSO that was not an item: " + + "Type: [" + dso.getType() + "] Handle: [" + dso.getHandle() + "] ID: [" + dso.getID() + "]" ); + } + return false; + } + + return isItemMasked( i, MaskType.BROWSE ); + } + + private boolean handleIndex( DSpaceObject dso ) throws SQLException + { + Item i = null; + if ( dso instanceof Item ) + { + i = (Item)dso; + } + else + { + if ( log.isDebugEnabled() ) + { + log.debug( "Can only check Item masks. Assuming this DSO is not masked. Received a DSO that was not an item: " + + "Type: [" + dso.getType() + "] Handle: [" + dso.getHandle() + "] ID: [" + dso.getID() + "]" ); + } + return false; + } + + return isItemMasked( i, MaskType.BROWSE ); + } + + private boolean handleHarvest( DSpaceObject dso ) throws SQLException + { + Item i = null; + if ( dso instanceof Item ) + { + i = (Item)dso; + } + else + { + if ( log.isDebugEnabled() ) + { + log.debug( "Can only check Item masks. Assuming this DSO is not masked. Received a DSO that was not an item: " + + "Type: [" + dso.getType() + "] Handle: [" + dso.getHandle() + "] ID: [" + dso.getID() + "]" ); + } + return false; + } + + return isItemMasked( i, MaskType.BROWSE ); + } + + /** + * Add the supplied handles and mask type to the + * MaskManager. + * + *handles may be a {@link #TOKEN} separated
+ * String of handles or just a single handle.
+ *
+ *
+ * @param handles a single handle or a {@link #TOKEN} separated
+ * list of handles.
+ * @param type the mask type for the handles
+ */
+ private void addMask( String handles, MaskType type )
+ {
+ StringTokenizer st = new StringTokenizer( handles, TOKEN );
+ while ( st.hasMoreTokens() )
+ {
+ String handle = st.nextToken().trim();
+ log.info( "Masking handle [" + handle + "] from [" + type + "]" );
+ mgr.addMask( handle, type );
+ }
+ }
+
+ /**
+ * Checks with the MaskManager for a mask type for
+ * handle.
+ *
+ * If the MaskManager knows that handle is masked,
+ * it will return a BitSet of mask flags. This method
+ * checks to see if the flag for type is set.
+ *
+ * @param handle the DSpace object handle
+ * @param type the type of masking
+ * @return true if handle is masked for type
+ */
+ private boolean isMaskedInternal( String handle, MaskType type )
+ {
+ BitSet mask = mgr.getMask( handle );
+
+ if ( mgr.getBitIndex( type ) != -1 && mask != null )
+ {
+ if ( mask.get( mgr.getBitIndex( type ) ) )
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * Check to see if a DSpace Item is masked.
+ *
+ * An Item is masked if one of the following
+ * conditions is true:
+ * + *
MaskManager asserts the Item is maskedMap
+ * of DSpace handles to masks. The map is not persisted in any way,
+ * so it must be seeded by calling {@link #addMask(String, MaskType)}
+ * each time this object is instantiated. This implementation is
+ * designed to be threadsafe. It can be shared by multiple {@link MaskService}
+ * instances.
+ *
+ * @see MaskService
+ */
+class InMemoryMaskManager implements MaskManager
+{
+ /**
+ * Logger
+ */
+ private Log log = LogFactory.getLog( InMemoryMaskManager.class );
+
+ /**
+ * Maps DSpace object handles to thier mask information.
+ * This Map is meant to serve as a cache.
+ *
+ * This map is shared amongst all instances of this class.
+ * The map is mutable. Access to the map is threadsafe, note
+ * however that update operations like put and
+ * set may interleave retrieval operations like
+ * get per the {@link ConcurrentHashMap} contract.
+ * Interleaving retrievals and updates is a reasonable sacrifice
+ * for reducing lock contention and synchronization overhead.
+ */
+ private static ConcurrentMaptype.
+ *
+ * @param type the {@link MaskType}
+ * @return the bit index of the type, or -1 if no mapping exists.
+ */
+ int getBitIndex( MaskType type )
+ {
+ if (! typeToBit.containsKey( type ) )
+ {
+ return -1;
+ }
+
+ return typeToBit.get( type );
+ }
+
+ private synchronized void putNewMask( String handle, BitSet maskBits )
+ {
+ log.debug( "Adding new mask for " + handle );
+ DspaceObjectMask dsMask = new DspaceObjectMask( handle, maskBits );
+ masks.put( handle, dsMask );
+ }
+
+ /**
+ * Encapsulates all the masks of a particular handle. References to member
+ * objects should never be handed out.
+ */
+ private class DspaceObjectMask
+ {
+ final String handle;
+ final BitSet mask;
+
+ private DspaceObjectMask( String handle )
+ {
+ if ( handle == null || handle.trim().equals( "" ) )
+ {
+ throw new IllegalArgumentException( "Handle must not be empty or null." );
+ }
+ this.handle = handle;
+ this.mask = new BitSet( MaskType.values().length );
+ }
+
+ private DspaceObjectMask( String handle, BitSet mask )
+ {
+ if ( handle == null || handle.trim().equals( "" ) )
+ {
+ throw new IllegalArgumentException( "Handle must not be empty or null." );
+ }
+
+ if ( mask == null )
+ {
+ throw new IllegalArgumentException( "Mask must not be null." );
+ }
+ this.handle = handle;
+ this.mask = mask;
+ }
+
+ public int hashCode()
+ {
+ final int PRIME = 31;
+ int result = 1;
+ result = PRIME * result + ( ( handle == null ) ? 0 : handle.hashCode() );
+ result = PRIME * result + ( ( mask == null ) ? 0 : mask.hashCode() );
+ return result;
+ }
+
+ public boolean equals( Object obj )
+ {
+ if ( this == obj )
+ return true;
+ if ( obj == null )
+ return false;
+ if ( getClass() != obj.getClass() )
+ return false;
+ final DspaceObjectMask other = (DspaceObjectMask) obj;
+ if ( handle == null )
+ {
+ if ( other.handle != null )
+ return false;
+ }
+ else if ( !handle.equals( other.handle ) )
+ return false;
+ if ( mask == null )
+ {
+ if ( other.mask != null )
+ return false;
+ }
+ else if ( !mask.equals( other.mask ) )
+ return false;
+ return true;
+ }
+
+ public String toString()
+ {
+ return "Mask handle: [" + handle + "] mask: [" + mask.toString() + "]";
+ }
+
+ }
+
+}
Property changes on: src/org/dspace/mask/InMemoryMaskManager.java
___________________________________________________________________
Name: svn:mime-type
+ text/plain
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Index: src/org/dspace/mask/MaskManager.java
===================================================================
--- src/org/dspace/mask/MaskManager.java (revision 0)
+++ src/org/dspace/mask/MaskManager.java (revision 5020)
@@ -0,0 +1,38 @@
+package org.dspace.mask;
+
+/**
+ * Responsible for managing the masks which
+ * are placed on DSpace objects.
+ *
+ * This interface should not be exposed.
+ */
+interface MaskManager
+{
+ /**
+ * Mask the handle entirely: all
+ * masks are enabled.
+ */
+ void mask( String handle );
+
+ /**
+ * Unmask the handle entirely: all
+ * masks are removed.
+ */
+ void unmask( String handle );
+
+ /**
+ * Add a mask to the handle.
+ *
+ * @param handle the handle
+ * @param type the mask type
+ */
+ void addMask( String handle, MaskType type );
+
+ /**
+ * Remove a mask from a handle
+ *
+ * @param handle the handle
+ * @param type the mask type
+ */
+ void removeMask( String handle, MaskType type );
+}
Property changes on: src/org/dspace/mask/MaskManager.java
___________________________________________________________________
Name: svn:mime-type
+ text/plain
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Index: src/org/dspace/mask/MaskService.java
===================================================================
--- src/org/dspace/mask/MaskService.java (revision 0)
+++ src/org/dspace/mask/MaskService.java (revision 5020)
@@ -0,0 +1,19 @@
+package org.dspace.mask;
+
+/**
+ * The MaskService is responsible for determining if
+ * the object represented by handle should be masked
+ * for the operation represented by the {@link MaskType} type.
+ */
+public interface MaskService
+{
+ /**
+ * Determine if handle should be masked for
+ * type.
+ *
+ * @param handle the handle identifying the object
+ * @param type the type of mask
+ * @return true if the object should be masked
+ */
+ public boolean isMasked( String handle, MaskType type );
+}
Property changes on: src/org/dspace/mask/MaskService.java
___________________________________________________________________
Name: svn:mime-type
+ text/plain
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native
Index: src/org/dspace/mask/MaskType.java
===================================================================
--- src/org/dspace/mask/MaskType.java (revision 0)
+++ src/org/dspace/mask/MaskType.java (revision 5020)
@@ -0,0 +1,8 @@
+package org.dspace.mask;
+
+public enum MaskType
+{
+ BROWSE,
+ HARVEST,
+ INDEX
+}
Property changes on: src/org/dspace/mask/MaskType.java
___________________________________________________________________
Name: svn:mime-type
+ text/plain
Name: svn:keywords
+ Id
Name: svn:eol-style
+ native