mirror of
https://github.com/DSpace/DSpace.git
synced 2025-10-15 14:03:17 +00:00

Contribution from CILEA funded by the Hub project from HKU (http://hub.hku.hk) Faceting, filtering (autocomplete), sidebar facet for the site home page, community and collections are all implemented. Changes to the Discovery API/configuration: 1) changed the unique field for the SOLR document, now is used the concatenation of ID and TYPE-ID (in future we want to index also object that have not an handle) 2) the prune query has been changed in search.resourcetype:[2 TO 4] so to not remove eventually extra data loaded in the SOLR search core 3) added defaultRpp parameter Main differences from the XMLUI implementation: 1) facets doesn't have a "...More" link but there are pagination to scroll facet in the context (search, home page, community, etc.) 2) facets doesn't show the values already selected 3) autocomplete is done against user input and does not dump all the values (this was a performance issue in XMLUI < 3.0, with 90k items I see JSON around 2Mb). With the new Discovery improvements the autocomplete feature in XMLUI seems to be turned off 4) to enable JSPUI discovery you need to add some extra plugins in dspace.cfg (instructions are provided as comment in the discovery.cfg)
2823 lines
99 KiB
Java
2823 lines
99 KiB
Java
/**
|
|
* The contents of this file are subject to the license and copyright
|
|
* detailed in the LICENSE and NOTICE files at the root of the source
|
|
* tree and available online at
|
|
*
|
|
* http://www.dspace.org/license/
|
|
*/
|
|
package org.dspace.content;
|
|
|
|
import java.io.IOException;
|
|
import java.io.InputStream;
|
|
import java.sql.SQLException;
|
|
import java.sql.Timestamp;
|
|
import java.util.ArrayList;
|
|
import java.util.Arrays;
|
|
import java.util.Date;
|
|
import java.util.HashMap;
|
|
import java.util.List;
|
|
import java.util.Map;
|
|
import java.util.StringTokenizer;
|
|
|
|
import org.apache.commons.lang.StringUtils;
|
|
import org.apache.log4j.Logger;
|
|
import org.dspace.app.util.AuthorizeUtil;
|
|
import org.dspace.authorize.AuthorizeConfiguration;
|
|
import org.dspace.authorize.AuthorizeException;
|
|
import org.dspace.authorize.AuthorizeManager;
|
|
import org.dspace.authorize.ResourcePolicy;
|
|
import org.dspace.browse.BrowseException;
|
|
import org.dspace.browse.IndexBrowse;
|
|
import org.dspace.core.Constants;
|
|
import org.dspace.core.Context;
|
|
import org.dspace.core.LogManager;
|
|
import org.dspace.content.authority.Choices;
|
|
import org.dspace.content.authority.ChoiceAuthorityManager;
|
|
import org.dspace.content.authority.MetadataAuthorityManager;
|
|
import org.dspace.event.Event;
|
|
import org.dspace.eperson.EPerson;
|
|
import org.dspace.eperson.Group;
|
|
import org.dspace.handle.HandleManager;
|
|
import org.dspace.storage.rdbms.DatabaseManager;
|
|
import org.dspace.storage.rdbms.TableRow;
|
|
import org.dspace.storage.rdbms.TableRowIterator;
|
|
|
|
/**
|
|
* Class representing an item in DSpace.
|
|
* <P>
|
|
* This class holds in memory the item Dublin Core metadata, the bundles in the
|
|
* item, and the bitstreams in those bundles. When modifying the item, if you
|
|
* modify the Dublin Core or the "in archive" flag, you must call
|
|
* <code>update</code> for the changes to be written to the database.
|
|
* Creating, adding or removing bundles or bitstreams has immediate effect in
|
|
* the database.
|
|
*
|
|
* @author Robert Tansley
|
|
* @author Martin Hald
|
|
* @version $Revision$
|
|
*/
|
|
public class Item extends DSpaceObject
|
|
{
|
|
/**
|
|
* Wild card for Dublin Core metadata qualifiers/languages
|
|
*/
|
|
public static final String ANY = "*";
|
|
|
|
/** log4j category */
|
|
private static final Logger log = Logger.getLogger(Item.class);
|
|
|
|
/** Our context */
|
|
private Context ourContext;
|
|
|
|
/** The table row corresponding to this item */
|
|
private TableRow itemRow;
|
|
|
|
/** The e-person who submitted this item */
|
|
private EPerson submitter;
|
|
|
|
/** The bundles in this item - kept in sync with DB */
|
|
private List<Bundle> bundles;
|
|
|
|
/** The Dublin Core metadata - inner class for lazy loading */
|
|
MetadataCache dublinCore = new MetadataCache();
|
|
|
|
/** Handle, if any */
|
|
private String handle;
|
|
|
|
/**
|
|
* True if the Dublin Core has changed since reading from the DB or the last
|
|
* update()
|
|
*/
|
|
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
|
|
*
|
|
* @param context
|
|
* the context this object exists in
|
|
* @param row
|
|
* the corresponding row in the table
|
|
* @throws SQLException
|
|
*/
|
|
Item(Context context, TableRow row) throws SQLException
|
|
{
|
|
ourContext = context;
|
|
itemRow = row;
|
|
dublinCoreChanged = false;
|
|
modified = false;
|
|
clearDetails();
|
|
|
|
// Get our Handle if any
|
|
handle = HandleManager.findHandle(context, this);
|
|
|
|
// Cache ourselves
|
|
context.cache(this, row.getIntColumn("item_id"));
|
|
}
|
|
|
|
private TableRowIterator retrieveMetadata() throws SQLException
|
|
{
|
|
return DatabaseManager.queryTable(ourContext, "MetadataValue",
|
|
"SELECT * FROM MetadataValue WHERE item_id= ? ORDER BY metadata_field_id, place",
|
|
itemRow.getIntColumn("item_id"));
|
|
}
|
|
|
|
/**
|
|
* Get an item from the database. The item, its Dublin Core metadata, and
|
|
* the bundle and bitstream metadata are all loaded into memory.
|
|
*
|
|
* @param context
|
|
* DSpace context object
|
|
* @param id
|
|
* Internal ID of the item
|
|
* @return the item, or null if the internal ID is invalid.
|
|
* @throws SQLException
|
|
*/
|
|
public static Item find(Context context, int id) throws SQLException
|
|
{
|
|
// First check the cache
|
|
Item fromCache = (Item) context.fromCache(Item.class, id);
|
|
|
|
if (fromCache != null)
|
|
{
|
|
return fromCache;
|
|
}
|
|
|
|
TableRow row = DatabaseManager.find(context, "item", id);
|
|
|
|
if (row == null)
|
|
{
|
|
if (log.isDebugEnabled())
|
|
{
|
|
log.debug(LogManager.getHeader(context, "find_item",
|
|
"not_found,item_id=" + id));
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
// not null, return item
|
|
if (log.isDebugEnabled())
|
|
{
|
|
log.debug(LogManager.getHeader(context, "find_item", "item_id="
|
|
+ id));
|
|
}
|
|
|
|
return new Item(context, row);
|
|
}
|
|
|
|
/**
|
|
* Create a new item, with a new internal ID. This method is not public,
|
|
* since items need to be created as workspace items. Authorisation is the
|
|
* responsibility of the caller.
|
|
*
|
|
* @param context
|
|
* DSpace context object
|
|
* @return the newly created item
|
|
* @throws SQLException
|
|
* @throws AuthorizeException
|
|
*/
|
|
static Item create(Context context) throws SQLException, AuthorizeException
|
|
{
|
|
TableRow row = DatabaseManager.create(context, "item");
|
|
Item i = new Item(context, row);
|
|
|
|
// set discoverable to true (default)
|
|
i.setDiscoverable(true);
|
|
|
|
// Call update to give the item a last modified date. OK this isn't
|
|
// amazingly efficient but creates don't happen that often.
|
|
context.turnOffAuthorisationSystem();
|
|
i.update();
|
|
context.restoreAuthSystemState();
|
|
|
|
context.addEvent(new Event(Event.CREATE, Constants.ITEM, i.getID(), null));
|
|
|
|
log.info(LogManager.getHeader(context, "create_item", "item_id="
|
|
+ row.getIntColumn("item_id")));
|
|
|
|
return i;
|
|
}
|
|
|
|
/**
|
|
* Get all the items in the archive. Only items with the "in archive" flag
|
|
* set are included. The order of the list is indeterminate.
|
|
*
|
|
* @param context
|
|
* DSpace context object
|
|
* @return an iterator over the items in the archive.
|
|
* @throws SQLException
|
|
*/
|
|
public static ItemIterator findAll(Context context) throws SQLException
|
|
{
|
|
String myQuery = "SELECT * FROM item WHERE in_archive='1'";
|
|
|
|
TableRowIterator rows = DatabaseManager.queryTable(context, "item", myQuery);
|
|
|
|
return new ItemIterator(context, rows);
|
|
}
|
|
|
|
/**
|
|
* Get all "final" items in the archive, both archived ("in archive" flag) or
|
|
* withdrawn items are included. The order of the list is indeterminate.
|
|
*
|
|
* @param context
|
|
* DSpace context object
|
|
* @return an iterator over the items in the archive.
|
|
* @throws SQLException
|
|
*/
|
|
public static ItemIterator findAllUnfiltered(Context context) throws SQLException
|
|
{
|
|
String myQuery = "SELECT * FROM item WHERE in_archive='1' or withdrawn='1'";
|
|
|
|
TableRowIterator rows = DatabaseManager.queryTable(context, "item", myQuery);
|
|
|
|
return new ItemIterator(context, rows);
|
|
}
|
|
|
|
/**
|
|
* Find all the items in the archive by a given submitter. The order is
|
|
* indeterminate. Only items with the "in archive" flag set are included.
|
|
*
|
|
* @param context
|
|
* DSpace context object
|
|
* @param eperson
|
|
* the submitter
|
|
* @return an iterator over the items submitted by eperson
|
|
* @throws SQLException
|
|
*/
|
|
public static ItemIterator findBySubmitter(Context context, EPerson eperson)
|
|
throws SQLException
|
|
{
|
|
String myQuery = "SELECT * FROM item WHERE in_archive='1' AND submitter_id="
|
|
+ eperson.getID();
|
|
|
|
TableRowIterator rows = DatabaseManager.queryTable(context, "item", myQuery);
|
|
|
|
return new ItemIterator(context, rows);
|
|
}
|
|
|
|
/**
|
|
* Get the internal ID of this item. In general, this shouldn't be exposed
|
|
* to users
|
|
*
|
|
* @return the internal identifier
|
|
*/
|
|
public int getID()
|
|
{
|
|
return itemRow.getIntColumn("item_id");
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
* @see org.dspace.content.DSpaceObject#getHandle()
|
|
*/
|
|
public String getHandle()
|
|
{
|
|
if(handle == null) {
|
|
try {
|
|
handle = HandleManager.findHandle(this.ourContext, this);
|
|
} catch (SQLException e) {
|
|
// TODO Auto-generated catch block
|
|
//e.printStackTrace();
|
|
}
|
|
}
|
|
return handle;
|
|
}
|
|
|
|
/**
|
|
* Find out if the item is part of the main archive
|
|
*
|
|
* @return true if the item is in the main archive
|
|
*/
|
|
public boolean isArchived()
|
|
{
|
|
return itemRow.getBooleanColumn("in_archive");
|
|
}
|
|
|
|
/**
|
|
* Find out if the item has been withdrawn
|
|
*
|
|
* @return true if the item has been withdrawn
|
|
*/
|
|
public boolean isWithdrawn()
|
|
{
|
|
return itemRow.getBooleanColumn("withdrawn");
|
|
}
|
|
|
|
/**
|
|
* Find out if the item is discoverable
|
|
*
|
|
* @return true if the item is discoverable
|
|
*/
|
|
public boolean isDiscoverable()
|
|
{
|
|
return itemRow.getBooleanColumn("discoverable");
|
|
}
|
|
|
|
/**
|
|
* Get the date the item was last modified, or the current date if
|
|
* last_modified is null
|
|
*
|
|
* @return the date the item was last modified, or the current date if the
|
|
* column is null.
|
|
*/
|
|
public Date getLastModified()
|
|
{
|
|
Date myDate = itemRow.getDateColumn("last_modified");
|
|
|
|
if (myDate == null)
|
|
{
|
|
myDate = new Date();
|
|
}
|
|
|
|
return myDate;
|
|
}
|
|
|
|
/**
|
|
* Method that updates the last modified date of the item
|
|
*/
|
|
public void updateLastModified()
|
|
{
|
|
try {
|
|
Date lastModified = new Timestamp(new Date().getTime());
|
|
itemRow.setColumn("last_modified", lastModified);
|
|
DatabaseManager.updateQuery(ourContext, "UPDATE item SET last_modified = ? WHERE item_id= ? ", lastModified, getID());
|
|
//Also fire a modified event since the item HAS been modified
|
|
ourContext.addEvent(new Event(Event.MODIFY, Constants.ITEM, getID(), null));
|
|
} catch (SQLException e) {
|
|
log.error(LogManager.getHeader(ourContext, "Error while updating last modified timestamp", "Item: " + getID()));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the "is_archived" flag. This is public and only
|
|
* <code>WorkflowItem.archive()</code> should set this.
|
|
*
|
|
* @param isArchived
|
|
* new value for the flag
|
|
*/
|
|
public void setArchived(boolean isArchived)
|
|
{
|
|
itemRow.setColumn("in_archive", isArchived);
|
|
modified = true;
|
|
}
|
|
|
|
/**
|
|
* Set the "discoverable" flag. This is public and only
|
|
*
|
|
* @param discoverable
|
|
* new value for the flag
|
|
*/
|
|
public void setDiscoverable(boolean discoverable)
|
|
{
|
|
itemRow.setColumn("discoverable", discoverable);
|
|
modified = true;
|
|
}
|
|
|
|
/**
|
|
* Set the owning Collection for the item
|
|
*
|
|
* @param c
|
|
* Collection
|
|
*/
|
|
public void setOwningCollection(Collection c)
|
|
{
|
|
itemRow.setColumn("owning_collection", c.getID());
|
|
modified = true;
|
|
}
|
|
|
|
/**
|
|
* Get the owning Collection for the item
|
|
*
|
|
* @return Collection that is the owner of the item
|
|
* @throws SQLException
|
|
*/
|
|
public Collection getOwningCollection() throws java.sql.SQLException
|
|
{
|
|
Collection myCollection = null;
|
|
|
|
// get the collection ID
|
|
int cid = itemRow.getIntColumn("owning_collection");
|
|
|
|
myCollection = Collection.find(ourContext, cid);
|
|
|
|
return myCollection;
|
|
}
|
|
|
|
// just get the collection ID for internal use
|
|
private int getOwningCollectionID()
|
|
{
|
|
return itemRow.getIntColumn("owning_collection");
|
|
}
|
|
|
|
/**
|
|
* Get Dublin Core metadata for the item.
|
|
* Passing in a <code>null</code> value for <code>qualifier</code>
|
|
* or <code>lang</code> only matches Dublin Core fields where that
|
|
* qualifier or languages is actually <code>null</code>.
|
|
* Passing in <code>Item.ANY</code>
|
|
* retrieves all metadata fields with any value for the qualifier or
|
|
* language, including <code>null</code>
|
|
* <P>
|
|
* Examples:
|
|
* <P>
|
|
* Return values of the unqualified "title" field, in any language.
|
|
* Qualified title fields (e.g. "title.uniform") are NOT returned:
|
|
* <P>
|
|
* <code>item.getDC( "title", null, Item.ANY );</code>
|
|
* <P>
|
|
* Return all US English values of the "title" element, with any qualifier
|
|
* (including unqualified):
|
|
* <P>
|
|
* <code>item.getDC( "title", Item.ANY, "en_US" );</code>
|
|
* <P>
|
|
* The ordering of values of a particular element/qualifier/language
|
|
* combination is significant. When retrieving with wildcards, values of a
|
|
* particular element/qualifier/language combinations will be adjacent, but
|
|
* the overall ordering of the combinations is indeterminate.
|
|
*
|
|
* @param element
|
|
* the Dublin Core element. <code>Item.ANY</code> matches any
|
|
* element. <code>null</code> doesn't really make sense as all
|
|
* DC must have an element.
|
|
* @param qualifier
|
|
* the qualifier. <code>null</code> means unqualified, and
|
|
* <code>Item.ANY</code> means any qualifier (including
|
|
* unqualified.)
|
|
* @param lang
|
|
* the ISO639 language code, optionally followed by an underscore
|
|
* and the ISO3166 country code. <code>null</code> means only
|
|
* values with no language are returned, and
|
|
* <code>Item.ANY</code> means values with any country code or
|
|
* no country code are returned.
|
|
* @return Dublin Core fields that match the parameters
|
|
*/
|
|
@Deprecated
|
|
public DCValue[] getDC(String element, String qualifier, String lang)
|
|
{
|
|
return getMetadata(MetadataSchema.DC_SCHEMA, element, qualifier, lang);
|
|
}
|
|
|
|
/**
|
|
* Get metadata for the item in a chosen schema.
|
|
* See <code>MetadataSchema</code> for more information about schemas.
|
|
* Passing in a <code>null</code> value for <code>qualifier</code>
|
|
* or <code>lang</code> only matches metadata fields where that
|
|
* qualifier or languages is actually <code>null</code>.
|
|
* Passing in <code>Item.ANY</code>
|
|
* retrieves all metadata fields with any value for the qualifier or
|
|
* language, including <code>null</code>
|
|
* <P>
|
|
* Examples:
|
|
* <P>
|
|
* Return values of the unqualified "title" field, in any language.
|
|
* Qualified title fields (e.g. "title.uniform") are NOT returned:
|
|
* <P>
|
|
* <code>item.getMetadata("dc", "title", null, Item.ANY );</code>
|
|
* <P>
|
|
* Return all US English values of the "title" element, with any qualifier
|
|
* (including unqualified):
|
|
* <P>
|
|
* <code>item.getMetadata("dc, "title", Item.ANY, "en_US" );</code>
|
|
* <P>
|
|
* The ordering of values of a particular element/qualifier/language
|
|
* combination is significant. When retrieving with wildcards, values of a
|
|
* particular element/qualifier/language combinations will be adjacent, but
|
|
* the overall ordering of the combinations is indeterminate.
|
|
*
|
|
* @param schema
|
|
* the schema for the metadata field. <em>Must</em> match
|
|
* the <code>name</code> of an existing metadata schema.
|
|
* @param element
|
|
* the element name. <code>Item.ANY</code> matches any
|
|
* element. <code>null</code> doesn't really make sense as all
|
|
* metadata must have an element.
|
|
* @param qualifier
|
|
* the qualifier. <code>null</code> means unqualified, and
|
|
* <code>Item.ANY</code> means any qualifier (including
|
|
* unqualified.)
|
|
* @param lang
|
|
* the ISO639 language code, optionally followed by an underscore
|
|
* and the ISO3166 country code. <code>null</code> means only
|
|
* values with no language are returned, and
|
|
* <code>Item.ANY</code> means values with any country code or
|
|
* no country code are returned.
|
|
* @return metadata fields that match the parameters
|
|
*/
|
|
public DCValue[] getMetadata(String schema, String element, String qualifier,
|
|
String lang)
|
|
{
|
|
// Build up list of matching values
|
|
List<DCValue> values = new ArrayList<DCValue>();
|
|
for (DCValue dcv : getMetadata())
|
|
{
|
|
if (match(schema, element, qualifier, lang, dcv))
|
|
{
|
|
// We will return a copy of the object in case it is altered
|
|
DCValue copy = new DCValue();
|
|
copy.element = dcv.element;
|
|
copy.qualifier = dcv.qualifier;
|
|
copy.value = dcv.value;
|
|
copy.language = dcv.language;
|
|
copy.schema = dcv.schema;
|
|
copy.authority = dcv.authority;
|
|
copy.confidence = dcv.confidence;
|
|
values.add(copy);
|
|
}
|
|
}
|
|
|
|
// Create an array of matching values
|
|
DCValue[] valueArray = new DCValue[values.size()];
|
|
valueArray = (DCValue[]) values.toArray(valueArray);
|
|
|
|
return valueArray;
|
|
}
|
|
|
|
/**
|
|
* Retrieve metadata field values from a given metadata string
|
|
* of the form <schema prefix>.<element>[.<qualifier>|.*]
|
|
*
|
|
* @param mdString
|
|
* The metadata string of the form
|
|
* <schema prefix>.<element>[.<qualifier>|.*]
|
|
*/
|
|
public DCValue[] getMetadata(String mdString)
|
|
{
|
|
StringTokenizer dcf = new StringTokenizer(mdString, ".");
|
|
|
|
String[] tokens = { "", "", "" };
|
|
int i = 0;
|
|
while(dcf.hasMoreTokens())
|
|
{
|
|
tokens[i] = dcf.nextToken().trim();
|
|
i++;
|
|
}
|
|
String schema = tokens[0];
|
|
String element = tokens[1];
|
|
String qualifier = tokens[2];
|
|
|
|
DCValue[] values;
|
|
if ("*".equals(qualifier))
|
|
{
|
|
values = getMetadata(schema, element, Item.ANY, Item.ANY);
|
|
}
|
|
else if ("".equals(qualifier))
|
|
{
|
|
values = getMetadata(schema, element, null, Item.ANY);
|
|
}
|
|
else
|
|
{
|
|
values = getMetadata(schema, element, qualifier, Item.ANY);
|
|
}
|
|
|
|
return values;
|
|
}
|
|
|
|
/**
|
|
* Add Dublin Core metadata fields. These are appended to existing values.
|
|
* Use <code>clearDC</code> to remove values. The ordering of values
|
|
* passed in is maintained.
|
|
*
|
|
* @param element
|
|
* the Dublin Core element
|
|
* @param qualifier
|
|
* the Dublin Core qualifier, or <code>null</code> for
|
|
* unqualified
|
|
* @param lang
|
|
* the ISO639 language code, optionally followed by an underscore
|
|
* and the ISO3166 country code. <code>null</code> means the
|
|
* value has no language (for example, a date).
|
|
* @param values
|
|
* the values to add.
|
|
*/
|
|
@Deprecated
|
|
public void addDC(String element, String qualifier, String lang,
|
|
String[] values)
|
|
{
|
|
addMetadata(MetadataSchema.DC_SCHEMA, element, qualifier, lang, values);
|
|
}
|
|
|
|
/**
|
|
* Add a single Dublin Core metadata field. This is appended to existing
|
|
* values. Use <code>clearDC</code> to remove values.
|
|
*
|
|
* @param element
|
|
* the Dublin Core element
|
|
* @param qualifier
|
|
* the Dublin Core qualifier, or <code>null</code> for
|
|
* unqualified
|
|
* @param lang
|
|
* the ISO639 language code, optionally followed by an underscore
|
|
* and the ISO3166 country code. <code>null</code> means the
|
|
* value has no language (for example, a date).
|
|
* @param value
|
|
* the value to add.
|
|
*/
|
|
@Deprecated
|
|
public void addDC(String element, String qualifier, String lang,
|
|
String value)
|
|
{
|
|
addMetadata(MetadataSchema.DC_SCHEMA, element, qualifier, lang, value);
|
|
}
|
|
|
|
/**
|
|
* Add metadata fields. These are appended to existing values.
|
|
* Use <code>clearDC</code> to remove values. The ordering of values
|
|
* passed in is maintained.
|
|
* <p>
|
|
* If metadata authority control is available, try to get authority
|
|
* values. The authority confidence depends on whether authority is
|
|
* <em>required</em> or not.
|
|
* @param schema
|
|
* the schema for the metadata field. <em>Must</em> match
|
|
* the <code>name</code> of an existing metadata schema.
|
|
* @param element
|
|
* the metadata element name
|
|
* @param qualifier
|
|
* the metadata qualifier name, or <code>null</code> for
|
|
* unqualified
|
|
* @param lang
|
|
* the ISO639 language code, optionally followed by an underscore
|
|
* and the ISO3166 country code. <code>null</code> means the
|
|
* value has no language (for example, a date).
|
|
* @param values
|
|
* the values to add.
|
|
*/
|
|
public void addMetadata(String schema, String element, String qualifier, String lang,
|
|
String[] values)
|
|
{
|
|
MetadataAuthorityManager mam = MetadataAuthorityManager.getManager();
|
|
String fieldKey = MetadataAuthorityManager.makeFieldKey(schema, element, qualifier);
|
|
if (mam.isAuthorityControlled(fieldKey))
|
|
{
|
|
String authorities[] = new String[values.length];
|
|
int confidences[] = new int[values.length];
|
|
for (int i = 0; i < values.length; ++i)
|
|
{
|
|
Choices c = ChoiceAuthorityManager.getManager().getBestMatch(fieldKey, values[i], getOwningCollectionID(), null);
|
|
authorities[i] = c.values.length > 0 ? c.values[0].authority : null;
|
|
confidences[i] = c.confidence;
|
|
}
|
|
addMetadata(schema, element, qualifier, lang, values, authorities, confidences);
|
|
}
|
|
else
|
|
{
|
|
addMetadata(schema, element, qualifier, lang, values, null, null);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add metadata fields. These are appended to existing values.
|
|
* Use <code>clearDC</code> to remove values. The ordering of values
|
|
* passed in is maintained.
|
|
* @param schema
|
|
* the schema for the metadata field. <em>Must</em> match
|
|
* the <code>name</code> of an existing metadata schema.
|
|
* @param element
|
|
* the metadata element name
|
|
* @param qualifier
|
|
* the metadata qualifier name, or <code>null</code> for
|
|
* unqualified
|
|
* @param lang
|
|
* the ISO639 language code, optionally followed by an underscore
|
|
* and the ISO3166 country code. <code>null</code> means the
|
|
* value has no language (for example, a date).
|
|
* @param values
|
|
* the values to add.
|
|
* @param authorities
|
|
* the external authority key for this value (or null)
|
|
* @param confidences
|
|
* the authority confidence (default 0)
|
|
*/
|
|
public void addMetadata(String schema, String element, String qualifier, String lang,
|
|
String[] values, String authorities[], int confidences[])
|
|
{
|
|
List<DCValue> dublinCore = getMetadata();
|
|
MetadataAuthorityManager mam = MetadataAuthorityManager.getManager();
|
|
boolean authorityControlled = mam.isAuthorityControlled(schema, element, qualifier);
|
|
boolean authorityRequired = mam.isAuthorityRequired(schema, element, qualifier);
|
|
String fieldName = schema+"."+element+((qualifier==null)? "": "."+qualifier);
|
|
|
|
// We will not verify that they are valid entries in the registry
|
|
// until update() is called.
|
|
for (int i = 0; i < values.length; i++)
|
|
{
|
|
DCValue dcv = new DCValue();
|
|
dcv.schema = schema;
|
|
dcv.element = element;
|
|
dcv.qualifier = qualifier;
|
|
dcv.language = (lang == null ? null : lang.trim());
|
|
|
|
// Logic to set Authority and Confidence:
|
|
// - normalize an empty string for authority to NULL.
|
|
// - if authority key is present, use given confidence or NOVALUE if not given
|
|
// - otherwise, preserve confidence if meaningful value was given since it may document a failed authority lookup
|
|
// - CF_UNSET signifies no authority nor meaningful confidence.
|
|
// - it's possible to have empty authority & CF_ACCEPTED if e.g. user deletes authority key
|
|
if (authorityControlled)
|
|
{
|
|
if (authorities != null && authorities[i] != null && authorities[i].length() > 0)
|
|
{
|
|
dcv.authority = authorities[i];
|
|
dcv.confidence = confidences == null ? Choices.CF_NOVALUE : confidences[i];
|
|
}
|
|
else
|
|
{
|
|
dcv.authority = null;
|
|
dcv.confidence = confidences == null ? Choices.CF_UNSET : confidences[i];
|
|
}
|
|
// authority sanity check: if authority is required, was it supplied?
|
|
// XXX FIXME? can't throw a "real" exception here without changing all the callers to expect it, so use a runtime exception
|
|
if (authorityRequired && (dcv.authority == null || dcv.authority.length() == 0))
|
|
{
|
|
throw new IllegalArgumentException("The metadata field \"" + fieldName + "\" requires an authority key but none was provided. Vaue=\"" + dcv.value + "\"");
|
|
}
|
|
}
|
|
if (values[i] != null)
|
|
{
|
|
// remove control unicode char
|
|
String temp = values[i].trim();
|
|
char[] dcvalue = temp.toCharArray();
|
|
for (int charPos = 0; charPos < dcvalue.length; charPos++)
|
|
{
|
|
if (Character.isISOControl(dcvalue[charPos]) &&
|
|
!String.valueOf(dcvalue[charPos]).equals("\u0009") &&
|
|
!String.valueOf(dcvalue[charPos]).equals("\n") &&
|
|
!String.valueOf(dcvalue[charPos]).equals("\r"))
|
|
{
|
|
dcvalue[charPos] = ' ';
|
|
}
|
|
}
|
|
dcv.value = String.valueOf(dcvalue);
|
|
}
|
|
else
|
|
{
|
|
dcv.value = null;
|
|
}
|
|
dublinCore.add(dcv);
|
|
addDetails(fieldName);
|
|
}
|
|
|
|
if (values.length > 0)
|
|
{
|
|
dublinCoreChanged = true;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Add a single metadata field. This is appended to existing
|
|
* values. Use <code>clearDC</code> to remove values.
|
|
*
|
|
* @param schema
|
|
* the schema for the metadata field. <em>Must</em> match
|
|
* the <code>name</code> of an existing metadata schema.
|
|
* @param element
|
|
* the metadata element name
|
|
* @param qualifier
|
|
* the metadata qualifier, or <code>null</code> for
|
|
* unqualified
|
|
* @param lang
|
|
* the ISO639 language code, optionally followed by an underscore
|
|
* and the ISO3166 country code. <code>null</code> means the
|
|
* value has no language (for example, a date).
|
|
* @param value
|
|
* the value to add.
|
|
*/
|
|
public void addMetadata(String schema, String element, String qualifier,
|
|
String lang, String value)
|
|
{
|
|
String[] valArray = new String[1];
|
|
valArray[0] = value;
|
|
|
|
addMetadata(schema, element, qualifier, lang, valArray);
|
|
}
|
|
|
|
/**
|
|
* Add a single metadata field. This is appended to existing
|
|
* values. Use <code>clearDC</code> to remove values.
|
|
*
|
|
* @param schema
|
|
* the schema for the metadata field. <em>Must</em> match
|
|
* the <code>name</code> of an existing metadata schema.
|
|
* @param element
|
|
* the metadata element name
|
|
* @param qualifier
|
|
* the metadata qualifier, or <code>null</code> for
|
|
* unqualified
|
|
* @param lang
|
|
* the ISO639 language code, optionally followed by an underscore
|
|
* and the ISO3166 country code. <code>null</code> means the
|
|
* value has no language (for example, a date).
|
|
* @param value
|
|
* the value to add.
|
|
* @param authority
|
|
* the external authority key for this value (or null)
|
|
* @param confidence
|
|
* the authority confidence (default 0)
|
|
*/
|
|
public void addMetadata(String schema, String element, String qualifier,
|
|
String lang, String value, String authority, int confidence)
|
|
{
|
|
String[] valArray = new String[1];
|
|
String[] authArray = new String[1];
|
|
int[] confArray = new int[1];
|
|
valArray[0] = value;
|
|
authArray[0] = authority;
|
|
confArray[0] = confidence;
|
|
|
|
addMetadata(schema, element, qualifier, lang, valArray, authArray, confArray);
|
|
}
|
|
|
|
/**
|
|
* Clear Dublin Core metadata values. As with <code>getDC</code> above,
|
|
* passing in <code>null</code> only matches fields where the qualifier or
|
|
* language is actually <code>null</code>.<code>Item.ANY</code> will
|
|
* match any element, qualifier or language, including <code>null</code>.
|
|
* Thus, <code>item.clearDC(Item.ANY, Item.ANY, Item.ANY)</code> will
|
|
* remove all Dublin Core metadata associated with an item.
|
|
*
|
|
* @param element
|
|
* the Dublin Core element to remove, or <code>Item.ANY</code>
|
|
* @param qualifier
|
|
* the qualifier. <code>null</code> means unqualified, and
|
|
* <code>Item.ANY</code> means any qualifier (including
|
|
* unqualified.)
|
|
* @param lang
|
|
* the ISO639 language code, optionally followed by an underscore
|
|
* and the ISO3166 country code. <code>null</code> means only
|
|
* values with no language are removed, and <code>Item.ANY</code>
|
|
* means values with any country code or no country code are
|
|
* removed.
|
|
*/
|
|
@Deprecated
|
|
public void clearDC(String element, String qualifier, String lang)
|
|
{
|
|
clearMetadata(MetadataSchema.DC_SCHEMA, element, qualifier, lang);
|
|
}
|
|
|
|
/**
|
|
* Clear metadata values. As with <code>getDC</code> above,
|
|
* passing in <code>null</code> only matches fields where the qualifier or
|
|
* language is actually <code>null</code>.<code>Item.ANY</code> will
|
|
* match any element, qualifier or language, including <code>null</code>.
|
|
* Thus, <code>item.clearDC(Item.ANY, Item.ANY, Item.ANY)</code> will
|
|
* remove all Dublin Core metadata associated with an item.
|
|
*
|
|
* @param schema
|
|
* the schema for the metadata field. <em>Must</em> match
|
|
* the <code>name</code> of an existing metadata schema.
|
|
* @param element
|
|
* the Dublin Core element to remove, or <code>Item.ANY</code>
|
|
* @param qualifier
|
|
* the qualifier. <code>null</code> means unqualified, and
|
|
* <code>Item.ANY</code> means any qualifier (including
|
|
* unqualified.)
|
|
* @param lang
|
|
* the ISO639 language code, optionally followed by an underscore
|
|
* and the ISO3166 country code. <code>null</code> means only
|
|
* values with no language are removed, and <code>Item.ANY</code>
|
|
* means values with any country code or no country code are
|
|
* removed.
|
|
*/
|
|
public void clearMetadata(String schema, String element, String qualifier,
|
|
String lang)
|
|
{
|
|
// We will build a list of values NOT matching the values to clear
|
|
List<DCValue> values = new ArrayList<DCValue>();
|
|
for (DCValue dcv : getMetadata())
|
|
{
|
|
if (!match(schema, element, qualifier, lang, dcv))
|
|
{
|
|
values.add(dcv);
|
|
}
|
|
}
|
|
|
|
// Now swap the old list of values for the new, unremoved values
|
|
setMetadata(values);
|
|
dublinCoreChanged = true;
|
|
}
|
|
|
|
/**
|
|
* Utility method for pattern-matching metadata elements. This
|
|
* method will return <code>true</code> if the given schema,
|
|
* element, qualifier and language match the schema, element,
|
|
* qualifier and language of the <code>DCValue</code> object passed
|
|
* in. Any or all of the element, qualifier and language passed
|
|
* in can be the <code>Item.ANY</code> wildcard.
|
|
*
|
|
* @param schema
|
|
* the schema for the metadata field. <em>Must</em> match
|
|
* the <code>name</code> of an existing metadata schema.
|
|
* @param element
|
|
* the element to match, or <code>Item.ANY</code>
|
|
* @param qualifier
|
|
* the qualifier to match, or <code>Item.ANY</code>
|
|
* @param language
|
|
* the language to match, or <code>Item.ANY</code>
|
|
* @param dcv
|
|
* the Dublin Core value
|
|
* @return <code>true</code> if there is a match
|
|
*/
|
|
private boolean match(String schema, String element, String qualifier,
|
|
String language, DCValue dcv)
|
|
{
|
|
// We will attempt to disprove a match - if we can't we have a match
|
|
if (!element.equals(Item.ANY) && !element.equals(dcv.element))
|
|
{
|
|
// Elements do not match, no wildcard
|
|
return false;
|
|
}
|
|
|
|
if (qualifier == null)
|
|
{
|
|
// Value must be unqualified
|
|
if (dcv.qualifier != null)
|
|
{
|
|
// Value is qualified, so no match
|
|
return false;
|
|
}
|
|
}
|
|
else if (!qualifier.equals(Item.ANY))
|
|
{
|
|
// Not a wildcard, so qualifier must match exactly
|
|
if (!qualifier.equals(dcv.qualifier))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (language == null)
|
|
{
|
|
// Value must be null language to match
|
|
if (dcv.language != null)
|
|
{
|
|
// Value is qualified, so no match
|
|
return false;
|
|
}
|
|
}
|
|
else if (!language.equals(Item.ANY))
|
|
{
|
|
// Not a wildcard, so language must match exactly
|
|
if (!language.equals(dcv.language))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!schema.equals(Item.ANY))
|
|
{
|
|
if (dcv.schema != null && !dcv.schema.equals(schema))
|
|
{
|
|
// The namespace doesn't match
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// If we get this far, we have a match
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get the e-person that originally submitted this item
|
|
*
|
|
* @return the submitter
|
|
*/
|
|
public EPerson getSubmitter() throws SQLException
|
|
{
|
|
if (submitter == null && !itemRow.isColumnNull("submitter_id"))
|
|
{
|
|
submitter = EPerson.find(ourContext, itemRow
|
|
.getIntColumn("submitter_id"));
|
|
}
|
|
return submitter;
|
|
}
|
|
|
|
/**
|
|
* Set the e-person that originally submitted this item. This is a public
|
|
* method since it is handled by the WorkspaceItem class in the ingest
|
|
* package. <code>update</code> must be called to write the change to the
|
|
* database.
|
|
*
|
|
* @param sub
|
|
* the submitter
|
|
*/
|
|
public void setSubmitter(EPerson sub)
|
|
{
|
|
submitter = sub;
|
|
|
|
if (submitter != null)
|
|
{
|
|
itemRow.setColumn("submitter_id", submitter.getID());
|
|
}
|
|
else
|
|
{
|
|
itemRow.setColumnNull("submitter_id");
|
|
}
|
|
modified = true;
|
|
}
|
|
|
|
/**
|
|
* See whether this Item is contained by a given Collection.
|
|
* @param collection
|
|
* @return true if {@code collection} contains this Item.
|
|
* @throws SQLException
|
|
*/
|
|
public boolean isIn(Collection collection) throws SQLException
|
|
{
|
|
TableRow tr = DatabaseManager.querySingle(ourContext,
|
|
"SELECT COUNT(*) AS count" +
|
|
" FROM collection2item" +
|
|
" WHERE collection_id = ? AND item_id = ?",
|
|
collection.getID(), itemRow.getIntColumn("item_id"));
|
|
return tr.getLongColumn("count") > 0;
|
|
}
|
|
|
|
/**
|
|
* Get the collections this item is in. The order is indeterminate.
|
|
*
|
|
* @return the collections this item is in, if any.
|
|
* @throws SQLException
|
|
*/
|
|
public Collection[] getCollections() throws SQLException
|
|
{
|
|
List<Collection> collections = new ArrayList<Collection>();
|
|
|
|
// Get collection table rows
|
|
TableRowIterator tri = DatabaseManager.queryTable(ourContext,"collection",
|
|
"SELECT collection.* FROM collection, collection2item WHERE " +
|
|
"collection2item.collection_id=collection.collection_id AND " +
|
|
"collection2item.item_id= ? ",
|
|
itemRow.getIntColumn("item_id"));
|
|
|
|
try
|
|
{
|
|
while (tri.hasNext())
|
|
{
|
|
TableRow row = tri.next();
|
|
|
|
// First check the cache
|
|
Collection fromCache = (Collection) ourContext.fromCache(
|
|
Collection.class, row.getIntColumn("collection_id"));
|
|
|
|
if (fromCache != null)
|
|
{
|
|
collections.add(fromCache);
|
|
}
|
|
else
|
|
{
|
|
collections.add(new Collection(ourContext, row));
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
// close the TableRowIterator to free up resources
|
|
if (tri != null)
|
|
{
|
|
tri.close();
|
|
}
|
|
}
|
|
|
|
Collection[] collectionArray = new Collection[collections.size()];
|
|
collectionArray = (Collection[]) collections.toArray(collectionArray);
|
|
|
|
return collectionArray;
|
|
}
|
|
|
|
/**
|
|
* Get the communities this item is in. Returns an unordered array of the
|
|
* communities that house the collections this item is in, including parent
|
|
* communities of the owning collections.
|
|
*
|
|
* @return the communities this item is in.
|
|
* @throws SQLException
|
|
*/
|
|
public Community[] getCommunities() throws SQLException
|
|
{
|
|
List<Community> communities = new ArrayList<Community>();
|
|
|
|
// Get community table rows
|
|
TableRowIterator tri = DatabaseManager.queryTable(ourContext,"community",
|
|
"SELECT community.* FROM community, community2item " +
|
|
"WHERE community2item.community_id=community.community_id " +
|
|
"AND community2item.item_id= ? ",
|
|
itemRow.getIntColumn("item_id"));
|
|
|
|
try
|
|
{
|
|
while (tri.hasNext())
|
|
{
|
|
TableRow row = tri.next();
|
|
|
|
// First check the cache
|
|
Community owner = (Community) ourContext.fromCache(Community.class,
|
|
row.getIntColumn("community_id"));
|
|
|
|
if (owner == null)
|
|
{
|
|
owner = new Community(ourContext, row);
|
|
}
|
|
|
|
communities.add(owner);
|
|
|
|
// now add any parent communities
|
|
Community[] parents = owner.getAllParents();
|
|
communities.addAll(Arrays.asList(parents));
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
// close the TableRowIterator to free up resources
|
|
if (tri != null)
|
|
{
|
|
tri.close();
|
|
}
|
|
}
|
|
|
|
Community[] communityArray = new Community[communities.size()];
|
|
communityArray = (Community[]) communities.toArray(communityArray);
|
|
|
|
return communityArray;
|
|
}
|
|
|
|
/**
|
|
* Get the bundles in this item.
|
|
*
|
|
* @return the bundles in an unordered array
|
|
*/
|
|
public Bundle[] getBundles() throws SQLException
|
|
{
|
|
if (bundles == null)
|
|
{
|
|
bundles = new ArrayList<Bundle>();
|
|
// Get bundles
|
|
TableRowIterator tri = DatabaseManager.queryTable(ourContext, "bundle",
|
|
"SELECT bundle.* FROM bundle, item2bundle WHERE " +
|
|
"item2bundle.bundle_id=bundle.bundle_id AND " +
|
|
"item2bundle.item_id= ? ",
|
|
itemRow.getIntColumn("item_id"));
|
|
|
|
try
|
|
{
|
|
while (tri.hasNext())
|
|
{
|
|
TableRow r = tri.next();
|
|
|
|
// First check the cache
|
|
Bundle fromCache = (Bundle) ourContext.fromCache(Bundle.class,
|
|
r.getIntColumn("bundle_id"));
|
|
|
|
if (fromCache != null)
|
|
{
|
|
bundles.add(fromCache);
|
|
}
|
|
else
|
|
{
|
|
bundles.add(new Bundle(ourContext, r));
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
// close the TableRowIterator to free up resources
|
|
if (tri != null)
|
|
{
|
|
tri.close();
|
|
}
|
|
}
|
|
}
|
|
|
|
Bundle[] bundleArray = new Bundle[bundles.size()];
|
|
bundleArray = (Bundle[]) bundles.toArray(bundleArray);
|
|
|
|
return bundleArray;
|
|
}
|
|
|
|
/**
|
|
* Get the bundles matching a bundle name (name corresponds roughly to type)
|
|
*
|
|
* @param name
|
|
* name of bundle (ORIGINAL/TEXT/THUMBNAIL)
|
|
*
|
|
* @return the bundles in an unordered array
|
|
*/
|
|
public Bundle[] getBundles(String name) throws SQLException
|
|
{
|
|
List<Bundle> matchingBundles = new ArrayList<Bundle>();
|
|
|
|
// now only keep bundles with matching names
|
|
Bundle[] bunds = getBundles();
|
|
for (int i = 0; i < bunds.length; i++ )
|
|
{
|
|
if (name.equals(bunds[i].getName()))
|
|
{
|
|
matchingBundles.add(bunds[i]);
|
|
}
|
|
}
|
|
|
|
Bundle[] bundleArray = new Bundle[matchingBundles.size()];
|
|
bundleArray = (Bundle[]) matchingBundles.toArray(bundleArray);
|
|
|
|
return bundleArray;
|
|
}
|
|
|
|
/**
|
|
* Create a bundle in this item, with immediate effect
|
|
*
|
|
* @param name
|
|
* bundle name (ORIGINAL/TEXT/THUMBNAIL)
|
|
* @return the newly created bundle
|
|
* @throws SQLException
|
|
* @throws AuthorizeException
|
|
*/
|
|
public Bundle createBundle(String name) throws SQLException,
|
|
AuthorizeException
|
|
{
|
|
if ((name == null) || "".equals(name))
|
|
{
|
|
throw new SQLException("Bundle must be created with non-null name");
|
|
}
|
|
|
|
// Check authorisation
|
|
AuthorizeManager.authorizeAction(ourContext, this, Constants.ADD);
|
|
|
|
Bundle b = Bundle.create(ourContext);
|
|
b.setName(name);
|
|
b.update();
|
|
|
|
addBundle(b);
|
|
|
|
return b;
|
|
}
|
|
|
|
/**
|
|
* Add an existing bundle to this item. This has immediate effect.
|
|
*
|
|
* @param b
|
|
* the bundle to add
|
|
* @throws SQLException
|
|
* @throws AuthorizeException
|
|
*/
|
|
public void addBundle(Bundle b) throws SQLException, AuthorizeException
|
|
{
|
|
// Check authorisation
|
|
AuthorizeManager.authorizeAction(ourContext, this, Constants.ADD);
|
|
|
|
log.info(LogManager.getHeader(ourContext, "add_bundle", "item_id="
|
|
+ getID() + ",bundle_id=" + b.getID()));
|
|
|
|
// Check it's not already there
|
|
Bundle[] bunds = getBundles();
|
|
for (int i = 0; i < bunds.length; i++)
|
|
{
|
|
if (b.getID() == bunds[i].getID())
|
|
{
|
|
// Bundle is already there; no change
|
|
return;
|
|
}
|
|
}
|
|
|
|
// now add authorization policies from owning item
|
|
// hmm, not very "multiple-inclusion" friendly
|
|
AuthorizeManager.inheritPolicies(ourContext, this, b);
|
|
|
|
// Add the bundle to in-memory list
|
|
bundles.add(b);
|
|
|
|
// Insert the mapping
|
|
TableRow mappingRow = DatabaseManager.row("item2bundle");
|
|
mappingRow.setColumn("item_id", getID());
|
|
mappingRow.setColumn("bundle_id", b.getID());
|
|
DatabaseManager.insert(ourContext, mappingRow);
|
|
|
|
ourContext.addEvent(new Event(Event.ADD, Constants.ITEM, getID(), Constants.BUNDLE, b.getID(), b.getName()));
|
|
}
|
|
|
|
/**
|
|
* Remove a bundle. This may result in the bundle being deleted, if the
|
|
* bundle is orphaned.
|
|
*
|
|
* @param b
|
|
* the bundle to remove
|
|
* @throws SQLException
|
|
* @throws AuthorizeException
|
|
* @throws IOException
|
|
*/
|
|
public void removeBundle(Bundle b) throws SQLException, AuthorizeException,
|
|
IOException
|
|
{
|
|
// Check authorisation
|
|
AuthorizeManager.authorizeAction(ourContext, this, Constants.REMOVE);
|
|
|
|
log.info(LogManager.getHeader(ourContext, "remove_bundle", "item_id="
|
|
+ getID() + ",bundle_id=" + b.getID()));
|
|
|
|
// Remove from internal list of bundles
|
|
Bundle[] bunds = getBundles();
|
|
|
|
for (int i = 0; i < bunds.length; i++)
|
|
{
|
|
if (b.getID() == bunds[i].getID())
|
|
{
|
|
// We've found the bundle to remove
|
|
bundles.remove(bunds[i]);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Remove mapping from DB
|
|
DatabaseManager.updateQuery(ourContext,
|
|
"DELETE FROM item2bundle WHERE item_id= ? " +
|
|
"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= ? ",
|
|
b.getID());
|
|
|
|
try
|
|
{
|
|
if (!tri.hasNext())
|
|
{
|
|
//make the right to remove the bundle explicit because the implicit
|
|
// relation
|
|
//has been removed. This only has to concern the currentUser
|
|
// because
|
|
//he started the removal process and he will end it too.
|
|
//also add right to remove from the bundle to remove it's
|
|
// bitstreams.
|
|
AuthorizeManager.addPolicy(ourContext, b, Constants.DELETE,
|
|
ourContext.getCurrentUser());
|
|
AuthorizeManager.addPolicy(ourContext, b, Constants.REMOVE,
|
|
ourContext.getCurrentUser());
|
|
|
|
// The bundle is an orphan, delete it
|
|
b.delete();
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
// close the TableRowIterator to free up resources
|
|
if (tri != null)
|
|
{
|
|
tri.close();
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Create a single bitstream in a new bundle. Provided as a convenience
|
|
* method for the most common use.
|
|
*
|
|
* @param is
|
|
* the stream to create the new bitstream from
|
|
* @param name
|
|
* is the name of the bundle (ORIGINAL, TEXT, THUMBNAIL)
|
|
* @return Bitstream that is created
|
|
* @throws AuthorizeException
|
|
* @throws IOException
|
|
* @throws SQLException
|
|
*/
|
|
public Bitstream createSingleBitstream(InputStream is, String name)
|
|
throws AuthorizeException, IOException, SQLException
|
|
{
|
|
// Authorisation is checked by methods below
|
|
// Create a bundle
|
|
Bundle bnd = createBundle(name);
|
|
Bitstream bitstream = bnd.createBitstream(is);
|
|
addBundle(bnd);
|
|
|
|
// FIXME: Create permissions for new bundle + bitstream
|
|
return bitstream;
|
|
}
|
|
|
|
/**
|
|
* Convenience method, calls createSingleBitstream() with name "ORIGINAL"
|
|
*
|
|
* @param is
|
|
* InputStream
|
|
* @return created bitstream
|
|
* @throws AuthorizeException
|
|
* @throws IOException
|
|
* @throws SQLException
|
|
*/
|
|
public Bitstream createSingleBitstream(InputStream is)
|
|
throws AuthorizeException, IOException, SQLException
|
|
{
|
|
return createSingleBitstream(is, "ORIGINAL");
|
|
}
|
|
|
|
/**
|
|
* Get all non-internal bitstreams in the item. This is mainly used for
|
|
* auditing for provenance messages and adding format.* DC values. The order
|
|
* is indeterminate.
|
|
*
|
|
* @return non-internal bitstreams.
|
|
*/
|
|
public Bitstream[] getNonInternalBitstreams() throws SQLException
|
|
{
|
|
List<Bitstream> bitstreamList = new ArrayList<Bitstream>();
|
|
|
|
// Go through the bundles and bitstreams picking out ones which aren't
|
|
// of internal formats
|
|
Bundle[] bunds = getBundles();
|
|
|
|
for (int i = 0; i < bunds.length; i++)
|
|
{
|
|
Bitstream[] bitstreams = bunds[i].getBitstreams();
|
|
|
|
for (int j = 0; j < bitstreams.length; j++)
|
|
{
|
|
if (!bitstreams[j].getFormat().isInternal())
|
|
{
|
|
// Bitstream is not of an internal format
|
|
bitstreamList.add(bitstreams[j]);
|
|
}
|
|
}
|
|
}
|
|
|
|
return bitstreamList.toArray(new Bitstream[bitstreamList.size()]);
|
|
}
|
|
|
|
/**
|
|
* Remove just the DSpace license from an item This is useful to update the
|
|
* current DSpace license, in case the user must accept the DSpace license
|
|
* again (either the item was rejected, or resumed after saving)
|
|
* <p>
|
|
* This method is used by the org.dspace.submit.step.LicenseStep class
|
|
*
|
|
* @throws SQLException
|
|
* @throws AuthorizeException
|
|
* @throws IOException
|
|
*/
|
|
public void removeDSpaceLicense() throws SQLException, AuthorizeException,
|
|
IOException
|
|
{
|
|
// get all bundles with name "LICENSE" (these are the DSpace license
|
|
// bundles)
|
|
Bundle[] bunds = getBundles("LICENSE");
|
|
|
|
for (int i = 0; i < bunds.length; i++)
|
|
{
|
|
// FIXME: probably serious troubles with Authorizations
|
|
// fix by telling system not to check authorization?
|
|
removeBundle(bunds[i]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Remove all licenses from an item - it was rejected
|
|
*
|
|
* @throws SQLException
|
|
* @throws AuthorizeException
|
|
* @throws IOException
|
|
*/
|
|
public void removeLicenses() throws SQLException, AuthorizeException,
|
|
IOException
|
|
{
|
|
// Find the License format
|
|
BitstreamFormat bf = BitstreamFormat.findByShortDescription(ourContext,
|
|
"License");
|
|
int licensetype = bf.getID();
|
|
|
|
// search through bundles, looking for bitstream type license
|
|
Bundle[] bunds = getBundles();
|
|
|
|
for (int i = 0; i < bunds.length; i++)
|
|
{
|
|
boolean removethisbundle = false;
|
|
|
|
Bitstream[] bits = bunds[i].getBitstreams();
|
|
|
|
for (int j = 0; j < bits.length; j++)
|
|
{
|
|
BitstreamFormat bft = bits[j].getFormat();
|
|
|
|
if (bft.getID() == licensetype)
|
|
{
|
|
removethisbundle = true;
|
|
}
|
|
}
|
|
|
|
// probably serious troubles with Authorizations
|
|
// fix by telling system not to check authorization?
|
|
if (removethisbundle)
|
|
{
|
|
removeBundle(bunds[i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Update the item "in archive" flag and Dublin Core metadata in the
|
|
* database
|
|
*
|
|
* @throws SQLException
|
|
* @throws AuthorizeException
|
|
*/
|
|
public void update() throws SQLException, AuthorizeException
|
|
{
|
|
// Check authorisation
|
|
// only do write authorization if user is not an editor
|
|
if (!canEdit())
|
|
{
|
|
AuthorizeManager.authorizeAction(ourContext, this, Constants.WRITE);
|
|
}
|
|
|
|
log.info(LogManager.getHeader(ourContext, "update_item", "item_id="
|
|
+ getID()));
|
|
|
|
// Set sequence IDs for bitstreams in item
|
|
int sequence = 0;
|
|
Bundle[] bunds = getBundles();
|
|
|
|
// find the highest current sequence number
|
|
for (int i = 0; i < bunds.length; i++)
|
|
{
|
|
Bitstream[] streams = bunds[i].getBitstreams();
|
|
|
|
for (int k = 0; k < streams.length; k++)
|
|
{
|
|
if (streams[k].getSequenceID() > sequence)
|
|
{
|
|
sequence = streams[k].getSequenceID();
|
|
}
|
|
}
|
|
}
|
|
|
|
// start sequencing bitstreams without sequence IDs
|
|
sequence++;
|
|
|
|
for (int i = 0; i < bunds.length; i++)
|
|
{
|
|
Bitstream[] streams = bunds[i].getBitstreams();
|
|
|
|
for (int k = 0; k < streams.length; k++)
|
|
{
|
|
if (streams[k].getSequenceID() < 0)
|
|
{
|
|
streams[k].setSequenceID(sequence);
|
|
sequence++;
|
|
streams[k].update();
|
|
modified = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Map counting number of values for each element/qualifier.
|
|
// Keys are Strings: "element" or "element.qualifier"
|
|
// Values are Integers indicating number of values written for a
|
|
// element/qualifier
|
|
Map<String,Integer> elementCount = new HashMap<String,Integer>();
|
|
|
|
// Redo Dublin Core if it's changed
|
|
if (dublinCoreChanged)
|
|
{
|
|
dublinCoreChanged = false;
|
|
|
|
// Arrays to store the working information required
|
|
int[] placeNum = new int[getMetadata().size()];
|
|
boolean[] storedDC = new boolean[getMetadata().size()];
|
|
MetadataField[] dcFields = new MetadataField[getMetadata().size()];
|
|
|
|
// Work out the place numbers for the in memory DC
|
|
for (int dcIdx = 0; dcIdx < getMetadata().size(); dcIdx++)
|
|
{
|
|
DCValue dcv = getMetadata().get(dcIdx);
|
|
|
|
// Work out the place number for ordering
|
|
int current = 0;
|
|
|
|
// Key into map is "element" or "element.qualifier"
|
|
String key = dcv.element + ((dcv.qualifier == null) ? "" : ("." + dcv.qualifier));
|
|
|
|
Integer currentInteger = elementCount.get(key);
|
|
if (currentInteger != null)
|
|
{
|
|
current = currentInteger.intValue();
|
|
}
|
|
|
|
current++;
|
|
elementCount.put(key, Integer.valueOf(current));
|
|
|
|
// Store the calculated place number, reset the stored flag, and cache the metadatafield
|
|
placeNum[dcIdx] = current;
|
|
storedDC[dcIdx] = false;
|
|
dcFields[dcIdx] = getMetadataField(dcv);
|
|
if (dcFields[dcIdx] == null)
|
|
{
|
|
// Bad DC field, log and throw exception
|
|
log.warn(LogManager
|
|
.getHeader(ourContext, "bad_dc",
|
|
"Bad DC field. schema="+dcv.schema
|
|
+ ", element: \""
|
|
+ ((dcv.element == null) ? "null"
|
|
: dcv.element)
|
|
+ "\" qualifier: \""
|
|
+ ((dcv.qualifier == null) ? "null"
|
|
: dcv.qualifier)
|
|
+ "\" value: \""
|
|
+ ((dcv.value == null) ? "null"
|
|
: dcv.value) + "\""));
|
|
|
|
throw new SQLException("bad_dublin_core "
|
|
+ "schema="+dcv.schema+", "
|
|
+ dcv.element
|
|
+ " " + dcv.qualifier);
|
|
}
|
|
}
|
|
|
|
// Now the precalculations are done, iterate through the existing metadata
|
|
// looking for matches
|
|
TableRowIterator tri = retrieveMetadata();
|
|
if (tri != null)
|
|
{
|
|
try
|
|
{
|
|
while (tri.hasNext())
|
|
{
|
|
TableRow tr = tri.next();
|
|
// Assume that we will remove this row, unless we get a match
|
|
boolean removeRow = true;
|
|
|
|
// Go through the in-memory metadata, unless we've already decided to keep this row
|
|
for (int dcIdx = 0; dcIdx < getMetadata().size() && removeRow; dcIdx++)
|
|
{
|
|
// Only process if this metadata has not already been matched to something in the DB
|
|
if (!storedDC[dcIdx])
|
|
{
|
|
boolean matched = true;
|
|
DCValue dcv = getMetadata().get(dcIdx);
|
|
|
|
// Check the metadata field is the same
|
|
if (matched && dcFields[dcIdx].getFieldID() != tr.getIntColumn("metadata_field_id"))
|
|
{
|
|
matched = false;
|
|
}
|
|
|
|
// Check the place is the same
|
|
if (matched && placeNum[dcIdx] != tr.getIntColumn("place"))
|
|
{
|
|
matched = false;
|
|
}
|
|
|
|
// Check the text is the same
|
|
if (matched)
|
|
{
|
|
String text = tr.getStringColumn("text_value");
|
|
if (dcv.value == null && text == null)
|
|
{
|
|
matched = true;
|
|
}
|
|
else if (dcv.value != null && dcv.value.equals(text))
|
|
{
|
|
matched = true;
|
|
}
|
|
else
|
|
{
|
|
matched = false;
|
|
}
|
|
}
|
|
|
|
// Check the language is the same
|
|
if (matched)
|
|
{
|
|
String lang = tr.getStringColumn("text_lang");
|
|
if (dcv.language == null && lang == null)
|
|
{
|
|
matched = true;
|
|
}
|
|
else if (dcv.language != null && dcv.language.equals(lang))
|
|
{
|
|
matched = true;
|
|
}
|
|
else
|
|
{
|
|
matched = false;
|
|
}
|
|
}
|
|
|
|
// check that authority and confidence match
|
|
if (matched)
|
|
{
|
|
String auth = tr.getStringColumn("authority");
|
|
int conf = tr.getIntColumn("confidence");
|
|
if (!((dcv.authority == null && auth == null) ||
|
|
(dcv.authority != null && auth != null && dcv.authority.equals(auth))
|
|
&& dcv.confidence == conf))
|
|
{
|
|
matched = false;
|
|
}
|
|
}
|
|
|
|
// If the db record is identical to the in memory values
|
|
if (matched)
|
|
{
|
|
// Flag that the metadata is already in the DB
|
|
storedDC[dcIdx] = true;
|
|
|
|
// Flag that we are not going to remove the row
|
|
removeRow = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If after processing all the metadata values, we didn't find a match
|
|
// delete this row from the DB
|
|
if (removeRow)
|
|
{
|
|
DatabaseManager.delete(ourContext, tr);
|
|
dublinCoreChanged = true;
|
|
modified = true;
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
tri.close();
|
|
}
|
|
}
|
|
|
|
// Add missing in-memory DC
|
|
for (int dcIdx = 0; dcIdx < getMetadata().size(); dcIdx++)
|
|
{
|
|
// Only write values that are not already in the db
|
|
if (!storedDC[dcIdx])
|
|
{
|
|
DCValue dcv = getMetadata().get(dcIdx);
|
|
|
|
// Write DCValue
|
|
MetadataValue metadata = new MetadataValue();
|
|
metadata.setItemId(getID());
|
|
metadata.setFieldId(dcFields[dcIdx].getFieldID());
|
|
metadata.setValue(dcv.value);
|
|
metadata.setLanguage(dcv.language);
|
|
metadata.setPlace(placeNum[dcIdx]);
|
|
metadata.setAuthority(dcv.authority);
|
|
metadata.setConfidence(dcv.confidence);
|
|
metadata.create(ourContext);
|
|
dublinCoreChanged = true;
|
|
modified = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (dublinCoreChanged || modified)
|
|
{
|
|
// Set the last modified date
|
|
itemRow.setColumn("last_modified", new Date());
|
|
|
|
// Make sure that withdrawn and in_archive are non-null
|
|
if (itemRow.isColumnNull("in_archive"))
|
|
{
|
|
itemRow.setColumn("in_archive", false);
|
|
}
|
|
|
|
if (itemRow.isColumnNull("withdrawn"))
|
|
{
|
|
itemRow.setColumn("withdrawn", false);
|
|
}
|
|
|
|
if (itemRow.isColumnNull("discoverable"))
|
|
{
|
|
itemRow.setColumn("discoverable", false);
|
|
}
|
|
|
|
|
|
DatabaseManager.update(ourContext, itemRow);
|
|
|
|
if (dublinCoreChanged)
|
|
{
|
|
ourContext.addEvent(new Event(Event.MODIFY_METADATA, Constants.ITEM, getID(), getDetails()));
|
|
clearDetails();
|
|
dublinCoreChanged = false;
|
|
}
|
|
|
|
ourContext.addEvent(new Event(Event.MODIFY, Constants.ITEM, getID(), null));
|
|
modified = false;
|
|
}
|
|
}
|
|
|
|
private transient MetadataField[] allMetadataFields = null;
|
|
private MetadataField getMetadataField(DCValue dcv) throws SQLException, AuthorizeException
|
|
{
|
|
if (allMetadataFields == null)
|
|
{
|
|
allMetadataFields = MetadataField.findAll(ourContext);
|
|
}
|
|
|
|
if (allMetadataFields != null)
|
|
{
|
|
int schemaID = getMetadataSchemaID(dcv);
|
|
for (MetadataField field : allMetadataFields)
|
|
{
|
|
if (field.getSchemaID() == schemaID &&
|
|
StringUtils.equals(field.getElement(), dcv.element) &&
|
|
StringUtils.equals(field.getQualifier(), dcv.qualifier))
|
|
{
|
|
return field;
|
|
}
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private int getMetadataSchemaID(DCValue dcv) throws SQLException
|
|
{
|
|
int schemaID;
|
|
MetadataSchema schema = MetadataSchema.find(ourContext,dcv.schema);
|
|
if (schema == null)
|
|
{
|
|
schemaID = MetadataSchema.DC_SCHEMA_ID;
|
|
}
|
|
else
|
|
{
|
|
schemaID = schema.getSchemaID();
|
|
}
|
|
return schemaID;
|
|
}
|
|
|
|
/**
|
|
* Withdraw the item from the archive. It is kept in place, and the content
|
|
* and metadata are not deleted, but it is not publicly accessible.
|
|
*
|
|
* @throws SQLException
|
|
* @throws AuthorizeException
|
|
* @throws IOException
|
|
*/
|
|
public void withdraw() throws SQLException, AuthorizeException, IOException
|
|
{
|
|
// Check permission. User either has to have REMOVE on owning collection
|
|
// or be COLLECTION_EDITOR of owning collection
|
|
AuthorizeUtil.authorizeWithdrawItem(ourContext, this);
|
|
|
|
String timestamp = DCDate.getCurrent().toString();
|
|
|
|
// Add suitable provenance - includes user, date, collections +
|
|
// bitstream checksums
|
|
EPerson e = ourContext.getCurrentUser();
|
|
|
|
// Build some provenance data while we're at it.
|
|
StringBuilder prov = new StringBuilder();
|
|
|
|
prov.append("Item withdrawn by ").append(e.getFullName()).append(" (")
|
|
.append(e.getEmail()).append(") on ").append(timestamp).append("\n")
|
|
.append("Item was in collections:\n");
|
|
|
|
Collection[] colls = getCollections();
|
|
|
|
for (int i = 0; i < colls.length; i++)
|
|
{
|
|
prov.append(colls[i].getMetadata("name")).append(" (ID: ").append(colls[i].getID()).append(")\n");
|
|
}
|
|
|
|
// Set withdrawn flag. timestamp will be set; last_modified in update()
|
|
itemRow.setColumn("withdrawn", true);
|
|
|
|
// in_archive flag is now false
|
|
itemRow.setColumn("in_archive", false);
|
|
|
|
prov.append(InstallItem.getBitstreamProvenanceMessage(this));
|
|
|
|
addDC("description", "provenance", "en", prov.toString());
|
|
|
|
// Update item in DB
|
|
update();
|
|
|
|
ourContext.addEvent(new Event(Event.MODIFY, Constants.ITEM, getID(), "WITHDRAW"));
|
|
|
|
// remove all authorization policies, saving the custom ones
|
|
AuthorizeManager.removeAllPoliciesByDSOAndTypeNotEqualsTo(ourContext, this, ResourcePolicy.TYPE_CUSTOM);
|
|
|
|
// Write log
|
|
log.info(LogManager.getHeader(ourContext, "withdraw_item", "user="
|
|
+ e.getEmail() + ",item_id=" + getID()));
|
|
}
|
|
|
|
/**
|
|
* Reinstate a withdrawn item
|
|
*
|
|
* @throws SQLException
|
|
* @throws AuthorizeException
|
|
* @throws IOException
|
|
*/
|
|
public void reinstate() throws SQLException, AuthorizeException,
|
|
IOException
|
|
{
|
|
// check authorization
|
|
AuthorizeUtil.authorizeReinstateItem(ourContext, this);
|
|
|
|
String timestamp = DCDate.getCurrent().toString();
|
|
|
|
// Check permission. User must have ADD on all collections.
|
|
// Build some provenance data while we're at it.
|
|
Collection[] colls = getCollections();
|
|
|
|
// Add suitable provenance - includes user, date, collections +
|
|
// bitstream checksums
|
|
EPerson e = ourContext.getCurrentUser();
|
|
StringBuilder prov = new StringBuilder();
|
|
prov.append("Item reinstated by ").append(e.getFullName()).append(" (")
|
|
.append(e.getEmail()).append(") on ").append(timestamp).append("\n")
|
|
.append("Item was in collections:\n");
|
|
|
|
for (int i = 0; i < colls.length; i++)
|
|
{
|
|
prov.append(colls[i].getMetadata("name")).append(" (ID: ").append(colls[i].getID()).append(")\n");
|
|
}
|
|
|
|
// Clear withdrawn flag
|
|
itemRow.setColumn("withdrawn", false);
|
|
|
|
// in_archive flag is now true
|
|
itemRow.setColumn("in_archive", true);
|
|
|
|
// Add suitable provenance - includes user, date, collections +
|
|
// bitstream checksums
|
|
prov.append(InstallItem.getBitstreamProvenanceMessage(this));
|
|
|
|
addDC("description", "provenance", "en", prov.toString());
|
|
|
|
// Update item in DB
|
|
update();
|
|
|
|
ourContext.addEvent(new Event(Event.MODIFY, Constants.ITEM, getID(), "REINSTATE"));
|
|
|
|
// authorization policies
|
|
if (colls.length > 0)
|
|
{
|
|
// FIXME: not multiple inclusion friendly - just apply access
|
|
// policies from first collection
|
|
// remove the item's policies and replace them with
|
|
// the defaults from the collection
|
|
inheritCollectionDefaultPolicies(colls[0]);
|
|
}
|
|
|
|
// Write log
|
|
log.info(LogManager.getHeader(ourContext, "reinstate_item", "user="
|
|
+ e.getEmail() + ",item_id=" + getID()));
|
|
}
|
|
|
|
/**
|
|
* Delete (expunge) the item. Bundles and bitstreams are also deleted if
|
|
* they are not also included in another item. The Dublin Core metadata is
|
|
* deleted.
|
|
*
|
|
* @throws SQLException
|
|
* @throws AuthorizeException
|
|
* @throws IOException
|
|
*/
|
|
void delete() throws SQLException, AuthorizeException, IOException
|
|
{
|
|
// Check authorisation here. If we don't, it may happen that we remove the
|
|
// metadata but when getting to the point of removing the bundles we get an exception
|
|
// leaving the database in an inconsistent state
|
|
AuthorizeManager.authorizeAction(ourContext, this, Constants.REMOVE);
|
|
|
|
ourContext.addEvent(new Event(Event.DELETE, Constants.ITEM, getID(), getHandle()));
|
|
|
|
log.info(LogManager.getHeader(ourContext, "delete_item", "item_id="
|
|
+ getID()));
|
|
|
|
// Remove from cache
|
|
ourContext.removeCached(this, getID());
|
|
|
|
// 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
|
|
**
|
|
** NB Do not check to see if the item is archived - withdrawn /
|
|
** non-archived items may still be tracked in some browse tables
|
|
** for administrative purposes, and these need to be removed.
|
|
**/
|
|
// FIXME: there is an exception handling problem here
|
|
try
|
|
{
|
|
// Remove from indices
|
|
IndexBrowse ib = new IndexBrowse(ourContext);
|
|
ib.itemRemoved(this);
|
|
}
|
|
catch (BrowseException e)
|
|
{
|
|
log.error("caught exception: ", e);
|
|
throw new SQLException(e.getMessage(), e);
|
|
}
|
|
|
|
// Delete the Dublin Core
|
|
removeMetadataFromDatabase();
|
|
|
|
// Remove bundles
|
|
Bundle[] bunds = getBundles();
|
|
|
|
for (int i = 0; i < bunds.length; i++)
|
|
{
|
|
removeBundle(bunds[i]);
|
|
}
|
|
|
|
// remove all of our authorization policies
|
|
AuthorizeManager.removeAllPolicies(ourContext, this);
|
|
|
|
// Remove any Handle
|
|
HandleManager.unbindHandle(ourContext, this);
|
|
|
|
// Finally remove item row
|
|
DatabaseManager.delete(ourContext, itemRow);
|
|
}
|
|
|
|
/**
|
|
* Remove item and all its sub-structure from the context cache.
|
|
* Useful in batch processes where a single context has a long,
|
|
* multi-item lifespan
|
|
*/
|
|
public void decache() throws SQLException
|
|
{
|
|
// Remove item and it's submitter from cache
|
|
ourContext.removeCached(this, getID());
|
|
if (submitter != null)
|
|
{
|
|
ourContext.removeCached(submitter, submitter.getID());
|
|
}
|
|
// Remove bundles & bitstreams from cache if they have been loaded
|
|
if (bundles != null)
|
|
{
|
|
Bundle[] bunds = getBundles();
|
|
for (int i = 0; i < bunds.length; i++)
|
|
{
|
|
ourContext.removeCached(bunds[i], bunds[i].getID());
|
|
Bitstream[] bitstreams = bunds[i].getBitstreams();
|
|
for (int j = 0; j < bitstreams.length; j++)
|
|
{
|
|
ourContext.removeCached(bitstreams[j], bitstreams[j].getID());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return <code>true</code> if <code>other</code> is the same Item as
|
|
* this object, <code>false</code> otherwise
|
|
*
|
|
* @param obj
|
|
* object to compare to
|
|
* @return <code>true</code> if object passed in represents the same item
|
|
* as this object
|
|
*/
|
|
@Override
|
|
public boolean equals(Object obj)
|
|
{
|
|
if (obj == null)
|
|
{
|
|
return false;
|
|
}
|
|
if (getClass() != obj.getClass())
|
|
{
|
|
return false;
|
|
}
|
|
final Item other = (Item) obj;
|
|
if (this.getType() != other.getType())
|
|
{
|
|
return false;
|
|
}
|
|
if (this.getID() != other.getID())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
@Override
|
|
public int hashCode()
|
|
{
|
|
int hash = 5;
|
|
hash = 71 * hash + (this.itemRow != null ? this.itemRow.hashCode() : 0);
|
|
return hash;
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
* Return true if this Collection 'owns' this item
|
|
*
|
|
* @param c
|
|
* Collection
|
|
* @return true if this Collection owns this item
|
|
*/
|
|
public boolean isOwningCollection(Collection c)
|
|
{
|
|
int owner_id = itemRow.getIntColumn("owning_collection");
|
|
|
|
if (c.getID() == owner_id)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// not the owner
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Utility method to remove all descriptive metadata associated with the item from
|
|
* the database (regardless of in-memory version)
|
|
*
|
|
* @throws SQLException
|
|
*/
|
|
private void removeMetadataFromDatabase() throws SQLException
|
|
{
|
|
DatabaseManager.updateQuery(ourContext,
|
|
"DELETE FROM MetadataValue WHERE item_id= ? ",
|
|
getID());
|
|
}
|
|
|
|
/**
|
|
* return type found in Constants
|
|
*
|
|
* @return int Constants.ITEM
|
|
*/
|
|
public int getType()
|
|
{
|
|
return Constants.ITEM;
|
|
}
|
|
|
|
/**
|
|
* remove all of the policies for item and replace them with a new list of
|
|
* policies
|
|
*
|
|
* @param newpolicies -
|
|
* this will be all of the new policies for the item and its
|
|
* contents
|
|
* @throws SQLException
|
|
* @throws AuthorizeException
|
|
*/
|
|
public void replaceAllItemPolicies(List<ResourcePolicy> newpolicies) throws SQLException,
|
|
AuthorizeException
|
|
{
|
|
// remove all our policies, add new ones
|
|
AuthorizeManager.removeAllPolicies(ourContext, this);
|
|
AuthorizeManager.addPolicies(ourContext, newpolicies, this);
|
|
}
|
|
|
|
/**
|
|
* remove all of the policies for item's bitstreams and bundles and replace
|
|
* them with a new list of policies
|
|
*
|
|
* @param newpolicies -
|
|
* this will be all of the new policies for the bundle and
|
|
* bitstream contents
|
|
* @throws SQLException
|
|
* @throws AuthorizeException
|
|
*/
|
|
public void replaceAllBitstreamPolicies(List<ResourcePolicy> newpolicies)
|
|
throws SQLException, AuthorizeException
|
|
{
|
|
// remove all policies from bundles, add new ones
|
|
// Remove bundles
|
|
Bundle[] bunds = getBundles();
|
|
|
|
for (int i = 0; i < bunds.length; i++)
|
|
{
|
|
Bundle mybundle = bunds[i];
|
|
mybundle.replaceAllBitstreamPolicies(newpolicies);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* remove all of the policies for item's bitstreams and bundles that belong
|
|
* to a given Group
|
|
*
|
|
* @param g
|
|
* Group referenced by policies that needs to be removed
|
|
* @throws SQLException
|
|
*/
|
|
public void removeGroupPolicies(Group g) throws SQLException
|
|
{
|
|
// remove Group's policies from Item
|
|
AuthorizeManager.removeGroupPolicies(ourContext, this, g);
|
|
|
|
// remove all policies from bundles
|
|
Bundle[] bunds = getBundles();
|
|
|
|
for (int i = 0; i < bunds.length; i++)
|
|
{
|
|
Bundle mybundle = bunds[i];
|
|
|
|
Bitstream[] bs = mybundle.getBitstreams();
|
|
|
|
for (int j = 0; j < bs.length; j++)
|
|
{
|
|
// remove bitstream policies
|
|
AuthorizeManager.removeGroupPolicies(ourContext, bs[j], g);
|
|
}
|
|
|
|
// change bundle policies
|
|
AuthorizeManager.removeGroupPolicies(ourContext, mybundle, g);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* remove all policies on an item and its contents, and replace them with
|
|
* the DEFAULT_ITEM_READ and DEFAULT_BITSTREAM_READ policies belonging to
|
|
* the collection.
|
|
*
|
|
* @param c
|
|
* Collection
|
|
* @throws java.sql.SQLException
|
|
* if an SQL error or if no default policies found. It's a bit
|
|
* draconian, but default policies must be enforced.
|
|
* @throws AuthorizeException
|
|
*/
|
|
public void inheritCollectionDefaultPolicies(Collection c)
|
|
throws java.sql.SQLException, AuthorizeException
|
|
{
|
|
adjustItemPolicies(c);
|
|
adjustBundleBitstreamPolicies(c);
|
|
|
|
log.debug(LogManager.getHeader(ourContext, "item_inheritCollectionDefaultPolicies",
|
|
"item_id=" + getID()));
|
|
}
|
|
|
|
public void adjustBundleBitstreamPolicies(Collection c) throws SQLException, AuthorizeException {
|
|
|
|
List<ResourcePolicy> defaultCollectionPolicies = AuthorizeManager.getPoliciesActionFilter(ourContext, c, Constants.DEFAULT_BITSTREAM_READ);
|
|
|
|
if (defaultCollectionPolicies.size() < 1){
|
|
throw new SQLException("Collection " + c.getID()
|
|
+ " (" + c.getHandle() + ")"
|
|
+ " has no default bitstream READ policies");
|
|
}
|
|
|
|
// remove all policies from bundles, add new ones
|
|
// Remove bundles
|
|
Bundle[] bunds = getBundles();
|
|
for (int i = 0; i < bunds.length; i++){
|
|
Bundle mybundle = bunds[i];
|
|
|
|
// if come from InstallItem: remove all submission/workflow policies
|
|
AuthorizeManager.removeAllPoliciesByDSOAndType(ourContext, mybundle, ResourcePolicy.TYPE_SUBMISSION);
|
|
AuthorizeManager.removeAllPoliciesByDSOAndType(ourContext, mybundle, ResourcePolicy.TYPE_WORKFLOW);
|
|
|
|
List<ResourcePolicy> policiesBundleToAdd = filterPoliciesToAdd(defaultCollectionPolicies, mybundle);
|
|
AuthorizeManager.addPolicies(ourContext, policiesBundleToAdd, mybundle);
|
|
|
|
for(Bitstream bitstream : mybundle.getBitstreams()){
|
|
// if come from InstallItem: remove all submission/workflow policies
|
|
AuthorizeManager.removeAllPoliciesByDSOAndType(ourContext, bitstream, ResourcePolicy.TYPE_SUBMISSION);
|
|
AuthorizeManager.removeAllPoliciesByDSOAndType(ourContext, bitstream, ResourcePolicy.TYPE_WORKFLOW);
|
|
|
|
List<ResourcePolicy> policiesBitstreamToAdd = filterPoliciesToAdd(defaultCollectionPolicies, bitstream);
|
|
AuthorizeManager.addPolicies(ourContext, policiesBitstreamToAdd, bitstream);
|
|
}
|
|
}
|
|
}
|
|
|
|
public void adjustItemPolicies(Collection c) throws SQLException, AuthorizeException {
|
|
// read collection's default READ policies
|
|
List<ResourcePolicy> defaultCollectionPolicies = AuthorizeManager.getPoliciesActionFilter(ourContext, c, Constants.DEFAULT_ITEM_READ);
|
|
|
|
// MUST have default policies
|
|
if (defaultCollectionPolicies.size() < 1)
|
|
{
|
|
throw new SQLException("Collection " + c.getID()
|
|
+ " (" + c.getHandle() + ")"
|
|
+ " has no default item READ policies");
|
|
}
|
|
|
|
// if come from InstallItem: remove all submission/workflow policies
|
|
AuthorizeManager.removeAllPoliciesByDSOAndType(ourContext, this, ResourcePolicy.TYPE_SUBMISSION);
|
|
AuthorizeManager.removeAllPoliciesByDSOAndType(ourContext, this, ResourcePolicy.TYPE_WORKFLOW);
|
|
|
|
// add default policies only if not already in place
|
|
List<ResourcePolicy> policiesToAdd = filterPoliciesToAdd(defaultCollectionPolicies, this);
|
|
AuthorizeManager.addPolicies(ourContext, policiesToAdd, this);
|
|
}
|
|
|
|
private List<ResourcePolicy> filterPoliciesToAdd(List<ResourcePolicy> defaultCollectionPolicies, DSpaceObject dso) throws SQLException {
|
|
List<ResourcePolicy> policiesToAdd = new ArrayList<ResourcePolicy>();
|
|
for (ResourcePolicy rp : defaultCollectionPolicies){
|
|
rp.setAction(Constants.READ);
|
|
// if an identical policy is already in place don't add it
|
|
if(!AuthorizeManager.isAnIdenticalPolicyAlreadyInPlace(ourContext, dso, rp)){
|
|
rp.setRpType(ResourcePolicy.TYPE_INHERITED);
|
|
policiesToAdd.add(rp);
|
|
}
|
|
}
|
|
return policiesToAdd;
|
|
}
|
|
|
|
/**
|
|
* Moves the item from one collection to another one
|
|
*
|
|
* @throws SQLException
|
|
* @throws AuthorizeException
|
|
* @throws IOException
|
|
*/
|
|
public void move (Collection from, Collection to) throws SQLException, AuthorizeException, IOException
|
|
{
|
|
// Use the normal move method, and default to not inherit permissions
|
|
this.move(from, to, false);
|
|
}
|
|
|
|
/**
|
|
* Moves the item from one collection to another one
|
|
*
|
|
* @throws SQLException
|
|
* @throws AuthorizeException
|
|
* @throws IOException
|
|
*/
|
|
public void move (Collection from, Collection to, boolean inheritDefaultPolicies) throws SQLException, AuthorizeException, IOException
|
|
{
|
|
// Check authorisation on the item before that the move occur
|
|
// otherwise we will need edit permission on the "target collection" to archive our goal
|
|
// only do write authorization if user is not an editor
|
|
if (!canEdit())
|
|
{
|
|
AuthorizeManager.authorizeAction(ourContext, this, Constants.WRITE);
|
|
}
|
|
|
|
// Move the Item from one Collection to the other
|
|
to.addItem(this);
|
|
from.removeItem(this);
|
|
|
|
// If we are moving from the owning collection, update that too
|
|
if (isOwningCollection(from))
|
|
{
|
|
// Update the owning collection
|
|
log.info(LogManager.getHeader(ourContext, "move_item",
|
|
"item_id=" + getID() + ", from " +
|
|
"collection_id=" + from.getID() + " to " +
|
|
"collection_id=" + to.getID()));
|
|
setOwningCollection(to);
|
|
|
|
// If applicable, update the item policies
|
|
if (inheritDefaultPolicies)
|
|
{
|
|
log.info(LogManager.getHeader(ourContext, "move_item",
|
|
"Updating item with inherited policies"));
|
|
inheritCollectionDefaultPolicies(to);
|
|
}
|
|
|
|
// Update the item
|
|
ourContext.turnOffAuthorisationSystem();
|
|
update();
|
|
ourContext.restoreAuthSystemState();
|
|
}
|
|
else
|
|
{
|
|
// Although we haven't actually updated anything within the item
|
|
// we'll tell the event system that it has, so that any consumers that
|
|
// care about the structure of the repository can take account of the move
|
|
|
|
// Note that updating the owning collection above will have the same effect,
|
|
// so we only do this here if the owning collection hasn't changed.
|
|
|
|
ourContext.addEvent(new Event(Event.MODIFY, Constants.ITEM, getID(), null));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check the bundle ORIGINAL to see if there are any uploaded files
|
|
*
|
|
* @return true if there is a bundle named ORIGINAL with one or more
|
|
* bitstreams inside
|
|
* @throws SQLException
|
|
*/
|
|
public boolean hasUploadedFiles() throws SQLException
|
|
{
|
|
Bundle[] bundles = getBundles("ORIGINAL");
|
|
if (bundles.length == 0)
|
|
{
|
|
// if no ORIGINAL bundle,
|
|
// return false that there is no file!
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
Bitstream[] bitstreams = bundles[0].getBitstreams();
|
|
if (bitstreams.length == 0)
|
|
{
|
|
// no files in ORIGINAL bundle!
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Get the collections this item is not in.
|
|
*
|
|
* @return the collections this item is not in, if any.
|
|
* @throws SQLException
|
|
*/
|
|
public Collection[] getCollectionsNotLinked() throws SQLException
|
|
{
|
|
Collection[] allCollections = Collection.findAll(ourContext);
|
|
Collection[] linkedCollections = getCollections();
|
|
Collection[] notLinkedCollections = new Collection[allCollections.length - linkedCollections.length];
|
|
|
|
if ((allCollections.length - linkedCollections.length) == 0)
|
|
{
|
|
return notLinkedCollections;
|
|
}
|
|
|
|
int i = 0;
|
|
|
|
for (Collection collection : allCollections)
|
|
{
|
|
boolean alreadyLinked = false;
|
|
|
|
for (Collection linkedCommunity : linkedCollections)
|
|
{
|
|
if (collection.getID() == linkedCommunity.getID())
|
|
{
|
|
alreadyLinked = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!alreadyLinked)
|
|
{
|
|
notLinkedCollections[i++] = collection;
|
|
}
|
|
}
|
|
|
|
return notLinkedCollections;
|
|
}
|
|
|
|
/**
|
|
* return TRUE if context's user can edit item, false otherwise
|
|
*
|
|
* @return boolean true = current user can edit item
|
|
* @throws SQLException
|
|
*/
|
|
public boolean canEdit() throws java.sql.SQLException
|
|
{
|
|
// can this person write to the item?
|
|
if (AuthorizeManager.authorizeActionBoolean(ourContext, this,
|
|
Constants.WRITE))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// is this collection not yet created, and an item template is created
|
|
if (getOwningCollection() == null)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
// is this person an COLLECTION_EDITOR for the owning collection?
|
|
if (getOwningCollection().canEditBoolean(false))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
public String getName()
|
|
{
|
|
DCValue t[] = getMetadata("dc", "title", null, Item.ANY);
|
|
return (t.length >= 1) ? t[0].value : null;
|
|
}
|
|
|
|
/**
|
|
* Returns an iterator of Items possessing the passed metadata field, or only
|
|
* those matching the passed value, if value is not Item.ANY
|
|
*
|
|
* @param context DSpace context object
|
|
* @param schema metadata field schema
|
|
* @param element metadata field element
|
|
* @param qualifier metadata field qualifier
|
|
* @param value field value or Item.ANY to match any value
|
|
* @return an iterator over the items matching that authority value
|
|
* @throws SQLException, AuthorizeException, IOException
|
|
*
|
|
*/
|
|
public static ItemIterator findByMetadataField(Context context,
|
|
String schema, String element, String qualifier, String value)
|
|
throws SQLException, AuthorizeException, IOException
|
|
{
|
|
MetadataSchema mds = MetadataSchema.find(context, schema);
|
|
if (mds == null)
|
|
{
|
|
throw new IllegalArgumentException("No such metadata schema: " + schema);
|
|
}
|
|
MetadataField mdf = MetadataField.findByElement(context, mds.getSchemaID(), element, qualifier);
|
|
if (mdf == null)
|
|
{
|
|
throw new IllegalArgumentException(
|
|
"No such metadata field: schema=" + schema + ", element=" + element + ", qualifier=" + qualifier);
|
|
}
|
|
|
|
String query = "SELECT item.* FROM metadatavalue,item WHERE item.in_archive='1' "+
|
|
"AND item.item_id = metadatavalue.item_id AND metadata_field_id = ?";
|
|
TableRowIterator rows = null;
|
|
if (Item.ANY.equals(value))
|
|
{
|
|
rows = DatabaseManager.queryTable(context, "item", query, mdf.getFieldID());
|
|
}
|
|
else
|
|
{
|
|
query += " AND metadatavalue.text_value = ?";
|
|
rows = DatabaseManager.queryTable(context, "item", query, mdf.getFieldID(), value);
|
|
}
|
|
return new ItemIterator(context, rows);
|
|
}
|
|
|
|
public DSpaceObject getAdminObject(int action) throws SQLException
|
|
{
|
|
DSpaceObject adminObject = null;
|
|
Collection collection = getOwningCollection();
|
|
Community community = null;
|
|
if (collection != null)
|
|
{
|
|
Community[] communities = collection.getCommunities();
|
|
if (communities != null && communities.length > 0)
|
|
{
|
|
community = communities[0];
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// is a template item?
|
|
TableRow qResult = DatabaseManager.querySingle(ourContext,
|
|
"SELECT collection_id FROM collection " +
|
|
"WHERE template_item_id = ?",getID());
|
|
if (qResult != null)
|
|
{
|
|
collection = Collection.find(ourContext, qResult.getIntColumn("collection_id"));
|
|
Community[] communities = collection.getCommunities();
|
|
if (communities != null && communities.length > 0)
|
|
{
|
|
community = communities[0];
|
|
}
|
|
}
|
|
}
|
|
|
|
switch (action)
|
|
{
|
|
case Constants.ADD:
|
|
// ADD a cc license is less general than add a bitstream but we can't/won't
|
|
// add complex logic here to know if the ADD action on the item is required by a cc or
|
|
// a generic bitstream so simply we ignore it.. UI need to enforce the requirements.
|
|
if (AuthorizeConfiguration.canItemAdminPerformBitstreamCreation())
|
|
{
|
|
adminObject = this;
|
|
}
|
|
else if (AuthorizeConfiguration.canCollectionAdminPerformBitstreamCreation())
|
|
{
|
|
adminObject = collection;
|
|
}
|
|
else if (AuthorizeConfiguration.canCommunityAdminPerformBitstreamCreation())
|
|
{
|
|
adminObject = community;
|
|
}
|
|
break;
|
|
case Constants.REMOVE:
|
|
// see comments on ADD action, same things...
|
|
if (AuthorizeConfiguration.canItemAdminPerformBitstreamDeletion())
|
|
{
|
|
adminObject = this;
|
|
}
|
|
else if (AuthorizeConfiguration.canCollectionAdminPerformBitstreamDeletion())
|
|
{
|
|
adminObject = collection;
|
|
}
|
|
else if (AuthorizeConfiguration.canCommunityAdminPerformBitstreamDeletion())
|
|
{
|
|
adminObject = community;
|
|
}
|
|
break;
|
|
case Constants.DELETE:
|
|
if (getOwningCollection() != null)
|
|
{
|
|
if (AuthorizeConfiguration.canCollectionAdminPerformItemDeletion())
|
|
{
|
|
adminObject = collection;
|
|
}
|
|
else if (AuthorizeConfiguration.canCommunityAdminPerformItemDeletion())
|
|
{
|
|
adminObject = community;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (AuthorizeConfiguration.canCollectionAdminManageTemplateItem())
|
|
{
|
|
adminObject = collection;
|
|
}
|
|
else if (AuthorizeConfiguration.canCommunityAdminManageCollectionTemplateItem())
|
|
{
|
|
adminObject = community;
|
|
}
|
|
}
|
|
break;
|
|
case Constants.WRITE:
|
|
// if it is a template item we need to check the
|
|
// collection/community admin configuration
|
|
if (getOwningCollection() == null)
|
|
{
|
|
if (AuthorizeConfiguration.canCollectionAdminManageTemplateItem())
|
|
{
|
|
adminObject = collection;
|
|
}
|
|
else if (AuthorizeConfiguration.canCommunityAdminManageCollectionTemplateItem())
|
|
{
|
|
adminObject = community;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
adminObject = this;
|
|
}
|
|
break;
|
|
default:
|
|
adminObject = this;
|
|
break;
|
|
}
|
|
return adminObject;
|
|
}
|
|
|
|
public DSpaceObject getParentObject() throws SQLException
|
|
{
|
|
Collection ownCollection = getOwningCollection();
|
|
if (ownCollection != null)
|
|
{
|
|
return ownCollection;
|
|
}
|
|
else
|
|
{
|
|
// is a template item?
|
|
TableRow qResult = DatabaseManager.querySingle(ourContext,
|
|
"SELECT collection_id FROM collection " +
|
|
"WHERE template_item_id = ?",getID());
|
|
if (qResult != null)
|
|
{
|
|
return Collection.find(ourContext,qResult.getIntColumn("collection_id"));
|
|
}
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Find all the items in the archive with a given authority key value
|
|
* in the indicated metadata field.
|
|
*
|
|
* @param context DSpace context object
|
|
* @param schema metadata field schema
|
|
* @param element metadata field element
|
|
* @param qualifier metadata field qualifier
|
|
* @param value the value of authority key to look for
|
|
* @return an iterator over the items matching that authority value
|
|
* @throws SQLException, AuthorizeException, IOException
|
|
*/
|
|
public static ItemIterator findByAuthorityValue(Context context,
|
|
String schema, String element, String qualifier, String value)
|
|
throws SQLException, AuthorizeException, IOException
|
|
{
|
|
MetadataSchema mds = MetadataSchema.find(context, schema);
|
|
if (mds == null)
|
|
{
|
|
throw new IllegalArgumentException("No such metadata schema: " + schema);
|
|
}
|
|
MetadataField mdf = MetadataField.findByElement(context, mds.getSchemaID(), element, qualifier);
|
|
if (mdf == null)
|
|
{
|
|
throw new IllegalArgumentException("No such metadata field: schema=" + schema + ", element=" + element + ", qualifier=" + qualifier);
|
|
}
|
|
|
|
TableRowIterator rows = DatabaseManager.queryTable(context, "item",
|
|
"SELECT item.* FROM metadatavalue,item WHERE item.in_archive='1' "+
|
|
"AND item.item_id = metadatavalue.item_id AND metadata_field_id = ? AND authority = ?",
|
|
mdf.getFieldID(), value);
|
|
return new ItemIterator(context, rows);
|
|
}
|
|
|
|
|
|
private List<DCValue> getMetadata()
|
|
{
|
|
try
|
|
{
|
|
return dublinCore.get(ourContext, getID(), log);
|
|
}
|
|
catch (SQLException e)
|
|
{
|
|
log.error("Loading item - cannot load metadata");
|
|
}
|
|
|
|
return new ArrayList<DCValue>();
|
|
}
|
|
|
|
private void setMetadata(List<DCValue> metadata)
|
|
{
|
|
dublinCore.set(metadata);
|
|
dublinCoreChanged = true;
|
|
}
|
|
|
|
class MetadataCache
|
|
{
|
|
List<DCValue> metadata = null;
|
|
|
|
List<DCValue> get(Context c, int itemId, Logger log) throws SQLException
|
|
{
|
|
if (metadata == null)
|
|
{
|
|
metadata = new ArrayList<DCValue>();
|
|
|
|
// Get Dublin Core metadata
|
|
TableRowIterator tri = retrieveMetadata(itemId);
|
|
|
|
if (tri != null)
|
|
{
|
|
try
|
|
{
|
|
while (tri.hasNext())
|
|
{
|
|
TableRow resultRow = tri.next();
|
|
|
|
// Get the associated metadata field and schema information
|
|
int fieldID = resultRow.getIntColumn("metadata_field_id");
|
|
MetadataField field = MetadataField.find(c, fieldID);
|
|
|
|
if (field == null)
|
|
{
|
|
log.error("Loading item - cannot find metadata field " + fieldID);
|
|
}
|
|
else
|
|
{
|
|
MetadataSchema schema = MetadataSchema.find(c, field.getSchemaID());
|
|
if (schema == null)
|
|
{
|
|
log.error("Loading item - cannot find metadata schema " + field.getSchemaID() + ", field " + fieldID);
|
|
}
|
|
else
|
|
{
|
|
// Make a DCValue object
|
|
DCValue dcv = new DCValue();
|
|
dcv.element = field.getElement();
|
|
dcv.qualifier = field.getQualifier();
|
|
dcv.value = resultRow.getStringColumn("text_value");
|
|
dcv.language = resultRow.getStringColumn("text_lang");
|
|
//dcv.namespace = schema.getNamespace();
|
|
dcv.schema = schema.getName();
|
|
dcv.authority = resultRow.getStringColumn("authority");
|
|
dcv.confidence = resultRow.getIntColumn("confidence");
|
|
|
|
// Add it to the list
|
|
metadata.add(dcv);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
// close the TableRowIterator to free up resources
|
|
if (tri != null)
|
|
{
|
|
tri.close();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return metadata;
|
|
}
|
|
|
|
void set(List<DCValue> m)
|
|
{
|
|
metadata = m;
|
|
}
|
|
|
|
TableRowIterator retrieveMetadata(int itemId) throws SQLException
|
|
{
|
|
if (itemId > 0)
|
|
{
|
|
return DatabaseManager.queryTable(ourContext, "MetadataValue",
|
|
"SELECT * FROM MetadataValue WHERE item_id= ? ORDER BY metadata_field_id, place",
|
|
itemId);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|
|
}
|