diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/BulkEditChange.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/BulkEditChange.java new file mode 100644 index 0000000000..7752583c46 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/BulkEditChange.java @@ -0,0 +1,291 @@ +/* + * BulkEditChange.java + * + * Version: $Revision$ + * + * Date: $Date$ + * + * 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.app.bulkedit; + +import org.dspace.content.Item; +import org.dspace.content.DCValue; +import org.dspace.content.Collection; + +import java.util.ArrayList; + +/** + * Utility class to store changes to item that may occur during a batch edit. + * + * @author Stuart Lewis + */ +public class BulkEditChange +{ + /** The item these changes relate to */ + private Item item; + + /** The ArrayList of hashtables with the new elements */ + private ArrayList adds; + + /** The ArrayList of hashtables with the removed elements */ + private ArrayList removes; + + /** The ArrayList of hashtablles with the unchanged elements */ + private ArrayList constant; + + /** The ArrayList of the complete set of new values (constant + adds) */ + private ArrayList complete; + + /** The Arraylist of old collections the item has been moved from */ + private ArrayList oldOwningCollections; + + /** The Arraylist of new collections the item has been moved into */ + private ArrayList newOwningCollections; + + /** Is this a new item */ + private boolean newItem; + + /** Have any changes actually been made? */ + private boolean empty; + + + /** + * Initalise a change holder for a new item + */ + public BulkEditChange() + { + // Set the item to be null + item = null; + newItem = true; + empty = true; + + // Initialise the arrays + adds = new ArrayList(); + removes = new ArrayList(); + constant = new ArrayList(); + complete = new ArrayList(); + oldOwningCollections = new ArrayList(); + newOwningCollections = new ArrayList(); + } + + /** + * Initalise a new change holder for an existing item + * + * @param i The Item to store + */ + public BulkEditChange(Item i) + { + // Store the item + item = i; + newItem = false; + empty = true; + + // Initalise the arrays + adds = new ArrayList(); + removes = new ArrayList(); + constant = new ArrayList(); + complete = new ArrayList(); + oldOwningCollections = new ArrayList(); + newOwningCollections = new ArrayList(); + } + + /** + * Store the item - used when a new item is created + * + * @param i The item + */ + public void setItem(Item i) + { + // Store the item + item = i; + } + + /** + * Add an added metadata value + * + * @param dcv The value to add + */ + public void registerAdd(DCValue dcv) + { + // Add the added value + adds.add(dcv); + complete.add(dcv); + empty = false; + } + + /** + * Add a removed metadata value + * + * @param dcv The value to remove + */ + public void registerRemove(DCValue dcv) + { + // Add the removed value + removes.add(dcv); + empty = false; + } + + /** + * Add an unchanged metadata value + * + * @param dcv The value to keep unchanged + */ + public void registerConstant(DCValue dcv) + { + // Add the removed value + constant.add(dcv); + complete.add(dcv); + } + + /** + * Add a new owning Collection + * + * @param c The new owning Collection + */ + public void registerNewOwningCollection(Collection c) + { + // Add the new owning Collection\ + newOwningCollections.add(c); + empty = false; + } + + /** + * Add an old owning Collection + * + * @param c The old owning Collection + */ + public void registerOldOwningCollection(Collection c) + { + // Add the old owning Collection\ + oldOwningCollections.add(c); + empty = false; + } + + /** + * Get the DSpace Item that these changes are applicable to. + * + * @return The item + */ + public Item getItem() + { + // Return the item + return item; + } + + /** + * Get the list of elements and their values that have been added. + * + * @return the list of elements and their values that have been added. + */ + public ArrayList getAdds() + { + // Return the array + return adds; + } + + /** + * Get the list of elements and their values that have been removed. + * + * @return the list of elements and their values that have been removed. + */ + public ArrayList getRemoves() + { + // Return the array + return removes; + } + + /** + * Get the list of unchanged values + * + * @return the list of unchanged values + */ + public ArrayList getConstant() + { + // Return the array + return constant; + } + + /** + * Get the list of all values + * + * @return the list of all values + */ + public ArrayList getComplete() + { + // Return the array + return complete; + } + + /** + * Get the list of new owning Collections + * + * @return the list of new owning collections + */ + public ArrayList getNewOwningCollections() + { + // Return the array + return newOwningCollections; + } + + /** + * Get the list of old owning Collections + * + * @return the list of old owning collections + */ + public ArrayList getOldOwningCollections() + { + // Return the array + return oldOwningCollections; + } + + /** + * Does this change object represent a new item? + * + * @return Whether or not this is for a new item + */ + public boolean isNewItem() + { + // Return the new item status + return newItem; + } + + /** + * Have any changes actually been recorded, or is this empty? + * + * @return Whether or not changes have been made + */ + public boolean hasChanges() + { + return !empty; + } +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java new file mode 100644 index 0000000000..0adfee04ae --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSV.java @@ -0,0 +1,517 @@ +/* + * DSpaceCSV.java + * + * Version: $Revision$ + * + * Date: $Date$ + * + * 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.app.bulkedit; + +import org.dspace.content.*; +import org.dspace.content.Collection; +import org.dspace.core.ConfigurationManager; + +import java.util.*; +import java.util.regex.Pattern; +import java.util.regex.Matcher; +import java.io.*; + +/** + * Utility class to read and write CSV files + * + * @author Stuart Lewis + */ +public class DSpaceCSV +{ + /** The headings of the CSV file */ + private ArrayList headings; + + /** An array list of CSV lines */ + private ArrayList lines; + + /** A counter of how many CSV lines this object holds */ + private int counter; + + /** The value separator (defaults to double pipe '||') */ + protected static String valueSeparator; + + /** The value separator in an escaped form for using in regexs */ + protected static String escpaedValueSeparator; + + /** The field separator (defaults to comma) */ + protected static String fieldSeparator; + + /** The field separator in an escaped form for using in regexs */ + protected static String escpaedFieldSeparator; + + + /** + * Create a new instance of a CSV line holder + */ + public DSpaceCSV() + { + // Set the value separator + setValueSeparator(); + + // Set the field separator + setFieldSeparator(); + + // Create the headings + headings = new ArrayList(); + + // Create the blank list of items + lines = new ArrayList(); + + // Initalise the counter + counter = 0; + } + + /** + * Create a new instance, reading the lines in from file + * + * @param f The file to read from + * + * @throws Exception thrown if there is an error reading or processing the file + */ + public DSpaceCSV(File f) throws Exception + { + // Set the value separator + setValueSeparator(); + + // Set the field separator + setFieldSeparator(); + + // Create the headings + headings = new ArrayList(); + + // Create the blank list of items + lines = new ArrayList(); + + // Initalise the counter + counter = 0; + + // Open the CSV file + BufferedReader input = new BufferedReader(new InputStreamReader(new FileInputStream(f),"UTF8")); + + // Read the heading line + String head = input.readLine(); + String[] headingElements = head.split(fieldSeparator); + for (String element : headingElements) + { + // Remove surrounding quotes if there are any + if ((element.startsWith("\"")) && (element.endsWith("\""))) + { + element = element.substring(1, element.length() - 1); + } + + if (!"id".equals(element)) + { + // Store the heading + headings.add(element); + } + } + + // Read each subsequent line + String line; + while ((line = input.readLine()) != null){ + // Are there an odd number of quotes? + while (((" " + line + " ").split("\"").length)%2 == 0) + { + line = line + "\n" + input.readLine(); + } + + // Parse the item metadata + addItem(line); + } + } + + /** + * Set the value separator for multiple values stored in one csv value. + * + * Is set in dspace.cfg as bulkedit.valueseparator + * + * If not set, defaults to double pipe '||' + */ + private void setValueSeparator() + { + // Get the value separator + valueSeparator = ConfigurationManager.getProperty("bulkedit.valueseparator"); + if ((valueSeparator != null) && (!"".equals(valueSeparator.trim()))) + { + valueSeparator = valueSeparator.trim(); + } + else + { + valueSeparator = "||"; + } + + // Now store the escaped version + Pattern spchars = Pattern.compile("([\\\\*+\\[\\](){}\\$.?\\^|])"); + Matcher match = spchars.matcher(valueSeparator); + escpaedValueSeparator = match.replaceAll("\\\\$1"); + } + + /** + * Set the field separator use to separate fields in the csv. + * + * Is set in dspace.cfg as bulkedit.fieldseparator + * + * If not set, defaults to comma ','. + * + * Special values are 'tab', 'hash' and 'semicolon' which will + * get substituted from the text to the value. + */ + private void setFieldSeparator() + { + // Get the value separator + fieldSeparator = ConfigurationManager.getProperty("bulkedit.fieldseparator"); + if ((fieldSeparator != null) && (!"".equals(fieldSeparator.trim()))) + { + fieldSeparator = fieldSeparator.trim(); + if ("tab".equals(fieldSeparator)) + { + fieldSeparator = "\t"; + } + else if ("semicolon".equals(fieldSeparator)) + { + fieldSeparator = ";"; + } + else if ("hash".equals(fieldSeparator)) + { + fieldSeparator = "#"; + } + else + { + fieldSeparator = fieldSeparator.trim(); + } + } + else + { + fieldSeparator = ","; + } + + // Now store the escaped version + Pattern spchars = Pattern.compile("([\\\\*+\\[\\](){}\\$.?\\^|])"); + Matcher match = spchars.matcher(fieldSeparator); + escpaedFieldSeparator = match.replaceAll("\\\\$1"); + } + + /** + * Add a DSpace item to the CSV file + * + * @param i The DSpace item + * + * @throws Exception if something goes wrong with adding the Item + */ + public void addItem(Item i) throws Exception + { + // Create the CSV line + DSpaceCSVLine line = new DSpaceCSVLine(i.getID()); + + // Add in owning collections + Collection[] collections = i.getCollections(); + for (Collection c : collections) + { + line.add("collection", c.getHandle()); + } + + // Populate it + DCValue md[] = i.getMetadata(Item.ANY, Item.ANY, Item.ANY, Item.ANY); + for (DCValue value : md) + { + // Get the key (schema.element) + String key = value.schema + "." + value.element; + + // Add the qualifer if there is one (schema.element.qualifier) + if (value.qualifier != null) + { + key = key + "." + value.qualifier; + } + + // Add the language if there is one (schema.element.qualifier[langauge]) + //if ((value.language != null) && (!"".equals(value.language))) + if (value.language != null) + { + key = key + "[" + value.language + "]"; + } + + // Store the item + line.add(key, value.value); + if (!headings.contains(key)) + { + headings.add(key); + } + } + lines.add(line); + counter++; + } + + /** + * Add an item to the CSV file, from a CSV line of elements + * + * @param line The line of elements + * @throws Exception Thrown if an error occurs when adding the item + */ + public void addItem(String line) throws Exception + { + // Check to see if the last character is a field separator, which hides the last empy column + boolean last = false; + if (line.endsWith(fieldSeparator)) + { + // Add a space to the end, then remove it later + last = true; + line += " "; + } + + // Split up on field separator + String[] parts = line.split(fieldSeparator); + ArrayList bits = new ArrayList(); + bits.addAll(Arrays.asList(parts)); + + // Merge parts with embedded separators + boolean alldone = false; + while (!alldone) + { + boolean found = false; + int i = 0; + for (String part : bits) + { + int bitcounter = part.length() - part.replaceAll("\"", "").length(); + if ((part.startsWith("\"")) && ((!part.endsWith("\"")) || ((bitcounter %2) == 1))) + { + found = true; + String add = bits.get(i) + fieldSeparator + bits.get(i + 1); + bits.remove(i); + bits.add(i, add); + bits.remove(i + 1); + break; + } + i++; + } + alldone = !found; + } + + // Deal with quotes around the elements + int i = 0; + for (String part : bits) + { + if ((part.startsWith("\"")) && (part.endsWith("\""))) + { + part = part.substring(1, part.length() - 1); + bits.set(i, part); + } + i++; + } + + // Remove embedded quotes + i = 0; + for (String part : bits) + { + if (part.contains("\"\"")) + { + part = part.replaceAll("\"\"", "\""); + bits.set(i, part); + } + i++; + } + + // Add elements to a DSpaceCSVLine + String id = parts[0].replaceAll("\"", ""); + DSpaceCSVLine csvLine; + + // Is this an existing item, or a new item (where id = '+') + if ("+".equals(id)) + { + csvLine = new DSpaceCSVLine(); + } + else + { + try + { + csvLine = new DSpaceCSVLine(Integer.parseInt(id)); + } + catch (NumberFormatException nfe) + { + System.err.println("Invalid item identifer: " + id); + System.err.println("Please check your CSV file for informaton. " + + "Item id must be numeric, or a '+' to add a new item"); + throw(nfe); + } + } + + // Add the rest of the parts + i = 0; + for (String part : bits) + { + if (i > 0) + { + // Is this a last empty item? + if ((last) && (i == headings.size())) + { + part = ""; + } + + // Make sure we register that this column was there + csvLine.add(headings.get(i - 1), null); + String[] elements = part.split(escpaedValueSeparator); + for (String element : elements) + { + if ((element != null) && (!"".equals(element))) + { + csvLine.add(headings.get(i - 1), element); + System.out.println(i + ":" + headings.size() + ":" + headings.get(i - 1) + ":" + element + ":"); + + } + } + } + i++; + } + lines.add(csvLine); + } + + /** + * Get the lines in CSV holders + * + * @return The lines + */ + public ArrayList getCSVLines() + { + // Return the lines + return lines; + } + + /** + * Get the CSV lines as an array of CSV formatted strings + * + * @return the array of CSV formatted Strings + */ + public String[] getCSVLinesAsStringArray() + { + // Create the headings line + String[] csvLines = new String[counter + 1]; + csvLines[0] = "id" + fieldSeparator + "collection"; + Collections.sort(headings); + for (String value : headings) + { + csvLines[0] = csvLines[0] + fieldSeparator + value; + } + + Iterator i = lines.iterator(); + int c = 1; + while (i.hasNext()) + { + csvLines[c++] = i.next().toCSV(headings); + } + + return csvLines; + } + + /** + * Save the CSV file to the given filename + * + * @param filename The filename to save the CSV file to + * + * @throws IOException Thrown if an error occurs when writing the file + */ + public void save(String filename) throws IOException + { + // Save the file + BufferedWriter out = new BufferedWriter( + new OutputStreamWriter( + new FileOutputStream(filename), "UTF8")); + for (String csvLine : getCSVLinesAsStringArray()) { + out.write(csvLine + "\n"); + } + out.flush(); + out.close(); + } + + /** + * Return the csv file as one long formatted string + * + * @return The formatted String as a csv + */ + public String toString() + { + // Return the csv as one long string + StringBuffer csvLines = new StringBuffer(); + String[] lines = this.getCSVLinesAsStringArray(); + for (String line : lines) + { + csvLines.append(line).append("\n"); + } + return csvLines.toString(); + } + + /** + * Test main method to check the marshalling and unmarshalling of strings in and out of CSV format + * + * @param args Not used + * @throws Exception Thrown if something goes wrong + */ + public static void main(String[] args) throws Exception + { + // Test the CSV parsing + String[] csv = {"id,\"dc.title\",dc.contributor.author,dc.description.abstract", + "1,Easy line,\"Lewis, Stuart\",A nice short abstract", + "2,Two authors,\"Lewis, Stuart||Bloggs, Joe\",Two people wrote this item", + "3,Three authors,\"Lewis, Stuart||Bloggs, Joe||Loaf, Meat\",Three people wrote this item", + "4,\"Two line\ntitle\",\"Lewis, Stuart\",abstract", + "5,\"\"\"Embedded quotes\"\" here\",\"Lewis, Stuart\",\"Abstract with\ntwo\nnew lines\""}; + + // Write the string to a file + String filename = "test.csv"; + BufferedWriter out = new BufferedWriter( + new OutputStreamWriter( + new FileOutputStream(filename), "UTF8")); + for (String csvLine : csv) { + out.write(csvLine + "\n"); + } + out.flush(); + out.close(); + System.gc(); + + // test the CSV parsing + DSpaceCSV dcsv = new DSpaceCSV(new File(filename)); + String[] lines = dcsv.getCSVLinesAsStringArray(); + for (String line : lines) + { + System.out.println(line); + } + + // Delete the test file + File toDelete = new File(filename); + toDelete.delete(); + } +} diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSVLine.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSVLine.java new file mode 100644 index 0000000000..4bf625427f --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/DSpaceCSVLine.java @@ -0,0 +1,212 @@ +/* + * DSpaceCSVLine.java + * + * Version: $Revision$ + * + * Date: $Date$ + * + * 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.app.bulkedit; + +import java.util.ArrayList; +import java.util.Hashtable; +import java.util.Enumeration; +import java.util.Iterator; + +/** + * Utility class to store a line from a CSV file + * + * @author Stuart Lewis + */ +public class DSpaceCSVLine +{ + /** The item id of the item represented by this line. -1 is for a new item */ + private int id; + + /** The elements in this line in a hashtable, keyed by the metadata type */ + private Hashtable items; + + /** + * Create a new CSV line + * + * @param id The item ID of the line + */ + public DSpaceCSVLine(int id) + { + // Store the ID + separator, and initalise the hashtable + this.id = id; + items = new Hashtable(); + } + + /** + * Create a new CSV line for a new item + */ + public DSpaceCSVLine() + { + // Set the ID to be -1, and initalise the hashtable + this.id = -1; + this.items = new Hashtable(); + } + + /** + * Get the item ID that this line represents + * + * @return The item ID + */ + public int getID() + { + // Return the ID + return id; + } + + /** + * Add a new metadata value to this line + * + * @param key The metadata key (e.g. dc.contributor.author) + * @param value The metadata value + */ + public void add(String key, String value) + { + // Create the array list if we need to + if (items.get(key) == null) + { + items.put(key, new ArrayList()); + } + + // Store the item if it is not null + if (value != null) + { + items.get(key).add(value); + } + } + + /** + * Get all the values that match the given metadata key. Will be null if none exist. + * + * @param key The metadata key + * @return All the elements that match + */ + public ArrayList get(String key) + { + // Return any relevant values + return items.get(key); + } + + /** + * Get all the metadata keys that are represented in this line + * + * @return An enumeration of all the keys + */ + public Enumeration keys() + { + // Return the keys + return items.keys(); + } + + /** + * Write this line out as a CSV formatted string, in the order given by the headings provided + * + * @param headings The headings which define the order the elements must be presented in + * @return The CSV formatted String + */ + protected String toCSV(ArrayList headings) + { + // Add the id + String bits = "\"" + id + "\"" + DSpaceCSV.fieldSeparator; + bits += valueToCSV(items.get("collection")) + DSpaceCSV.fieldSeparator; + + // Add the rest of the elements + Iterator i = headings.iterator(); + String key; + while (i.hasNext()) + { + key = i.next(); + if ((items.get(key) != null) && (!"collection".equals(key))) + { + bits = bits + valueToCSV(items.get(key)); + } + + if (i.hasNext()) + { + bits = bits + DSpaceCSV.fieldSeparator; + } + } + return bits; + } + + /** + * Internal method to create a CSV formatted String joining a given set of elements + * + * @param values The values to create the string from + * @return The line as a CSV formatted String + */ + private String valueToCSV(ArrayList values) + { + // Concatenate any fields together + String s = ""; + + // Check there is some content + if (values == null) + { + return s; + } + + // Get on with the work + if (values.size() == 1) + { + s = values.get(0); + } + else + { + Iterator i = values.iterator(); + while (i.hasNext()) + { + s = s + i.next(); + if (i.hasNext()) + { + s = s + DSpaceCSV.valueSeparator; + } + } + } + + // Replace internal quotes with two sets of quotes + s = s.replaceAll("\"", "\"\""); + + // Wrap in quotes + s = "\"" + s + "\""; + + // Return the csv formatted string + return s; + } +} + diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExport.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExport.java new file mode 100644 index 0000000000..a2639eb603 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataExport.java @@ -0,0 +1,279 @@ +/* + * MetadataExport.java + * + * Version: $Revision$ + * + * Date: $Date$ + * + * 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.app.bulkedit; + +import org.apache.commons.cli.*; +import org.apache.log4j.Logger; + +import org.dspace.content.*; +import org.dspace.core.Constants; +import org.dspace.core.Context; +import org.dspace.handle.HandleManager; + +import java.util.ArrayList; +import java.sql.SQLException; + +/** + * Metadata exporter to allow the batch export of metadata into a file + * + * @author Stuart Lewis + */ +public class MetadataExport +{ + /** The Context */ + Context c; + + /** The items to export */ + ItemIterator toExport; + + /** log4j logger */ + private static Logger log = Logger.getLogger(MetadataExport.class); + + public MetadataExport(Context c, ItemIterator toExport) + { + // Store the export settings + this.c = c; + this.toExport = toExport; + } + + /** + * Method to export a community (and sub-communites and collections) + * + * @param c The Context + * @param toExport The Community to export + */ + public MetadataExport(Context c, Community toExport) + { + try + { + // Try to export the community + this.c = c; + this.toExport = new ItemIterator(c, buildFromCommunity(toExport, new ArrayList(), 0)); + } + catch (SQLException sqle) + { + // Something went wrong... + System.err.println("Error running exporter:"); + sqle.printStackTrace(System.err); + System.exit(1); + } + } + + /** + * Build an array list of item ids that are in a community (include sub-communities and collections) + * + * @param community The community to build from + * @param itemIDs The itemID (used for recuriosn - use an empty ArrayList) + * @param indent How many spaces to use when writing out the names of items added + * @return The list of item ids + * @throws SQLException + */ + private ArrayList buildFromCommunity(Community community, ArrayList itemIDs, int indent) + throws SQLException + { + // Add all the collections + Collection[] collections = community.getCollections(); + for (Collection collection : collections) + { + for (int i = 0; i < indent; i++) System.out.print(" "); + ItemIterator items = collection.getAllItems(); + while (items.hasNext()) + { + int id = items.next().getID(); + // Only add if not already included (so mapped items only appear once) + if (!itemIDs.contains(id)) + { + itemIDs.add(id); + } + } + } + + // Add all the sub-communities + Community[] communities = community.getSubcommunities(); + for (Community subCommunity : communities) + { + for (int i = 0; i < indent; i++) System.out.print(" "); + buildFromCommunity(subCommunity, itemIDs, indent + 1); + } + + return itemIDs; + } + + /** + * Run the export + * + * @return the exported CSV lines + */ + public DSpaceCSV export() + { + try + { + // Process each item + DSpaceCSV csv = new DSpaceCSV(); + while (toExport.hasNext()) + { + csv.addItem(toExport.next()); + } + + // Return the results + return csv; + } + catch (Exception e) + { + return null; + } + } + + /** + * Print the help message + * + * @param options The command line options the user gave + * @param exitCode the system exit code to use + */ + private static void printHelp(Options options, int exitCode) + { + // print the help message + HelpFormatter myhelp = new HelpFormatter(); + myhelp.printHelp("MetadataExport\n", options); + System.out.println("\nfull export: metadataexport -f filename"); + System.out.println("partial export: metadataexport -i handle -f filename"); + System.exit(exitCode); + } + + /** + * main method to run the metadata exporter + * + * @param argv the command line arguments given + */ + public static void main(String[] argv) throws Exception + { + // Create an options object and populate it + CommandLineParser parser = new PosixParser(); + + Options options = new Options(); + + options.addOption("i", "id", true, "ID or handle of thing to export (item, collection, or community)"); + options.addOption("f", "file", true, "destination where you want file written"); + options.addOption("h", "help", false, "help"); + + CommandLine line = null; + + try + { + line = parser.parse(options, argv); + } + catch (ParseException pe) + { + System.err.println("Error with commands."); + printHelp(options, 1); + } + + if (line.hasOption('h')) + { + printHelp(options, 0); + } + + // Check a filename is given + if (!line.hasOption('f')) + { + System.err.println("Required parameter -f missing!"); + printHelp(options, 1); + } + String filename = line.getOptionValue('f'); + + // Create a context + Context c = new Context(); + c.turnOffAuthorisationSystem(); + + // The things we'll export + ItemIterator toExport = null; + MetadataExport exporter = null; + + // Check we have an item OK + if (!line.hasOption('i')) + { + System.out.println("Exporting whole repository WARNING: May take some time!"); + exporter = new MetadataExport(c, Item.findAll(c)); + } + else + { + String handle = line.getOptionValue('i'); + DSpaceObject dso = HandleManager.resolveToObject(c, handle); + if (dso == null) + { + System.err.println("Item '" + handle + "' does not resolve to an item in your repository!"); + printHelp(options, 1); + } + + if (dso.getType() == Constants.ITEM) + { + System.out.println("Exporting item '" + dso.getName() + "' (" + handle + ")"); + ArrayList item = new ArrayList(); + item.add(dso.getID()); + exporter = new MetadataExport(c, new ItemIterator(c, item)); + } + else if (dso.getType() == Constants.COLLECTION) + { + System.out.println("Exporting collection '" + dso.getName() + "' (" + handle + ")"); + Collection collection = (Collection)dso; + toExport = collection.getAllItems(); + exporter = new MetadataExport(c, toExport); + } + else if (dso.getType() == Constants.COMMUNITY) + { + System.out.println("Exporting community '" + dso.getName() + "' (" + handle + ")"); + exporter = new MetadataExport(c, (Community)dso); + } + else + { + System.err.println("Error identifying '" + handle + "'"); + System.exit(1); + } + } + + // Perform the export + DSpaceCSV csv = exporter.export(); + + // Save the files to the file + csv.save(filename); + + // Finsh off and tidy up + c.restoreAuthSystemState(); + c.complete(); + } +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java new file mode 100644 index 0000000000..bc45e4d871 --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImport.java @@ -0,0 +1,1018 @@ +/* + * MetadataImport.java + * + * Version: $Revision$ + * + * Date: $Date$ + * + * 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.app.bulkedit; + +import org.apache.commons.cli.*; +import org.apache.log4j.Logger; + +import org.dspace.content.*; +import org.dspace.core.Context; +import org.dspace.core.Constants; +import org.dspace.authorize.AuthorizeException; +import org.dspace.handle.HandleManager; +import org.dspace.eperson.EPerson; +import org.dspace.workflow.WorkflowManager; + +import java.util.ArrayList; +import java.util.Enumeration; +import java.io.File; +import java.io.InputStreamReader; +import java.io.BufferedReader; +import java.io.IOException; +import java.sql.SQLException; + +/** + * Metadata importer to allow the batch import of metadata from a file + * + * @author Stuart Lewis + */ +public class MetadataImport +{ + /** The Context */ + Context c; + + /** The lines to import */ + ArrayList toImport; + + /** log4j logger */ + private static Logger log = Logger.getLogger(MetadataImport.class); + + /** + * Create an instance of the metadata importer. Requires a context and an array of CSV lines + * to examine. + * + * @param c The context + * @param toImport An array of CSV lines to examine + */ + public MetadataImport(Context c, ArrayList toImport) + { + // Store the import settings + this.c = c; + this.toImport = toImport; + } + + /** + * Run an import. The import can either be read-only to detect changes, or + * can write changes as it goes. + * + * @param change Whether or not to write the changes to the database + * @param useWorkflow Whether the workflows should be used when creating new items + * @param workflowNotify If the workflows should be used, whether to send notifications or not + * @param useTemplate Use collection template if create new item + * @return An array of BulkEditChange elements representing the items that have changed + * + * @throws MetadataImportException if something goes wrong + */ + public ArrayList runImport(boolean change, + boolean useWorkflow, + boolean workflowNotify, + boolean useTemplate) throws MetadataImportException + { + // Store the changes + ArrayList changes = new ArrayList(); + + // Make the changes + try + { + // Process each change + for (DSpaceCSVLine line : toImport) + { + // Get the DSpace item to compare with + int id = line.getID(); + + // Is this a new item? + if (id != -1) + { + // Get the item + Item item = Item.find(c, id); + BulkEditChange whatHasChanged = new BulkEditChange(item); + + // Has it moved collection? + ArrayList collections = line.get("collection"); + if (collections != null) + { + // Sanity check we're not orphaning it + if (collections.size() == 0) + { + throw new MetadataImportException("Missing collection from item " + item.getHandle()); + } + Collection[] actualCollections = item.getCollections(); + compare(item, collections, actualCollections, whatHasChanged, change); + } + + // Iterate through each metadata element in the csv line + Enumeration e = line.keys(); + while (e.hasMoreElements()) + { + // Get the values we already have + String md = e.nextElement(); + if (!"id".equals(md)) + { + // Get the values from the CSV + String[] fromCSV = line.get(md).toArray(new String[line.get(md).size()]); + + // Compare + compare(item, fromCSV, change, md, whatHasChanged); + } + } + + // Only record if changes have been made + if (whatHasChanged.hasChanges()) + { + changes.add(whatHasChanged); + } + } + else + { + // This is marked as a new item, so no need to compare + + // First check a user is set, otherwise this can't happen + if (c.getCurrentUser() == null) + { + throw new MetadataImportException("When adding new items, a user must be specified with the -e option"); + } + + // Iterate through each metadata element in the csv line + Enumeration e = line.keys(); + BulkEditChange whatHasChanged = new BulkEditChange(); + while (e.hasMoreElements()) + { + // Get the values we already have + String md = e.nextElement(); + if (!"id".equals(md)) + { + // Get the values from the CSV + String[] fromCSV = line.get(md).toArray(new String[line.get(md).size()]); + + // Add all the values from the CSV line + add(fromCSV, md, whatHasChanged); + } + } + + // Check it has an owning collection + if (line.get("collection") == null) + { + throw new MetadataImportException("New items must have a 'collection' assiged in the form of a handle"); + } + + // Check collections are really collections + ArrayList collections = line.get("collection"); + ArrayList check = new ArrayList(); + for (int i = 0; i < collections.size(); i++) + { + String handle = collections.get(i); + Collection collection; + try + { + collection = (Collection)HandleManager.resolveToObject(c, handle); + // Check for duplicate + if (check.contains(collection)) + { + throw new MetadataImportException("Duplicate collection assignement detected in new item! " + handle); + } + else + { + check.add(collection); + } + } + catch (Exception ex) + { + throw new MetadataImportException("'" + handle + "' is not a Collection! You must specify a valid collection for new items"); + } + } + + // Record the addition to collections + for (int i = 0; i < collections.size(); i++) + { + String handle = collections.get(i); + Collection extra = (Collection)HandleManager.resolveToObject(c, handle); + whatHasChanged.registerNewOwningCollection(extra); + } + + // Create the new item? + if (change) + { + // Create the item + String collectionHandle = line.get("collection").get(0); + Collection collection = (Collection)HandleManager.resolveToObject(c, collectionHandle); + WorkspaceItem wsItem = WorkspaceItem.create(c, collection, useTemplate); + Item item = wsItem.getItem(); + + // Add the metadata to the item + for (DCValue dcv : whatHasChanged.getAdds()) + { + item.addMetadata(dcv.schema, + dcv.element, + dcv.qualifier, + dcv.language, + dcv.value); + } + + // Should the workflow be used? + if ((useWorkflow) && (workflowNotify)) + { + WorkflowManager.start(c, wsItem); + } + else if (useWorkflow) + { + WorkflowManager.startWithoutNotify(c, wsItem); + } + else + { + // Install the item + InstallItem.installItem(c, wsItem); + } + + // Add to extra collections + if (line.get("collection").size() > 0) + { + for (int i = 1; i < collections.size(); i++) + { + String handle = collections.get(i); + Collection extra = (Collection)HandleManager.resolveToObject(c, handle); + extra.addItem(item); + } + } + + // Commit changes to the object + c.commit(); + whatHasChanged.setItem(item); + } + + // Record the changes + changes.add(whatHasChanged); + } + } + } + catch (MetadataImportException mie) + { + throw mie; + } + catch (Exception e) + { + e.printStackTrace(); + } + + // Return the changes + return changes; + } + + /** + * Compare an item metadata with a line from CSV, and optionally update the item + * + * @param item The current item metadata + * @param fromCSV The metadata from the CSV file + * @param change Whether or not to make the update + * @param md The element to compare + * @param changes The changes object to populate + * + * @throws SQLException if there is a problem accessing a Collection from the database, from its handle + * @throws AuthorizeException if there is an authorization problem with permissions + */ + private void compare(Item item, String[] fromCSV, boolean change, + String md, BulkEditChange changes) throws SQLException, AuthorizeException + { + // Don't compare collections + if ("collection".equals(md)) + { + return; + } + + // Make a String array of the current values stored in this element + // First, strip of language if it is there + String language = null; + if (md.contains("[")) + { + String[] bits = md.split("\\["); + language = bits[1].substring(0, bits[1].length() - 1); + } + String[] bits = md.split("\\."); + String schema = bits[0]; + String element = bits[1]; + // If there is a language on the element, strip if off + if (element.contains("[")) + { + element = element.substring(0, element.indexOf("[")); + } + String qualifier = null; + if (bits.length > 2) + { + qualifier = bits[2]; + + // If there is a language, strip if off + if (qualifier.contains("[")) + { + qualifier = qualifier.substring(0, qualifier.indexOf("[")); + } + } + DCValue[] current = item.getMetadata(schema, element, qualifier, language); + + String[] dcvalues = new String[current.length]; + int i = 0; + for (DCValue dcv : current) + { + dcvalues[i] = dcv.value; + i++; + } + + // Compare from csv->current + for (String value : dcvalues) + { + // Look to see if it should be removed + DCValue dcv = new DCValue(); + dcv.schema = schema; + dcv.element = element; + dcv.qualifier = qualifier; + dcv.language = language; + dcv.value = value; + if ((value != null) && (!"".equals(value)) && (!contains(value, fromCSV))) + { + // Remove it + changes.registerRemove(dcv); + } + else + { + // Keep it + changes.registerConstant(dcv); + } + } + + // Compare from current->csv + for (String value : fromCSV) + { + // Look to see if it should be added + DCValue dcv = new DCValue(); + dcv.schema = schema; + dcv.element = element; + dcv.qualifier = qualifier; + dcv.language = language; + dcv.value = value; + if ((value != null) && (!"".equals(value)) && (!contains(value, dcvalues))) + { + changes.registerAdd(dcv); + } + } + + // Update the item if it has changed + if ((change) && + ((changes.getAdds().size() > 0) || (changes.getRemoves().size() > 0))) + { + // Get the complete list of what values should now be in that element + ArrayList list = changes.getComplete(); + ArrayList values = new ArrayList(); + for (DCValue value : list) + { + if ((qualifier == null) && (language == null)) + { + if ((schema.equals(value.schema)) && + (element.equals(value.element)) && + (value.qualifier == null) && + (value.language == null)) + { + values.add(value.value); + } + } + else if (qualifier == null) + { + if ((schema.equals(value.schema)) && + (element.equals(value.element)) && + (language.equals(value.language)) && + (value.qualifier == null)) + { + values.add(value.value); + } + } + else if (language == null) + { + if ((schema.equals(value.schema)) && + (element.equals(value.element)) && + (qualifier.equals(value.qualifier)) && + (value.language == null)) + { + values.add(value.value); + } + } + else + { + if ((schema.equals(value.schema)) && + (element.equals(value.element)) && + (qualifier.equals(value.qualifier)) && + (language.equals(value.language))) + { + values.add(value.value); + } + } + } + + // Set those values + item.clearMetadata(schema, element, qualifier, language); + String[] theValues = values.toArray(new String[values.size()]); + item.addMetadata(schema, element, qualifier, language, theValues); + item.update(); + } + } + + /** + * Compare changes between an items owning collections and what is in the CSV file + * + * @param item The item in question + * @param collections The collection handles fro mthe CSV file + * @param actualCollections The Collections from the actual item + * @param bechange The bulkedit change object for this item + * @param change Whether or not to actuate a change + * + * @throws SQLException if there is a problem accessing a Collection from the database, from its handle + * @throws AuthorizeException if there is an authorization problem with permissions + * @throws IOException Can be thrown when moving items in communities + */ + private void compare(Item item, + ArrayList collections, + Collection[] actualCollections, + BulkEditChange bechange, + boolean change) throws SQLException, AuthorizeException, IOException, MetadataImportException + { + // First, loop through the strings from the CSV + for (String csvcollection : collections) + { + // Look for it in the actual list of Collections + boolean found = false; + for (Collection collection : actualCollections) + { + // Is it there? + if (csvcollection.equals(collection.getHandle())) + { + found = true; + } + } + + // Was it found? + if (!found) + { + // Register than change + DSpaceObject dso = HandleManager.resolveToObject(c, csvcollection); + if ((dso == null) || (dso.getType() != Constants.COLLECTION)) + { + throw new MetadataImportException("Collection defined for item " + item.getID() + + " (" + item.getHandle() + ") is not a collection"); + } + else + { + Collection col = (Collection)dso; + bechange.registerNewOwningCollection(col); + + // Execute the change + if (change) + { + // Add the item to the community + col.addItem(item); + } + } + } + } + + // Second, loop through the strings from the current item + for (Collection collection : actualCollections) + { + // Look for it in the actual list of Collections + boolean found = false; + for (String csvcollection : collections) + { + // Is it there? + if (collection.getHandle().equals(csvcollection)) + { + found = true; + } + } + + // Was it found? + if (!found) + { + // Reocrd that it isn't there any more + bechange.registerOldOwningCollection(collection); + + // Execute the change + if (change) + { + // Sanity check it is in another collection + if (item.getCollections().length > 1) + { + collection.removeItem(item); + } + else + { + throw new MetadataImportException("Not removing item " + item.getHandle() + + " from collection " + collection.getHandle() + + " as it would leave it oprhaned!"); + } + } + } + } + } + + /** + * Add an item metadata with a line from CSV, and optionally update the item + * + * @param fromCSV The metadata from the CSV file + * @param md The element to compare + * @param changes The changes object to populate + * + * @throws SQLException when an SQL error has occured (querying DSpace) + * @throws AuthorizeException If the user can't make the changes + */ + private void add(String[] fromCSV, String md, BulkEditChange changes) + throws SQLException, AuthorizeException + { + // Don't add owning collection + if ("collection".equals(md)) + { + return; + } + + // Make a String array of the values + // First, strip of language if it is there + String language = null; + if (md.contains("[")) + { + String[] bits = md.split("\\["); + language = bits[1].substring(0, bits[1].length() - 1); + } + String[] bits = md.split("\\."); + String schema = bits[0]; + String element = bits[1]; + // If there is a language on the element, strip if off + if (element.contains("[")) + { + element = element.substring(0, element.indexOf("[")); + } + String qualifier = null; + if (bits.length > 2) + { + qualifier = bits[2]; + + // If there is a language, strip if off + if (qualifier.contains("[")) + { + qualifier = qualifier.substring(0, qualifier.indexOf("[")); + } + } + + // Add all the values + for (String value : fromCSV) + { + // Look to see if it should be removed + DCValue dcv = new DCValue(); + dcv.schema = schema; + dcv.element = element; + dcv.qualifier = qualifier; + dcv.language = language; + dcv.value = value; + + // Add it + if ((value != null) && (!"".equals(value))) + { + changes.registerAdd(dcv); + } + } + } + + /** + * Method to find if a String occurs in an array of Strings + * + * @param needle The String to look for + * @param haystack The array of Strings to search through + * @return Whether or not it is contained + */ + private boolean contains(String needle, String[] haystack) + { + // Look for the needle in the haystack + for (String examine : haystack) + { + if (clean(examine).equals(clean(needle))) + { + return true; + } + } + return false; + } + + /** + * Clean elements before comparing + * + * @param in The element to clean + * @return The cleaned up element + */ + private String clean(String in) + { + // Check for nulls + if (in == null) + { + return null; + } + + // Remove newlines as different operating systems sometimes use different formats + return in.replaceAll("\r\n", "").replaceAll("\n", "").trim(); + } + + /** + * Print the help message + * + * @param options The command line options the user gave + * @param exitCode the system exit code to use + */ + private static void printHelp(Options options, int exitCode) + { + // print the help message + HelpFormatter myhelp = new HelpFormatter(); + myhelp.printHelp("MetatadataImport\n", options); + System.out.println("\nmetadataimport: MetadataImport -f filename"); + System.exit(exitCode); + } + + /** + * Display the changes that have been detected, or that have been made + * + * @param changes The changes detected + * @param changed Whether or not the changes have been made + * @return The number of items that have changed + */ + private static int displayChanges(ArrayList changes, boolean changed) + { + // Display the changes + int changeCounter = 0; + for (BulkEditChange change : changes) + { + // Get the changes + ArrayList adds = change.getAdds(); + ArrayList removes = change.getRemoves(); + ArrayList newCollections = change.getNewOwningCollections(); + ArrayList oldCollections = change.getOldOwningCollections(); + if ((adds.size() > 0) || (removes.size() > 0) || + (newCollections.size() > 0) || (oldCollections.size() > 0)) + { + // Show the item + Item i = change.getItem(); + + System.out.println("-----------------------------------------------------------"); + if (!change.isNewItem()) + { + System.out.println("Changes for item: " + i.getID() + " (" + i.getHandle() + ")"); + } + else + { + System.out.print("New item: "); + if (i != null) + { + if (i.getHandle() != null) + { + System.out.print(i.getID() + " (" + i.getHandle() + ")"); + } + else + { + System.out.print(i.getID() + " (in workflow)"); + } + } + System.out.println(); + } + changeCounter++; + } + + // Show new collections + for (Collection c : newCollections) + { + String cHandle = c.getHandle(); + String cName = c.getName(); + if (!changed) + { + System.out.print(" + Add to collection (" + cHandle + "): "); + } + else + { + System.out.print(" + Added to collection (" + cHandle + "): "); + } + System.out.println(cName); + } + + // Show old collections + for (Collection c : oldCollections) + { + String cHandle = c.getHandle(); + String cName = c.getName(); + if (!changed) + { + System.out.print(" + Remove from collection (" + cHandle + "): "); + } + else + { + System.out.print(" + Removed from collection (" + cHandle + "): "); + } + System.out.println(cName); + } + + // Show additions + for (DCValue dcv : adds) + { + String md = dcv.schema + "." + dcv.element; + if (dcv.qualifier != null) + { + md += "." + dcv.qualifier; + } + if (dcv.language != null) + { + md += "[" + dcv.language + "]"; + } + if (!changed) + { + System.out.print(" + Add (" + md + "): "); + } + else + { + System.out.print(" + Added (" + md + "): "); + } + System.out.println(dcv.value); + } + + // Show removals + for (DCValue dcv : removes) + { + String md = dcv.schema + "." + dcv.element; + if (dcv.qualifier != null) + { + md += "." + dcv.qualifier; + } + if (dcv.language != null) + { + md += "[" + dcv.language + "]"; + } + if (!changed) + { + System.out.println(" - Remove (" + md + "): " + dcv.value); + } + else + { + System.out.println(" - Removed (" + md + "): " + dcv.value); + } + } + } + return changeCounter; + } + + /** + * main method to run the metadata exporter + * + * @param argv the command line arguments given + * + * @throws Exception Thrown if something goes wrong with the import + */ + public static void main(String[] argv) + { + // Create an options object and populate it + CommandLineParser parser = new PosixParser(); + + Options options = new Options(); + + options.addOption("f", "file", true, "source file"); + options.addOption("e", "email", true, "email address or user id of user (required if adding new items)"); + options.addOption("s", "silent", false, "silent operation - doesn't request confirmation of changes USE WITH CAUTION"); + options.addOption("w", "workflow", false, "workflow - when adding new items, use collection workflow"); + options.addOption("n", "notify", false, "notify - when adding new items using a workflow, send notification emails"); + options.addOption("t", "template", false, "template - when adding new items, use the collection template (if it exists)"); + options.addOption("h", "help", false, "help"); + + // Parse the command line arguments + CommandLine line; + try + { + line = parser.parse(options, argv); + } + catch (ParseException pe) + { + System.err.println("Error parsing command line arguments: " + pe.getMessage()); + System.exit(1); + return; + } + + if (line.hasOption('h')) + { + printHelp(options, 0); + } + + // Check a filename is given + if (!line.hasOption('f')) + { + System.err.println("Required parameter -f missing!"); + printHelp(options, 1); + } + String filename = line.getOptionValue('f'); + + // Option to apply template to new items + boolean useTemplate = false; + if (line.hasOption('t')) + { + useTemplate = true; + } + + // Options for workflows, and workflow notifications for new items + boolean useWorkflow = false; + boolean workflowNotify = false; + if (line.hasOption('w')) + { + useWorkflow = true; + if (line.hasOption('n')) + { + workflowNotify = true; + } + } + else if (line.hasOption('n')) + { + System.err.println("Invalid option 'n': (notify) can only be specified with the 'w' (workflow) option."); + System.exit(1); + } + + // Create a context + Context c; + try + { + c = new Context(); + c.turnOffAuthorisationSystem(); + } + catch (Exception e) + { + System.err.println("Unable to create a new DSpace Context: " + e.getMessage()); + System.exit(1); + return; + } + + // Find the EPerson, assign to context + try + { + if (line.hasOption('e')) + { + EPerson eperson = null; + String e = line.getOptionValue('e'); + if (e.indexOf('@') != -1) + { + eperson = EPerson.findByEmail(c, e); + } + else + { + eperson = EPerson.find(c, Integer.parseInt(e)); + } + + if (eperson == null) + { + System.out.println("Error, eperson cannot be found: " + e); + System.exit(1); + } + c.setCurrentUser(eperson); + } + } catch (Exception e) + { + System.err.println("Unable to find DSpace user: " + e.getMessage()); + System.exit(1); + return; + } + + // Is this a silent run? + boolean change = false; + + // Read lines from the CSV file + DSpaceCSV csv; + try + { + csv = new DSpaceCSV(new File(filename)); + } + catch (Exception e) + { + System.err.println("Error reading file: " + e.getMessage()); + System.exit(1); + return; + } + + // Perform the first import - just higlight differences + MetadataImport importer = new MetadataImport(c, csv.getCSVLines()); + ArrayList changes; + + if (!line.hasOption('s')) + { + // See what has changed + try + { + changes = importer.runImport(false, useWorkflow, workflowNotify, useTemplate); + } + catch (MetadataImportException mie) + { + System.err.println("Error: " + mie.getMessage()); + System.exit(1); + return; + } + + // Display the changes + int changeCounter = displayChanges(changes, false); + + // If there were changes, ask if we should execute them + if (changeCounter > 0) + { + try + { + // Ask the user if they want to make the changes + System.out.println("\n" + changeCounter + " item(s) will be changed\n"); + System.out.print("Do you want to make these changes? [y/n] "); + String yn = (new BufferedReader(new InputStreamReader(System.in))).readLine(); + if ("y".equalsIgnoreCase(yn)) + { + change = true; + } + else + { + System.out.println("No data has been changed."); + } + } + catch (IOException ioe) + { + System.err.println("Error: " + ioe.getMessage()); + System.err.println("No changes have been made"); + System.exit(1); + } + } + else + { + System.out.println("There were no changes detected"); + } + } + else + { + change = true; + } + + try + { + // If required, make the change + if (change) + { + try + { + // Make the changes + changes = importer.runImport(true, useWorkflow, workflowNotify, useTemplate); + } + catch (MetadataImportException mie) + { + System.err.println("Error: " + mie.getMessage()); + System.exit(1); + return; + } + + // Display the changes + displayChanges(changes, true); + + // Commit the change to the DB + c.commit(); + } + + // Finsh off and tidy up + c.restoreAuthSystemState(); + c.complete(); + } + catch (Exception e) + { + c.abort(); + System.err.println("Error commiting changes to database: " + e.getMessage()); + System.err.println("Aborting most recent changes."); + System.exit(1); + } + } +} \ No newline at end of file diff --git a/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImportException.java b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImportException.java new file mode 100644 index 0000000000..cc326b034d --- /dev/null +++ b/dspace-api/src/main/java/org/dspace/app/bulkedit/MetadataImportException.java @@ -0,0 +1,56 @@ +/* + * MetadataImportException.java + * + * Version: $Revision$ + * + * Date: $Date$ + * + * 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.app.bulkedit; + +/** + * Metadata importer exception + * + * @author Stuart Lewis + */ +public class MetadataImportException extends Exception +{ + /** + * Instantiate a new MetadataImportException + * + * @param message the error message + */ + public MetadataImportException(String message) + { + super(message); + } +} \ No newline at end of file diff --git a/dspace-api/src/main/resources/Messages.properties b/dspace-api/src/main/resources/Messages.properties index ee43bd164f..1618ce5828 100644 --- a/dspace-api/src/main/resources/Messages.properties +++ b/dspace-api/src/main/resources/Messages.properties @@ -281,6 +281,20 @@ jsp.dspace-admin.group-eperson-select.title = Select EPerson jsp.dspace-admin.group-group-select.add = Add Group jsp.dspace-admin.group-group-select.heading = Select Group to Add to Group {0} jsp.dspace-admin.group-group-select.title = Select Group +jsp.dspace-admin.metadataimport.title = Import metadata +jsp.dspace-admin.metadataimport.apply = Apply changes +jsp.dspace-admin.metadataimport.unknownerror = An unknown error has occured +jsp.dspace-admin.metadataimport.changesforitem = Changes for item +jsp.dspace-admin.metadataimport.newitem = New item +jsp.dspace-admin.metadataimport.addtocollection = Add to collection +jsp.dspace-admin.metadataimport.addedtocollection = Added to collection +jsp.dspace-admin.metadataimport.removefromcollection = Remove from collection +jsp.dspace-admin.metadataimport.removedfromcollection = Removed from collection +jsp.dspace-admin.metadataimport.add = Add +jsp.dspace-admin.metadataimport.added = Added +jsp.dspace-admin.metadataimport.remove = Remove +jsp.dspace-admin.metadataimport.removed = Removed +jsp.dspace-admin.metadataimport. jsp.dspace-admin.index.heading = Administration Tools jsp.dspace-admin.index.text = Please select an operation from the navigation bar on the left. jsp.dspace-admin.item-select.enter = Enter the Handle or internal item ID of the item you wish to select. @@ -490,6 +504,7 @@ jsp.general.goto = Go to jsp.general.home = DSpace Home jsp.general.id = ID jsp.general.location = In: +jsp.general.metadataexport.button = Export metadata jsp.general.mydspace = My DSpace jsp.general.orbrowse = or browse jsp.general.search.button = Go @@ -538,6 +553,7 @@ jsp.layout.navbar-admin.groups = Groups jsp.layout.navbar-admin.help = Help jsp.layout.navbar-admin.items = Items jsp.layout.navbar-admin.logout = Log Out +jsp.layout.navbar-admin.metadataimport = Import metadata jsp.layout.navbar-admin.metadataregistry = Metadata
Registry jsp.layout.navbar-admin.statistics = Statistics jsp.layout.navbar-admin.supervisors = Supervisors diff --git a/dspace-jspui/dspace-jspui-api/src/main/java/org/dspace/app/webui/servlet/MetadataExportServlet.java b/dspace-jspui/dspace-jspui-api/src/main/java/org/dspace/app/webui/servlet/MetadataExportServlet.java new file mode 100644 index 0000000000..0167c4f74c --- /dev/null +++ b/dspace-jspui/dspace-jspui-api/src/main/java/org/dspace/app/webui/servlet/MetadataExportServlet.java @@ -0,0 +1,131 @@ +/* + * MetadataExportServlet.java + * + * Version: $Revision$ + * + * Date: $Date$ + * + * 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.app.webui.servlet; + +import java.io.IOException; +import java.io.PrintWriter; +import java.sql.SQLException; +import java.util.ArrayList; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.log4j.Logger; +import org.dspace.app.bulkedit.MetadataExport; +import org.dspace.app.bulkedit.DSpaceCSV; +import org.dspace.app.webui.util.JSPManager; +import org.dspace.authorize.AuthorizeException; +import org.dspace.core.*; +import org.dspace.content.DSpaceObject; +import org.dspace.content.ItemIterator; +import org.dspace.content.Collection; +import org.dspace.content.Community; +import org.dspace.handle.HandleManager; + +/** + * Servlet to export metadata as CSV (comma separated values) + * + * @author Stuart Lewis + */ +public class MetadataExportServlet extends DSpaceServlet +{ + /** log4j category */ + private static Logger log = Logger.getLogger(MetadataExportServlet.class); + + /** + * Respond to a post request + * + * @param context a DSpace Context object + * @param request the HTTP request + * @param response the HTTP response + * + * @throws ServletException + * @throws IOException + * @throws SQLException + * @throws AuthorizeException + */ + protected void doDSPost(Context context, HttpServletRequest request, + HttpServletResponse response) throws ServletException, IOException, + SQLException, AuthorizeException + { + // Get the handle requested for the export + String handle = request.getParameter("handle"); + log.info(LogManager.getHeader(context, "metadataexport", "exporting_handle:" + handle)); + ItemIterator toExport = null; + MetadataExport exporter = null; + if (handle != null) + { + DSpaceObject thing = HandleManager.resolveToObject(context, handle); + if (thing != null) + { + if (thing.getType() == Constants.ITEM) + { + ArrayList item = new ArrayList(); + item.add(thing.getID()); + exporter = new MetadataExport(context, new ItemIterator(context, item)); + } + else if (thing.getType() == Constants.COLLECTION) + { + Collection collection = (Collection)thing; + toExport = collection.getAllItems(); + exporter = new MetadataExport(context, toExport); + } + else if (thing.getType() == Constants.COMMUNITY) + { + exporter = new MetadataExport(context, (Community)thing); + } + + // Perform the export + DSpaceCSV csv = exporter.export(); + + // Return the csv file + response.setContentType("text/csv; charset=UTF-8"); + response.setHeader("Content-Disposition", "attachment; filename=" + handle.replaceAll("/", "-") + ".csv"); + PrintWriter out = response.getWriter(); + out.write(csv.toString()); + out.flush(); + out.close(); + return; + } + } + + // Something has gone wrong + JSPManager.showIntegrityError(request, response); + } +} \ No newline at end of file diff --git a/dspace-jspui/dspace-jspui-api/src/main/java/org/dspace/app/webui/servlet/MetadataImportServlet.java b/dspace-jspui/dspace-jspui-api/src/main/java/org/dspace/app/webui/servlet/MetadataImportServlet.java new file mode 100644 index 0000000000..3d1c9685f5 --- /dev/null +++ b/dspace-jspui/dspace-jspui-api/src/main/java/org/dspace/app/webui/servlet/MetadataImportServlet.java @@ -0,0 +1,194 @@ +/* + * MetadataImportServlet.java + * + * Version: $Revision$ + * + * Date: $Date$ + * + * 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.app.webui.servlet; + +import java.io.*; +import java.sql.SQLException; +import java.util.ArrayList; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.http.HttpSession; + +import org.apache.log4j.Logger; +import org.dspace.app.webui.util.JSPManager; +import org.dspace.app.webui.util.FileUploadRequest; +import org.dspace.app.bulkedit.MetadataImport; +import org.dspace.app.bulkedit.DSpaceCSV; +import org.dspace.app.bulkedit.BulkEditChange; +import org.dspace.authorize.AuthorizeException; +import org.dspace.core.*; + +/** + * Servlet to import metadata as CSV (comma separated values) + * + * @author Stuart Lewis + */ +public class MetadataImportServlet extends DSpaceServlet +{ + /** log4j category */ + private static Logger log = Logger.getLogger(MetadataImportServlet.class); + + /** + * Respond to a post request for metadata bulk importing via csv + * + * @param context a DSpace Context object + * @param request the HTTP request + * @param response the HTTP response + * + * @throws ServletException + * @throws IOException + * @throws SQLException + * @throws AuthorizeException + */ + protected void doDSPost(Context context, HttpServletRequest request, + HttpServletResponse response) throws ServletException, IOException, + SQLException, AuthorizeException + { + // First, see if we have a multipart request (uploading a metadata file) + String contentType = request.getContentType(); + if ((contentType != null) && (contentType.indexOf("multipart/form-data") != -1)) + { + // Process the file uploaded + try + { + // Get the changes + log.info(LogManager.getHeader(context, "metadataimport", "loading file")); + ArrayList changes = processUpload(context, request); + log.debug(LogManager.getHeader(context, "metadataimport", changes.size() + " items with changes identifed")); + + // Were there any changes detected? + if (changes.size() != 0) + { + request.setAttribute("changes", changes); + request.setAttribute("changed", false); + JSPManager.showJSP(request, response, "/dspace-admin/metadataimport-showchanges.jsp"); + } + else + { + request.setAttribute("message", "No changes detected"); + JSPManager.showJSP(request, response, "/dspace-admin/metadataimport.jsp"); + } + } + catch (Exception e) + { + request.setAttribute("message", e.getMessage()); + log.debug(LogManager.getHeader(context, "metadataimport", "Error encountered while looking for changes: " + e.getMessage())); + JSPManager.showJSP(request, response, "/dspace-admin/metadataimport-error.jsp"); + } + } + else if ("confirm".equals(request.getParameter("type"))) + { + // Get the csv lines from the session + HttpSession session = request.getSession(true); + DSpaceCSV csv = (DSpaceCSV)session.getAttribute("csv"); + + // Make the changes + try + { + MetadataImport mImport = new MetadataImport(context, csv.getCSVLines()); + ArrayList changes = mImport.runImport(true, false, false, false); + + // Commit the changes + context.commit(); + log.debug(LogManager.getHeader(context, "metadataimport", changes.size() + " items changed")); + + // Blank out the session data + session.removeAttribute("csv"); + + request.setAttribute("changes", changes); + request.setAttribute("changed", true); + JSPManager.showJSP(request, response, "/dspace-admin/metadataimport-showchanges.jsp"); + } + catch (Exception e) + { + request.setAttribute("message", e.getMessage()); + log.debug(LogManager.getHeader(context, "metadataimport", "Error encountered while making changes: " + e.getMessage())); + JSPManager.showJSP(request, response, "/dspace-admin/metadataimport-error.jsp"); + } + } + else if ("cancel".equals(request.getParameter("type"))) + { + // Blank out the session data + HttpSession session = request.getSession(true); + session.removeAttribute("csv"); + + request.setAttribute("message", "Changes cancelled. No items have been modified."); + log.debug(LogManager.getHeader(context, "metadataimport", "Changes cancelled")); + JSPManager.showJSP(request, response, "/dspace-admin/metadataimport.jsp"); + } + else + { + // Show the upload screen + JSPManager.showJSP(request, response, "/dspace-admin/metadataimport.jsp"); + } + } + + protected void doDSGet(Context context, HttpServletRequest request, + HttpServletResponse response) throws ServletException, IOException, + SQLException, AuthorizeException + { + // Show the upload screen + JSPManager.showJSP(request, response, "/dspace-admin/metadataimport.jsp"); + } + + + private ArrayList processUpload(Context context, + HttpServletRequest request) throws Exception + { + // Wrap multipart request to get the submission info + FileUploadRequest wrapper = new FileUploadRequest(request); + File f = wrapper.getFile("file"); + + // Run the import + DSpaceCSV csv = new DSpaceCSV(f); + MetadataImport mImport = new MetadataImport(context, csv.getCSVLines()); + ArrayList changes = mImport.runImport(false, false, false, false); + + // Store the csv lines in the session + HttpSession session = request.getSession(true); + session.setAttribute("csv", csv); + + // Remove temp file + f.delete(); + + // Return the changes + return changes; + } +} \ No newline at end of file diff --git a/dspace-jspui/dspace-jspui-webapp/src/main/webapp/WEB-INF/web.xml b/dspace-jspui/dspace-jspui-webapp/src/main/webapp/WEB-INF/web.xml index 7845478b67..7c0c7f403f 100644 --- a/dspace-jspui/dspace-jspui-webapp/src/main/webapp/WEB-INF/web.xml +++ b/dspace-jspui/dspace-jspui-webapp/src/main/webapp/WEB-INF/web.xml @@ -278,6 +278,16 @@ org.dspace.app.webui.servlet.LogoutServlet + + metadataexport + org.dspace.app.webui.servlet.MetadataExportServlet + + + + metadataimport + org.dspace.app.webui.servlet.MetadataImportServlet + + metadata-field-registry org.dspace.app.webui.servlet.admin.MetadataFieldRegistryServlet @@ -546,6 +556,16 @@ /logout + + metadataexport + /dspace-admin/metadataexport + + + + metadataimport + /dspace-admin/metadataimport + + metadata-field-registry /dspace-admin/metadata-field-registry diff --git a/dspace-jspui/dspace-jspui-webapp/src/main/webapp/collection-home.jsp b/dspace-jspui/dspace-jspui-webapp/src/main/webapp/collection-home.jsp index eaf4fb7b33..ed452b20dd 100644 --- a/dspace-jspui/dspace-jspui-webapp/src/main/webapp/collection-home.jsp +++ b/dspace-jspui/dspace-jspui-webapp/src/main/webapp/collection-home.jsp @@ -309,6 +309,14 @@ + + +
+ + " /> +
+ + <% } %> diff --git a/dspace-jspui/dspace-jspui-webapp/src/main/webapp/community-home.jsp b/dspace-jspui/dspace-jspui-webapp/src/main/webapp/community-home.jsp index f96c54bda8..5de39c51ca 100644 --- a/dspace-jspui/dspace-jspui-webapp/src/main/webapp/community-home.jsp +++ b/dspace-jspui/dspace-jspui-webapp/src/main/webapp/community-home.jsp @@ -352,6 +352,14 @@ " /> + + + +
+ + " /> +
+ <% } %> diff --git a/dspace-jspui/dspace-jspui-webapp/src/main/webapp/display-item.jsp b/dspace-jspui/dspace-jspui-webapp/src/main/webapp/display-item.jsp index bdd23874c8..34ae2d09ea 100644 --- a/dspace-jspui/dspace-jspui-webapp/src/main/webapp/display-item.jsp +++ b/dspace-jspui/dspace-jspui-webapp/src/main/webapp/display-item.jsp @@ -61,9 +61,7 @@ <%@ taglib uri="http://www.dspace.org/dspace-tags.tld" prefix="dspace" %> -<%@ page import="org.dspace.app.webui.util.UIUtil" %> <%@ page import="org.dspace.content.Collection" %> -<%@ page import="org.dspace.content.Community" %> <%@ page import="org.dspace.content.DCValue" %> <%@ page import="org.dspace.content.Item" %> <%@ page import="org.dspace.core.ConfigurationManager" %> @@ -141,6 +139,10 @@ " /> +
+ + " /> +
diff --git a/dspace-jspui/dspace-jspui-webapp/src/main/webapp/dspace-admin/metadataimport-error.jsp b/dspace-jspui/dspace-jspui-webapp/src/main/webapp/dspace-admin/metadataimport-error.jsp new file mode 100644 index 0000000000..102a14771e --- /dev/null +++ b/dspace-jspui/dspace-jspui-webapp/src/main/webapp/dspace-admin/metadataimport-error.jsp @@ -0,0 +1,68 @@ +<%@ page import="javax.servlet.jsp.jstl.fmt.LocaleSupport" %> +<%-- +- Version: $Revision$ +- Date: $Date$ +- +- 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 their +- 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. +--%> + +<%-- + - Form to show an error from the metadata importer +--%> + +<%@ page contentType="text/html;charset=UTF-8" %> + +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> + +<%@ taglib uri="http://www.dspace.org/dspace-tags.tld" prefix="dspace" %> + +<% + String error = (String)request.getAttribute("error"); + if (error == null) + { + error = LocaleSupport.getLocalizedMessage(pageContext, "jsp.dspace-admin.metadataimport.unknownerror"); + } +%> + + + +

+ +

+ <%= error %> +

+ +
diff --git a/dspace-jspui/dspace-jspui-webapp/src/main/webapp/dspace-admin/metadataimport-showchanges.jsp b/dspace-jspui/dspace-jspui-webapp/src/main/webapp/dspace-admin/metadataimport-showchanges.jsp new file mode 100644 index 0000000000..814c86df1a --- /dev/null +++ b/dspace-jspui/dspace-jspui-webapp/src/main/webapp/dspace-admin/metadataimport-showchanges.jsp @@ -0,0 +1,194 @@ +<%-- +- Version: $Revision$ +- Date: $Date$ +- +- 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 their +- 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. +--%> + +<%-- + - Show the changes that might be made +--%> + +<%@ page contentType="text/html;charset=UTF-8" %> + +<%@ page import="org.dspace.app.bulkedit.BulkEditChange" %> +<%@ page import="java.util.ArrayList" %> +<%@ page import="org.dspace.content.Item" %> +<%@ page import="org.dspace.content.DCValue" %> +<%@ page import="org.dspace.content.Collection" %> + +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> + +<%@ taglib uri="http://www.dspace.org/dspace-tags.tld" prefix="dspace" %> + +<% + ArrayList changes = (ArrayList)request.getAttribute("changes"); + boolean changed = ((Boolean)request.getAttribute("changed")).booleanValue(); +%> + + + +

+ + + + <% + // Display the changes + int changeCounter = 0; + for (BulkEditChange change : changes) + { + // Get the changes + ArrayList adds = change.getAdds(); + ArrayList removes = change.getRemoves(); + ArrayList newCollections = change.getNewOwningCollections(); + ArrayList oldCollections = change.getOldOwningCollections(); + boolean isAChange = false; + if ((adds.size() > 0) || (removes.size() > 0) || + (newCollections.size() > 0) || (oldCollections.size() > 0)) + { + // Show the item + if (!change.isNewItem()) + { + Item i = change.getItem(); + %><% + } + else + { + %><% + } + changeCounter++; + isAChange = true; + } + + // Show new collections + for (Collection c : newCollections) + { + String cHandle = c.getHandle(); + String cName = c.getName(); + if (!changed) + { + %><% + } + else + { + %><% + } + } + + // Show old collections + for (Collection c : oldCollections) + { + String cHandle = c.getHandle(); + String cName = c.getName(); + if (!changed) + { + %><% + } + else + { + %><% + } + } + + // Show additions + for (DCValue dcv : adds) + { + String md = dcv.schema + "." + dcv.element; + if (dcv.qualifier != null) + { + md += "." + dcv.qualifier; + } + if (dcv.language != null) + { + md += "[" + dcv.language + "]"; + } + if (!changed) + { + %><% + } + else + { + %><% + } + } + + // Show removals + for (DCValue dcv : removes) + { + String md = dcv.schema + "." + dcv.element; + if (dcv.qualifier != null) + { + md += "." + dcv.qualifier; + } + if (dcv.language != null) + { + md += "[" + dcv.language + "]"; + } + if (!changed) + { + %><% + } + else + { + %><% + } + } + } + %> + +
: <%= i.getID() %> (<%= i.getHandle() %>)
:
(<%= cHandle %>): <%= cName %>
(<%= cHandle %>): <%= cName %>
(<%= cHandle %>): <%= cName %>
(<%= cHandle %>): <%= cName %>
(<%= md %>)<%= dcv.value %>
(<%= md %>)<%= dcv.value %>
(<%= md %>)<%= dcv.value %>
(<%= md %>)<%= dcv.value %>
+ + <% + if (!changed) + { + %> +

+ + + " /> +

+
+ + " /> +
+

+ <% + } + %> + + + +
diff --git a/dspace-jspui/dspace-jspui-webapp/src/main/webapp/dspace-admin/metadataimport.jsp b/dspace-jspui/dspace-jspui-webapp/src/main/webapp/dspace-admin/metadataimport.jsp new file mode 100644 index 0000000000..b2329fbdb2 --- /dev/null +++ b/dspace-jspui/dspace-jspui-webapp/src/main/webapp/dspace-admin/metadataimport.jsp @@ -0,0 +1,81 @@ +<%-- + - Version: $Revision$ + - Date: $Date$ + - + - 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 their + - 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. + --%> + +<%-- + - Form to upload a csv metadata file +--%> + +<%@ page contentType="text/html;charset=UTF-8" %> + +<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> + +<%@ taglib uri="http://www.dspace.org/dspace-tags.tld" prefix="dspace" %> + +<% + String message = (String)request.getAttribute("message"); + if (message == null) + { + message = ""; + } + else + { + message = "

" + message + "

"; + } +%> + + + +

+ +
+ + <%= message %> + +

+ +

+ +

+ " /> +

+ +
+ +
diff --git a/dspace-jspui/dspace-jspui-webapp/src/main/webapp/layout/navbar-admin.jsp b/dspace-jspui/dspace-jspui-webapp/src/main/webapp/layout/navbar-admin.jsp index 03a171a118..0cb23a3220 100644 --- a/dspace-jspui/dspace-jspui-webapp/src/main/webapp/layout/navbar-admin.jsp +++ b/dspace-jspui/dspace-jspui-webapp/src/main/webapp/layout/navbar-admin.jsp @@ -168,7 +168,7 @@ - + .gif" width="16" height="16"/> @@ -177,6 +177,15 @@ + + + + .gif" width="16" height="16"/> + + + + +   diff --git a/dspace/CHANGES b/dspace/CHANGES index 61acd88af4..9a9f5873ec 100644 --- a/dspace/CHANGES +++ b/dspace/CHANGES @@ -13,6 +13,7 @@ - [DS-190] Portuguese (pt_PT) translation Messages.properties JSP-UI v1.5.2 (Stuart Lewis) + - [DS-161] Bulk metadata editing - [DS-194] Give METS ingester configuration option to make use of collection templates - [DS-195] Allow the primary bitstream to be set in the item importer / exporter - [DS-196] METS exposed via OAI-PMH includes description.provenance information