/* * MetadataField.java * * Version: $Revision: 3761 $ * * Date: $Date: 2009-05-07 04:18:02 +0000 (Thu, 07 May 2009) $ * * Copyright (c) 2002-2009, The DSpace Foundation. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of the DSpace Foundation nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. */ package org.dspace.content; import java.io.IOException; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; import java.util.HashMap; import org.apache.log4j.Logger; import org.dspace.authorize.AuthorizeException; import org.dspace.authorize.AuthorizeManager; import org.dspace.core.Context; import org.dspace.core.LogManager; import org.dspace.storage.rdbms.DatabaseManager; import org.dspace.storage.rdbms.TableRow; import org.dspace.storage.rdbms.TableRowIterator; /** * DSpace object that represents a metadata field, which is * defined by a combination of schema, element, and qualifier. Every * metadata element belongs in a field. * * @author Martin Hald * @version $Revision: 3761 $ * @see org.dspace.content.MetadataValue, org.dspace.content.MetadataSchema */ public class MetadataField { private int fieldID = 0; private int schemaID = 0; private String element; private String qualifier; private String scopeNote; /** log4j logger */ private static Logger log = Logger.getLogger(MetadataField.class); /** The row in the table representing this type */ private TableRow row; // cache of field by ID (Integer) private static HashMap id2field = null; /** * Default constructor. */ public MetadataField() { } /** * Constructor creating a field within a schema. * * @param schema schema to which the field belongs */ public MetadataField(MetadataSchema schema) { this.schemaID = schema.getSchemaID(); } /** * Full contructor for new metadata field elements. * * @param schema schema to which the field belongs * @param element element of the field * @param qualifier qualifier of the field * @param scopeNote scope note of the field */ public MetadataField(MetadataSchema schema, String element, String qualifier, String scopeNote) { this.schemaID = schema.getSchemaID(); this.element = element; this.qualifier = qualifier; this.scopeNote = scopeNote; } /** * Full construtor for existing metadata field elements. * * @param schemaID schema to which the field belongs * @param fieldID dataabse ID of field. * @param element element of the field * @param qualifier qualifier of the field * @param scopeNote scope note of the field */ public MetadataField(int schemaID, int fieldID, String element, String qualifier, String scopeNote) { this.schemaID = schemaID; this.fieldID = fieldID; this.element = element; this.qualifier = qualifier; this.scopeNote = scopeNote; } /** * Constructor to load the object from the database. * * @param row database row from which to populate object. */ public MetadataField(TableRow row) { if (row != null) { this.fieldID = row.getIntColumn("metadata_field_id"); this.schemaID = row.getIntColumn("metadata_schema_id"); this.element = row.getStringColumn("element"); this.qualifier = row.getStringColumn("qualifier"); this.scopeNote = row.getStringColumn("scope_note"); this.row = row; } } /** * Get the element name. * * @return element name */ public String getElement() { return element; } /** * Set the element name. * * @param element new value for element */ public void setElement(String element) { this.element = element; } /** * Get the metadata field id. * * @return metadata field id */ public int getFieldID() { return fieldID; } /** * Get the qualifier. * * @return qualifier */ public String getQualifier() { return qualifier; } /** * Set the qualifier. * * @param qualifier new value for qualifier */ public void setQualifier(String qualifier) { this.qualifier = qualifier; } /** * Get the schema record key. * * @return schema record key */ public int getSchemaID() { return schemaID; } /** * Set the schema record key. * * @param schemaID new value for key */ public void setSchemaID(int schemaID) { this.schemaID = schemaID; } /** * Get the scope note. * * @return scope note */ public String getScopeNote() { return scopeNote; } /** * Set the scope note. * * @param scopeNote new value for scope note */ public void setScopeNote(String scopeNote) { this.scopeNote = scopeNote; } /** * Creates a new metadata field. * * @param context * DSpace context object * @throws IOException * @throws AuthorizeException * @throws SQLException * @throws NonUniqueMetadataException */ public void create(Context context) throws IOException, AuthorizeException, SQLException, NonUniqueMetadataException { // Check authorisation: Only admins may create DC types if (!AuthorizeManager.isAdmin(context)) { throw new AuthorizeException( "Only administrators may modify the metadata registry"); } // Ensure the element and qualifier are unique within a given schema. if (!unique(context, schemaID, element, qualifier)) { throw new NonUniqueMetadataException("Please make " + element + "." + qualifier + " unique within schema #" + schemaID); } // Create a table row and update it with the values row = DatabaseManager.row("MetadataFieldRegistry"); row.setColumn("metadata_schema_id", schemaID); row.setColumn("element", element); row.setColumn("qualifier", qualifier); row.setColumn("scope_note", scopeNote); DatabaseManager.insert(context, row); decache(); // Remember the new row number this.fieldID = row.getIntColumn("metadata_field_id"); log.info(LogManager.getHeader(context, "create_metadata_field", "metadata_field_id=" + row.getIntColumn("metadata_field_id"))); } /** * Retrieves the metadata field from the database. * * @param context dspace context * @param schemaID schema by ID * @param element element name * @param qualifier qualifier (may be ANY or null) * @return recalled metadata field * @throws SQLException * @throws AuthorizeException */ public static MetadataField findByElement(Context context, int schemaID, String element, String qualifier) throws SQLException, AuthorizeException { // Grab rows from DB TableRowIterator tri; if (qualifier == null) { tri = DatabaseManager.queryTable(context,"MetadataFieldRegistry", "SELECT * FROM MetadataFieldRegistry WHERE metadata_schema_id= ? " + "AND element= ? AND qualifier is NULL ", schemaID, element); } else { tri = DatabaseManager.queryTable(context,"MetadataFieldRegistry", "SELECT * FROM MetadataFieldRegistry WHERE metadata_schema_id= ? " + "AND element= ? AND qualifier= ? ", schemaID, element, qualifier); } TableRow row = null; try { if (tri.hasNext()) { row = tri.next(); } } finally { // close the TableRowIterator to free up resources if (tri != null) tri.close(); } if (row == null) { return null; } else { return new MetadataField(row); } } /** * Retrieve all Dublin Core types from the registry * * @param context dspace context * @return an array of all the Dublin Core types * @throws SQLException */ public static MetadataField[] findAll(Context context) throws SQLException { List fields = new ArrayList(); // Get all the metadatafieldregistry rows TableRowIterator tri = DatabaseManager.queryTable(context, "MetadataFieldRegistry", "SELECT mfr.* FROM MetadataFieldRegistry mfr, MetadataSchemaRegistry msr where mfr.metadata_schema_id= msr.metadata_schema_id ORDER BY msr.short_id, mfr.element, mfr.qualifier"); try { // Make into DC Type objects while (tri.hasNext()) { fields.add(new MetadataField(tri.next())); } } finally { // close the TableRowIterator to free up resources if (tri != null) tri.close(); } // Convert list into an array MetadataField[] typeArray = new MetadataField[fields.size()]; return (MetadataField[]) fields.toArray(typeArray); } /** * Return all metadata fields that are found in a given schema. * * @param context dspace context * @param schemaID schema by db ID * @return array of metadata fields * @throws SQLException */ public static MetadataField[] findAllInSchema(Context context, int schemaID) throws SQLException { List fields = new ArrayList(); // Get all the metadatafieldregistry rows TableRowIterator tri = DatabaseManager.queryTable(context,"MetadataFieldRegistry", "SELECT * FROM MetadataFieldRegistry WHERE metadata_schema_id= ? " + " ORDER BY element, qualifier", schemaID); try { // Make into DC Type objects while (tri.hasNext()) { fields.add(new MetadataField(tri.next())); } } finally { // close the TableRowIterator to free up resources if (tri != null) tri.close(); } // Convert list into an array MetadataField[] typeArray = new MetadataField[fields.size()]; return (MetadataField[]) fields.toArray(typeArray); } /** * Update the metadata field in the database. * * @param context dspace context * @throws SQLException * @throws AuthorizeException * @throws NonUniqueMetadataException * @throws IOException */ public void update(Context context) throws SQLException, AuthorizeException, NonUniqueMetadataException, IOException { // Check authorisation: Only admins may update the metadata registry if (!AuthorizeManager.isAdmin(context)) { throw new AuthorizeException( "Only administrators may modiffy the Dublin Core registry"); } // Check to see if the schema ID was altered. If is was then we will // query to ensure that there is not already a duplicate name field. if (row.getIntColumn("metadata_schema_id") != schemaID) { if (MetadataField.hasElement(context, schemaID, element, qualifier)) { throw new NonUniqueMetadataException( "Duplcate field name found in target schema"); } } // Ensure the element and qualifier are unique within a given schema. if (!unique(context, schemaID, element, qualifier)) { throw new NonUniqueMetadataException("Please make " + element + "." + qualifier); } row.setColumn("metadata_schema_id", schemaID); row.setColumn("element", element); row.setColumn("qualifier", qualifier); row.setColumn("scope_note", scopeNote); DatabaseManager.update(context, row); decache(); log.info(LogManager.getHeader(context, "update_metadatafieldregistry", "metadata_field_id=" + getFieldID() + "element=" + getElement() + "qualifier=" + getQualifier())); } /** * Return true if and only if the schema has a field with the given element * and qualifier pair. * * @param context dspace context * @param schemaID schema by ID * @param element element name * @param qualifier qualifier name * @return true if the field exists * @throws SQLException * @throws AuthorizeException */ private static boolean hasElement(Context context, int schemaID, String element, String qualifier) throws SQLException, AuthorizeException { return MetadataField.findByElement(context, schemaID, element, qualifier) != null; } /** * Delete the metadata field. * * @param context dspace context * @throws SQLException * @throws AuthorizeException */ public void delete(Context context) throws SQLException, AuthorizeException { // Check authorisation: Only admins may create DC types if (!AuthorizeManager.isAdmin(context)) { throw new AuthorizeException( "Only administrators may modify the metadata registry"); } log.info(LogManager.getHeader(context, "delete_metadata_field", "metadata_field_id=" + getFieldID())); DatabaseManager.delete(context, row); decache(); } /** * A sanity check that ensures a given element and qualifier are unique * within a given schema. The check happens in code as we cannot use a * database constraint. * * @param context dspace context * @param schemaID * @param element * @param qualifier * @return true if unique * @throws AuthorizeException * @throws SQLException * @throws IOException */ private boolean unique(Context context, int schemaID, String element, String qualifier) throws IOException, SQLException, AuthorizeException { int count = 0; Connection con = null; PreparedStatement statement = null; ResultSet rs = null; try { con = context.getDBConnection(); TableRow reg = DatabaseManager.row("MetadataFieldRegistry"); String qualifierClause = ""; if (qualifier == null) { qualifierClause = "and qualifier is null"; } else { qualifierClause = "and qualifier = ?"; } String query = "SELECT COUNT(*) FROM " + reg.getTable() + " WHERE metadata_schema_id= ? " + " and metadata_field_id != ? " + " and element= ? " + qualifierClause; statement = con.prepareStatement(query); statement.setInt(1,schemaID); statement.setInt(2,fieldID); statement.setString(3,element); if (qualifier != null) { statement.setString(4,qualifier); } rs = statement.executeQuery(); if (rs.next()) { count = rs.getInt(1); } } finally { if (rs != null) { try { rs.close(); } catch (SQLException sqle) { } } if (statement != null) { try { statement.close(); } catch (SQLException sqle) { } } } return (count == 0); } /** * Return the HTML FORM key for the given field. * * @param schema * @param element * @param qualifier * @return HTML FORM key */ public static String formKey(String schema, String element, String qualifier) { if (qualifier == null) { return schema + "_" + element; } else { return schema + "_" + element + "_" + qualifier; } } /** * Find the field corresponding to the given numeric ID. The ID is * a database key internal to DSpace. * * @param context * context, in case we need to read it in from DB * @param id * the metadata field ID * @return the metadata field object * @throws SQLException */ public static MetadataField find(Context context, int id) throws SQLException { initCache(context); // 'sanity check' first. Integer iid = new Integer(id); if (!id2field.containsKey(iid)) return null; return (MetadataField) id2field.get(iid); } // invalidate the cache e.g. after something modifies DB state. private static void decache() { id2field = null; } // load caches if necessary private static void initCache(Context context) throws SQLException { if (id2field != null) return; synchronized (MetadataField.class) { if (id2field == null) { HashMap new_id2field = new HashMap(); log.info("Loading MetadataField elements into cache."); // Grab rows from DB TableRowIterator tri = DatabaseManager.queryTable(context,"MetadataFieldRegistry", "SELECT * from MetadataFieldRegistry"); try { while (tri.hasNext()) { TableRow row = tri.next(); int fieldID = row.getIntColumn("metadata_field_id"); new_id2field.put(new Integer(fieldID), new MetadataField(row)); } } finally { // close the TableRowIterator to free up resources if (tri != null) tri.close(); } id2field = new_id2field; } } } }