Package org.dspace.content

Source Code of org.dspace.content.DSpaceObject$MetadataCache

/**
* 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};
        }
    }
   
}
TOP

Related Classes of org.dspace.content.DSpaceObject$MetadataCache

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.