/**
* 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.sql.SQLException;
import java.util.*;
import org.apache.commons.lang.ArrayUtils;
import org.apache.log4j.Logger;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.dspace.authorize.AuthorizeException;
import org.dspace.content.authority.ChoiceAuthorityManager;
import org.dspace.content.authority.Choices;
import org.dspace.content.authority.MetadataAuthorityManager;
import org.dspace.core.Constants;
import org.dspace.core.Context;
import org.dspace.core.LogManager;
import org.dspace.eperson.EPerson;
import org.dspace.eperson.Group;
import org.dspace.event.Event;
import org.dspace.storage.rdbms.DatabaseManager;
import org.dspace.storage.rdbms.TableRow;
import org.dspace.storage.rdbms.TableRowIterator;
import org.dspace.handle.HandleManager;
import org.dspace.identifier.IdentifierService;
import org.dspace.utils.DSpace;
/**
* Abstract base class for DSpace objects
*/
public abstract class DSpaceObject
{
/** Our context */
protected Context ourContext;
/** log4j category */
private static final Logger log = Logger.getLogger(DSpaceObject.class);
/**
* True if anything else was changed since last update()
* (to drive event mechanism)
*/
protected boolean modifiedMetadata;
// accumulate information to add to "detail" element of content Event,
// e.g. to document metadata fields touched, etc.
private StringBuffer eventDetails = null;
private String[] identifiers = null;
/** The Dublin Core metadata - inner class for lazy loading */
protected MetadataCache metadataCache = new MetadataCache();
/**
* Construct a DSpaceOBject with the given table row
*
* @throws SQLException
*/
protected DSpaceObject()
{
modifiedMetadata = false;
}
protected DSpaceObject(Context context)
{
ourContext = context;
modifiedMetadata = false;
}
public void updateMetadata() throws SQLException, AuthorizeException {
// 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>();
modifiedMetadata = 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++)
{
Metadatum 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("Invalid metadata field: [" + dcv.getField() + "] : [" + dcv.value + "]");
throw new SQLException("Invalid metadata field: [" + dcv.getField() + "]");
}
}
// 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;
Metadatum 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);
modifiedMetadata = 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])
{
Metadatum dcv = getMetadata().get(dcIdx);
// Write Metadatum
MetadataValue metadata = new MetadataValue();
metadata.setResourceId(getID());
metadata.setResourceTypeId(getType());
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);
modifiedMetadata = true;
}
}
if(modifiedMetadata) {
ourContext.addEvent(new Event(Event.MODIFY_METADATA, getType(), getID(), getDetails(), getIdentifiers(ourContext)));
modifiedMetadata = false;
}
}
/**
* Reset the cache of event details.
*/
protected void clearDetails()
{
eventDetails = null;
}
/**
* Add a string to the cache of event details. Automatically
* separates entries with a comma.
* Subclass can just start calling addDetails, since it creates
* the cache if it needs to.
* @param d detail string to add.
*/
protected void addDetails(String d)
{
if (eventDetails == null)
{
eventDetails = new StringBuffer(d);
}
else
{
eventDetails.append(", ").append(d);
}
}
/**
* @return summary of event details, or null if there are none.
*/
protected String getDetails()
{
return (eventDetails == null ? null : eventDetails.toString());
}
/**
* Get the type of this object, found in Constants
*
* @return type of the object
*/
public abstract int getType();
/**
* Provide the text name of the type of this DSpaceObject. It is most likely all uppercase.
* @return Object type as text
*/
public String getTypeText()
{
return Constants.typeText[this.getType()];
}
/**
* Get the internal ID (database primary key) of this object
*
* @return internal ID of object
*/
public abstract int getID();
/**
* Get the Handle of the object. This may return <code>null</code>
*
* @return Handle of the object, or <code>null</code> if it doesn't have
* one
*/
public abstract String getHandle();
/**
* Get a proper name for the object. This may return <code>null</code>.
* Name should be suitable for display in a user interface.
*
* @return Name for the object, or <code>null</code> if it doesn't have
* one
*/
public abstract String getName();
/**
* Tries to lookup all Identifiers of this DSpaceObject.
* @return An array containing all found identifiers or an array with a length of 0.
*/
public String[] getIdentifiers(Context context)
{
if (identifiers == null)
{
log.debug("This DSO's identifiers cache is empty, looking for identifiers...");
identifiers = new String[0];
IdentifierService identifierService =
new DSpace().getSingletonService(IdentifierService.class);
if (identifierService != null)
{
identifiers = identifierService.lookup(context, this);
} else {
log.warn("No IdentifierService found, will return an array containing "
+ "the Handle only.");
if (getHandle() != null)
{
identifiers = new String[] { HandleManager.getCanonicalForm(getHandle()) };
}
}
}
// it the DSO has no identifiers at all including handle, we should return an empty array.
// G.e. items during submission (workspace items) have no handle and no other identifier.
if (identifiers == null)
{
identifiers = new String[] {};
}
if (log.isDebugEnabled())
{
StringBuilder dbgMsg = new StringBuilder();
for (String id : identifiers)
{
if (dbgMsg.capacity() == 0)
{
dbgMsg.append("This DSO's Identifiers are: ");
} else {
dbgMsg.append(", ");
}
dbgMsg.append(id);
}
dbgMsg.append(".");
log.debug(dbgMsg.toString());
}
return identifiers;
}
public void resetIdentifiersCache()
{
identifiers = null;
}
/**
* Generic find for when the precise type of a DSO is not known, just the
* a pair of type number and database ID.
*
* @param context - the context
* @param type - type number
* @param id - id within table of type'd objects
* @return the object found, or null if it does not exist.
* @throws SQLException only upon failure accessing the database.
*/
public static DSpaceObject find(Context context, int type, int id)
throws SQLException
{
switch (type)
{
case Constants.BITSTREAM : return Bitstream.find(context, id);
case Constants.BUNDLE : return Bundle.find(context, id);
case Constants.ITEM : return Item.find(context, id);
case Constants.COLLECTION: return Collection.find(context, id);
case Constants.COMMUNITY : return Community.find(context, id);
case Constants.GROUP : return Group.find(context, id);
case Constants.EPERSON : return EPerson.find(context, id);
case Constants.SITE : return Site.find(context, id);
}
return null;
}
/**
* Return the dspace object where an ADMIN action right is sufficient to
* grant the initial authorize check.
* <p>
* Default behaviour is ADMIN right on the object grant right on all other
* action on the object itself. Subclass should override this method as
* needed.
*
* @param action
* ID of action being attempted, from
* <code>org.dspace.core.Constants</code>. The ADMIN action is
* not a valid parameter for this method, an
* IllegalArgumentException should be thrown
* @return the dspace object, if any, where an ADMIN action is sufficient to
* grant the original action
* @throws SQLException
* @throws IllegalArgumentException
* if the ADMIN action is supplied as parameter of the method
* call
*/
public DSpaceObject getAdminObject(int action) throws SQLException
{
if (action == Constants.ADMIN)
{
throw new IllegalArgumentException("Illegal call to the DSpaceObject.getAdminObject method");
}
return this;
}
/**
* Return the dspace object that "own" the current object in the hierarchy.
* Note that this method has a meaning slightly different from the
* getAdminObject because it is independent of the action but it is in a way
* related to it. It defines the "first" dspace object <b>OTHER</b> then the
* current one, where allowed ADMIN actions imply allowed ADMIN actions on
* the object self.
*
* @return the dspace object that "own" the current object in
* the hierarchy
* @throws SQLException
*/
public DSpaceObject getParentObject() throws SQLException
{
return null;
}
public abstract void update() throws SQLException, AuthorizeException;
public abstract void updateLastModified();
private TableRowIterator retrieveMetadata() throws SQLException
{
return DatabaseManager.queryTable(ourContext, "MetadataValue",
"SELECT * FROM MetadataValue WHERE resource_id= ? and resource_type_id = ? ORDER BY metadata_field_id, place",
getID(),
getType());
}
/**
* Get Dublin Core metadata for the DSpace Object.
* 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>DSpaceObject.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>dspaceobject.getDC( "title", null, DSpaceObject.ANY );</code>
* <P>
* Return all US English values of the "title" element, with any qualifier
* (including unqualified):
* <P>
* <code>dspaceobject.getDC( "title", DSpaceObject.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>DSpaceObject.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>DSpaceObject.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>DSpaceObject.ANY</code> means values with any country code or
* no country code are returned.
* @return Dublin Core fields that match the parameters
*/
@Deprecated
public Metadatum[] getDC(String element, String qualifier, String lang)
{
return getMetadata(MetadataSchema.DC_SCHEMA, element, qualifier, lang);
}
/**
* Get metadata for the DSpace Object 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>DSpaceObject.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>dspaceobject.getMetadataByMetadataString("dc", "title", null, DSpaceObject.ANY );</code>
* <P>
* Return all US English values of the "title" element, with any qualifier
* (including unqualified):
* <P>
* <code>dspaceobject.getMetadataByMetadataString("dc, "title", DSpaceObject.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>DSpaceObject.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>DSpaceObject.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>DSpaceObject.ANY</code> means values with any country code or
* no country code are returned.
* @return metadata fields that match the parameters
*/
public Metadatum[] getMetadata(String schema, String element, String qualifier,
String lang)
{
// Build up list of matching values
List<Metadatum> values = new ArrayList<Metadatum>();
for (Metadatum dcv : getMetadata())
{
if (match(schema, element, qualifier, lang, dcv))
{
// We will return a copy of the object in case it is altered
Metadatum copy = new Metadatum();
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
Metadatum[] valueArray = new Metadatum[values.size()];
valueArray = (Metadatum[]) 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 Metadatum[] getMetadataByMetadataString(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];
Metadatum[] 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;
}
/**
* Retrieve first metadata field value
*/
protected String getMetadataFirstValue(String schema, String element, String qualifier, String language){
Metadatum[] dcvalues = getMetadata(schema, element, qualifier, Item.ANY);
if(dcvalues.length>0){
return dcvalues[0].value;
}
return null;
}
/**
* Set first metadata field value
*/
protected void setMetadataSingleValue(String schema, String element, String qualifier, String language, String value) {
if(value != null)
{
clearMetadata(schema, element, qualifier, language);
addMetadata(schema, element, qualifier, language, value);
modifiedMetadata = true;
}
}
protected List<Metadatum> getMetadata()
{
try
{
return metadataCache.get(ourContext, getID(), getType(), log);
}
catch (SQLException e)
{
log.error("Loading item - cannot load metadata");
}
return new ArrayList<Metadatum>();
}
/**
* Get the value of a metadata field
*
* @param value
* the name of the metadata field to get
*
* @return the value of the metadata field (or null if the column is an SQL NULL)
*
* @exception IllegalArgumentException
* if the requested metadata field doesn't exist
*/
public String getMetadata(String value){
Metadatum[] dcvalues = getMetadataByMetadataString(value);
if(dcvalues.length>0) {
return dcvalues[0].value;
}
return null;
}
public List<Metadatum> getMetadata(String mdString, String authority) {
String[] elements = getElements(mdString);
return getMetadata(elements[0], elements[1], elements[2], elements[3], authority);
}
public List<Metadatum> getMetadata(String schema, String element, String qualifier, String lang, String authority) {
Metadatum[] metadata = getMetadata(schema, element, qualifier, lang);
List<Metadatum> dcValues = Arrays.asList(metadata);
if (!authority.equals(Item.ANY)) {
Iterator<Metadatum> iterator = dcValues.iterator();
while (iterator.hasNext()) {
Metadatum dcValue = iterator.next();
if (!authority.equals(dcValue.authority)) {
iterator.remove();
}
}
}
return dcValues;
}
/**
* Splits "schema.element.qualifier.language" into an array.
* <p/>
* The returned array will always have length >= 4
* <p/>
* Values in the returned array can be empty or null.
*/
public static String[] getElements(String fieldName) {
String[] tokens = StringUtils.split(fieldName, ".");
int add = 4 - tokens.length;
if (add > 0) {
tokens = (String[]) ArrayUtils.addAll(tokens, new String[add]);
}
return tokens;
}
/**
* Splits "schema.element.qualifier.language" into an array.
* <p/>
* The returned array will always have length >= 4
* <p/>
* When @param fill is true, elements that would be empty or null are replaced by Item.ANY
*/
public static String[] getElementsFilled(String fieldName) {
String[] elements = getElements(fieldName);
for (int i = 0; i < elements.length; i++) {
if (StringUtils.isBlank(elements[i])) {
elements[i] = Item.ANY;
}
}
return elements;
}
public void replaceMetadataValue(Metadatum oldValue, Metadatum newValue)
{
// check both dcvalues are for the same field
if (oldValue.hasSameFieldAs(newValue)) {
String schema = oldValue.schema;
String element = oldValue.element;
String qualifier = oldValue.qualifier;
// Save all metadata for this field
Metadatum[] dcvalues = getMetadata(schema, element, qualifier, Item.ANY);
clearMetadata(schema, element, qualifier, Item.ANY);
for (Metadatum dcvalue : dcvalues) {
if (dcvalue.equals(oldValue)) {
addMetadata(schema, element, qualifier, newValue.language, newValue.value, newValue.authority, newValue.confidence);
} else {
addMetadata(schema, element, qualifier, dcvalue.language, dcvalue.value, dcvalue.authority, dcvalue.confidence);
}
}
}
}
/**
* 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)
{
getAuthoritiesAndConfidences(fieldKey, values, authorities, confidences, i);
}
addMetadata(schema, element, qualifier, lang, values, authorities, confidences);
}
else
{
addMetadata(schema, element, qualifier, lang, values, null, null);
}
}
protected void getAuthoritiesAndConfidences(String fieldKey, String[] values, String[] authorities, int[] confidences, int i) {
Choices c = ChoiceAuthorityManager.getManager().getBestMatch(fieldKey, values[i], -1, null);
authorities[i] = c.values.length > 0 ? c.values[0].authority : null;
confidences[i] = c.confidence;
}
/**
* 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<Metadatum> 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++)
{
Metadatum dcv = new Metadatum();
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)
{
modifiedMetadata = 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>idspaceobject.clearDC(DSpaceObject.ANY, DSpaceObject.ANY, DSpaceObject.ANY)</code> will
* remove all Dublin Core metadata associated with an DSpaceObject.
*
* @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>dspaceobject.clearDC(Item.ANY, Item.ANY, Item.ANY)</code> will
* remove all Dublin Core metadata associated with an DSpaceObject.
*
* @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<Metadatum> values = new ArrayList<Metadatum>();
for (Metadatum 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);
modifiedMetadata = 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>Metadatum</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, Metadatum 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;
}
protected transient MetadataField[] allMetadataFields = null;
protected MetadataField getMetadataField(Metadatum 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(Metadatum 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;
}
/**
* Utility method to remove all descriptive metadata associated with the DSpaceObject from
* the database (regardless of in-memory version)
*
* @throws SQLException
*/
protected void removeMetadataFromDatabase() throws SQLException
{
DatabaseManager.updateQuery(ourContext,
"DELETE FROM MetadataValue WHERE resource_id= ? and resource_type_id=?",
getID(),
getType());
}
private void setMetadata(List<Metadatum> metadata)
{
metadataCache.set(metadata);
modifiedMetadata = true;
}
class MetadataCache
{
List<Metadatum> metadata = null;
List<Metadatum> get(Context c, int resourceId, int resourceTypeId, Logger log) throws SQLException
{
if (metadata == null)
{
metadata = new ArrayList<Metadatum>();
// Get Dublin Core metadata
TableRowIterator tri = retrieveMetadata(resourceId, resourceTypeId);
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 + " for resourceType=" + resourceTypeId + " and resourceId=" + resourceId);
}
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 Metadatum object
Metadatum dcv = new Metadatum();
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<Metadatum> m)
{
metadata = m;
}
TableRowIterator retrieveMetadata(int resourceId, int resourceTypeId) throws SQLException
{
return DatabaseManager.queryTable(ourContext, "MetadataValue",
"SELECT * FROM MetadataValue WHERE resource_id= ? and resource_type_id = ? ORDER BY metadata_field_id, place",
resourceId,
resourceTypeId);
}
}
protected String[] getMDValueByField(String field){
StringTokenizer dcf = new StringTokenizer(field, ".");
String[] tokens = { "", "", "" };
int i = 0;
while(dcf.hasMoreTokens()){
tokens[i] = dcf.nextToken().trim();
i++;
}
if(i!=0){
return tokens;
}else{
return getMDValueByLegacyField(field);
}
}
protected String[] getMDValueByLegacyField(String field){
switch (field) {
case "introductory_text":
return new String[]{MetadataSchema.DC_SCHEMA, "description", null};
case "short_description":
return new String[]{MetadataSchema.DC_SCHEMA, "description", "abstract"};
case "side_bar_text":
return new String[]{MetadataSchema.DC_SCHEMA, "description", "tableofcontents"};
case "copyright_text":
return new String[]{MetadataSchema.DC_SCHEMA, "rights", null};
case "name":
return new String[]{MetadataSchema.DC_SCHEMA, "title", null};
case "provenance_description":
return new String[]{MetadataSchema.DC_SCHEMA,"provenance", null};
case "license":
return new String[]{MetadataSchema.DC_SCHEMA, "rights", "license"};
case "user_format_description":
return new String[]{MetadataSchema.DC_SCHEMA,"format",null};
case "source":
return new String[]{MetadataSchema.DC_SCHEMA,"source",null};
case "firstname":
return new String[]{"eperson","firstname",null};
case "lastname":
return new String[]{"eperson","lastname",null};
case "phone":
return new String[]{"eperson","phone",null};
case "language":
return new String[]{"eperson","language",null};
default:
return new String[]{null, null, null};
}
}
}