Files
DSpace/dspace-api/src/main/java/org/dspace/content/Item.java
Andrea Bollini 4fc8b9348b DS-1217 Porting Discovery to the JSPUI
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)
2012-09-13 12:31:23 +02:00

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;
}
}
}