/** *############################################################################ * A component of the Greenstone Librarian Interface, part of the Greenstone * digital library suite from the New Zealand Digital Library Project at the * University of Waikato, New Zealand. * * Author: Michael Dewsnip, NZDL Project, University of Waikato, NZ * * Copyright (C) 2005 New Zealand Digital Library Project * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. *############################################################################ */ package org.greenstone.gatherer.metadata; import java.io.*; import java.util.*; import org.greenstone.gatherer.DebugStream; import org.greenstone.gatherer.Gatherer; import org.greenstone.gatherer.collection.CollectionManager; import org.greenstone.gatherer.collection.CollectionTreeNode; import org.greenstone.gatherer.remote.RemoteGreenstoneServer; import org.greenstone.gatherer.util.XMLTools; import org.greenstone.gatherer.util.Utility; /** This class is a static class that manages the metadata.xml files */ public class MetadataXMLFileManager { static private ArrayList metadata_xml_files = new ArrayList(); /** The objects listening for MetadataChanged events. */ static private ArrayList metadata_changed_listeners = new ArrayList(); /** Keep track of which metadata.xml files have been modified so we can upload them to the server */ static private ArrayList modified_metadata_xml_files = new ArrayList(); /** For non-accumulating metadata (gs.FilenameEncoding), need the metadata.xml files * sorted in top-down folder level order, which is achieved through this Comparator. * By declaring a static class member here, we avoid recreating this object for every * comparison, which would otherwise have resulted in a constructor call each time. */ static private MetadataXMLFileComparator metadataXMLFileComparator = new MetadataXMLFileComparator(); static public void addMetadata(CollectionTreeNode file_node, ArrayList metadata_values) { addMetadata(new CollectionTreeNode[] { file_node }, metadata_values); } static public void addMetadata(CollectionTreeNode[] file_nodes, MetadataValue metadata_value) { ArrayList metadata_values = new ArrayList(); metadata_values.add(metadata_value); addMetadata(file_nodes, metadata_values); } static public void addMetadata(CollectionTreeNode[] file_nodes, ArrayList metadata_values) { // Check the list of metadata values is non-empty if (metadata_values.isEmpty()) { return; } // Add the metadata to each file node in turn for (int i = 0; i < file_nodes.length; i++) { File current_file = file_nodes[i].getFile(); DebugStream.println("Adding metadata to " + current_file.getAbsolutePath() + " - hex: " + Utility.debugUnicodeString(current_file.getAbsolutePath())); // Find which metadata.xml file needs editing boolean applicable_metadata_xml_file_found = false; File current_file_directory = (current_file.isDirectory() ? current_file : current_file.getParentFile()); String current_file_directory_path = current_file_directory.getAbsolutePath(); for (int j = 0; j < metadata_xml_files.size(); j++) { MetadataXMLFile metadata_xml_file = (MetadataXMLFile) metadata_xml_files.get(j); // This metadata.xml file is only applicable if it is at the same level as the file if (current_file_directory_path.equals(metadata_xml_file.getParentFile().getAbsolutePath())) { applicable_metadata_xml_file_found = true; metadata_xml_file.addMetadata(file_nodes[i], metadata_values); if (!modified_metadata_xml_files.contains(metadata_xml_file)) { modified_metadata_xml_files.add(metadata_xml_file); } } } // If no applicable metadata.xml file exists, we have to create a new one if (!applicable_metadata_xml_file_found) { // Create a new (empty) metadata.xml file in the file's directory... File new_metadata_xml_file_file = new File(current_file_directory, "metadata.xml"); XMLTools.writeXMLFile(new_metadata_xml_file_file, XMLTools.parseXMLFile("xml/metadata.xml", true)); // ...load it... MetadataXMLFile new_metadata_xml_file = loadMetadataXMLFile(new_metadata_xml_file_file,true); // ...and add the metadata new_metadata_xml_file.addMetadata(file_nodes[i], metadata_values); if (!modified_metadata_xml_files.contains(new_metadata_xml_file)) { modified_metadata_xml_files.add(new_metadata_xml_file); } } } // Let any listeners know that the metadata has changed fireMetadataChangedEvent(file_nodes); } static public void addMetadataChangedListener(MetadataChangedListener metadata_changed_listener) { metadata_changed_listeners.add(metadata_changed_listener); } static public void clearMetadataXMLFiles() { metadata_xml_files.clear(); } static private void fireMetadataChangedEvent(CollectionTreeNode[] file_nodes) { // Send the event off to all the MetadataChangedListeners for (int i = 0; i < metadata_changed_listeners.size(); i++) { ((MetadataChangedListener) metadata_changed_listeners.get(i)).metadataChanged(file_nodes); } } /** Returns the metadata assigned to a file outside the collection, excluding folder-level/inherited metadata. */ static public ArrayList getMetadataAssignedDirectlyToExternalFile(File file) { DebugStream.println("Getting metadata assigned directly to external file " + file + "..."); // Build up a list of applicable metadata.xml files ArrayList applicable_metadata_xml_files = new ArrayList(); File directory = (file.isDirectory() ? file : file.getParentFile()); while (directory != null) { File metadata_xml_file = new File(directory, "metadata.xml"); if (metadata_xml_file.exists() && !metadata_xml_file.isDirectory()) { // It is very important that shallower files come before deeper ones applicable_metadata_xml_files.add(0, new MetadataXMLFile(metadata_xml_file.getAbsolutePath())); } directory = directory.getParentFile(); } // Get the metadata assigned to the specified file from the applicable metadata.xml files ArrayList assigned_metadata = getMetadataAssignedToFile(file, applicable_metadata_xml_files, false); // Remove any folder-level metadata for (int i = assigned_metadata.size() - 1; i >= 0; i--) { if (((MetadataValue) assigned_metadata.get(i)).isInheritedMetadata()) { assigned_metadata.remove(i); } } return assigned_metadata; } /** Returns the metadata assigned to a file inside the collection, excluding folder-level/inherited metadata. */ static public ArrayList getMetadataAssignedDirectlyToFile(File file) { return getMetadataAssignedDirectlyToFile(file, false); } /** Returns the metadata assigned to a file inside the collection, excluding folder-level/inherited metadata. */ static public ArrayList getMetadataAssignedDirectlyToFile(File file, boolean filenameEncodingMetaOnly) { // Get all the metadata assigned to the specified file... ArrayList assigned_metadata = getMetadataAssignedToFile(file, filenameEncodingMetaOnly); // ...then remove any folder-level metadata for (int i = assigned_metadata.size() - 1; i >= 0; i--) { if (((MetadataValue) assigned_metadata.get(i)).isInheritedMetadata()) { assigned_metadata.remove(i); } } return assigned_metadata; /* // Get all the metadata assigned to the specified file... // Build up a list of applicable metadata.xml files - which in this case // is exclusively the metadata file at this file/folder's own level. ArrayList applicable_metadata_xml_files = new ArrayList(); // Find the metadata.xml file (if any) that is at the same level as the file String file_directory_path = (file.isDirectory() ? file : file.getParentFile()).getAbsolutePath() + File.separator; for (int i = 0; i < metadata_xml_files.size(); i++) { MetadataXMLFile metadata_xml_file = (MetadataXMLFile) metadata_xml_files.get(i); if (file_directory_path.equals(metadata_xml_file.getParentFile().getAbsolutePath() + File.separator)) { //System.err.println("Found metadata_xml_file: " + metadata_xml_file); applicable_metadata_xml_files.add(metadata_xml_file); } } if(applicable_metadata_xml_files.size() == 0) { return new ArrayList(0); } // Return the metadata assigned to the specified file from the applicable metadata.xml files return getMetadataAssignedToFile(file, applicable_metadata_xml_files, filenameEncodingMetaOnly); */ } /** Returns all the metadata assigned to a file inside the collection. */ static public ArrayList getMetadataAssignedToFile(File file) { return getMetadataAssignedToFile(file, false); } /** Returns all the metadata assigned to a file inside the collection (or * just gs.filenameEncoding if parameter filenameEncodingMetaOnly is true), * including folder-level/inherited metadata. */ static public ArrayList getMetadataAssignedToFile(File file, boolean filenameEncodingMetaOnly) { // Build up a list of applicable metadata.xml files ArrayList applicable_metadata_xml_files = new ArrayList(); // Look at each loaded metadata.xml file to see if it is potentially applicable String file_directory_path = (file.isDirectory() ? file : file.getParentFile()).getAbsolutePath() + File.separator; for (int i = 0; i < metadata_xml_files.size(); i++) { MetadataXMLFile metadata_xml_file = (MetadataXMLFile) metadata_xml_files.get(i); // This metadata.xml file is only potentially applicable if it is above or at the same level as the file if (file_directory_path.startsWith(metadata_xml_file.getParentFile().getAbsolutePath() + File.separator)) { applicable_metadata_xml_files.add(metadata_xml_file); } } // Sort the metadataxml files in order starting from those in the // topmost folders down to the one in the lowest level folder. Collections.sort(applicable_metadata_xml_files, metadataXMLFileComparator); // Return the metadata assigned to the specified file from the applicable metadata.xml files return getMetadataAssignedToFile(file, applicable_metadata_xml_files, filenameEncodingMetaOnly); } // package access method static ArrayList getMetadataAssignedToFile(File file, ArrayList applicable_metadata_xml_files, boolean filenameEncodingMetaOnly) { // Build up a list of metadata values assigned to this file ArrayList metadata_values_all = new ArrayList(); // Look at each applicable metadata.xml file to see if it assigns metadata to this file for (int i = 0; i < applicable_metadata_xml_files.size(); i++) { MetadataXMLFile metadata_xml_file = (MetadataXMLFile) applicable_metadata_xml_files.get(i); DebugStream.println("Applicable metadata.xml file: " + metadata_xml_file); ArrayList metadata_values = metadata_xml_file.getMetadataAssignedToFile(file, filenameEncodingMetaOnly); for (int j = 0; j < metadata_values.size(); j++) { MetadataValue metadata_value = (MetadataValue) metadata_values.get(j); // Overriding metadata: remove any values with this metadata element if (metadata_value.isAccumulatingMetadata() == false) { for (int k = metadata_values_all.size() - 1; k >= 0; k--) { if (((MetadataValue) metadata_values_all.get(k)).getMetadataElement().equals(metadata_value.getMetadataElement())) { metadata_values_all.remove(k); } } } metadata_values_all.add(metadata_value); } } return metadata_values_all; } static public void loadMetadataXMLFiles(File directory, boolean skimfile) { // Make sure the directory (import) exists if (directory.exists() == false) { return; } // Look recursively at each subfile of the directory for metadata.xml files File[] directory_files = directory.listFiles(); for (int i = 0; i < directory_files.length; i++) { File child_file = directory_files[i]; if (child_file.isDirectory()) { loadMetadataXMLFiles(child_file,skimfile); } else if (child_file.getName().equals("metadata.xml")) { loadMetadataXMLFile(child_file,skimfile); } } } static private MetadataXMLFile loadMetadataXMLFile(File metadata_xml_file_file, boolean skimfile) { MetadataXMLFile metadata_xml_file = new MetadataXMLFile(metadata_xml_file_file.getAbsolutePath()); if (metadata_xml_files.contains(metadata_xml_file)) { // This metadata.xml file has already been loaded, so return the loaded object return (MetadataXMLFile) metadata_xml_files.get(metadata_xml_files.indexOf(metadata_xml_file)); } if(skimfile){ metadata_xml_file.skimFile(); } metadata_xml_files.add(metadata_xml_file); return metadata_xml_file; } static public void removeMetadata(CollectionTreeNode file_node, ArrayList metadata_values) { removeMetadata(new CollectionTreeNode[] { file_node }, metadata_values); } static public void removeMetadata(CollectionTreeNode[] file_nodes, MetadataValue metadata_value) { ArrayList metadata_values = new ArrayList(); metadata_values.add(metadata_value); removeMetadata(file_nodes, metadata_values); } static public void removeMetadata(CollectionTreeNode[] file_nodes, ArrayList metadata_values) { // Check the list of metadata values is non-empty if (metadata_values.isEmpty()) { return; } // Remove the metadata from each file node in turn for (int i = 0; i < file_nodes.length; i++) { File current_file = file_nodes[i].getFile(); DebugStream.println("Removing metadata from " + current_file.getAbsolutePath()); // Find which metadata.xml file needs editing File current_file_directory = (current_file.isDirectory() ? current_file : current_file.getParentFile()); String current_file_directory_path = current_file_directory.getAbsolutePath(); for (int j = 0; j < metadata_xml_files.size(); j++) { MetadataXMLFile metadata_xml_file = (MetadataXMLFile) metadata_xml_files.get(j); // This metadata.xml file is only potentially applicable if it is above or at the same level as the file if (current_file_directory_path.startsWith(metadata_xml_file.getParentFile().getAbsolutePath())) { metadata_xml_file.removeMetadata(file_nodes[i], metadata_values); if (!modified_metadata_xml_files.contains(metadata_xml_file)) { modified_metadata_xml_files.add(metadata_xml_file); } } } } // Let any listeners know that the metadata has changed fireMetadataChangedEvent(file_nodes); } static public void removeMetadataChangedListener(MetadataChangedListener metadata_changed_listener) { metadata_changed_listeners.remove(metadata_changed_listener); } static public void replaceMetadata(CollectionTreeNode[] file_nodes, MetadataValue old_metadata_value, MetadataValue new_metadata_value) { // Replace the metadata in each file node in turn for (int i = 0; i < file_nodes.length; i++) { File current_file = file_nodes[i].getFile(); DebugStream.println("Replacing metadata in " + current_file.getAbsolutePath()); // Find which metadata.xml file needs editing File current_file_directory = (current_file.isDirectory() ? current_file : current_file.getParentFile()); String current_file_directory_path = current_file_directory.getAbsolutePath(); for (int j = 0; j < metadata_xml_files.size(); j++) { MetadataXMLFile metadata_xml_file = (MetadataXMLFile) metadata_xml_files.get(j); // This metadata.xml file is only applicable if it is at the same level as the file if (current_file_directory_path.equals(metadata_xml_file.getParentFile().getAbsolutePath())) { metadata_xml_file.replaceMetadata(file_nodes[i], old_metadata_value, new_metadata_value); if (!modified_metadata_xml_files.contains(metadata_xml_file)) { modified_metadata_xml_files.add(metadata_xml_file); } } } } // Let any listeners know that the metadata has changed fireMetadataChangedEvent(file_nodes); } /** Ensures that all the metadata is written to metadata.xml files. */ static public void saveMetadataXMLFiles() { // Save the file currently loaded into memory out to disk MetadataXMLFile.saveLoadedFile(); // If the collection is stored on a remote server, upload all the modified files now if (Gatherer.isGsdlRemote) { if (modified_metadata_xml_files.isEmpty()) { DebugStream.println("No modified metadata.xml files to upload."); return; } // Upload the files modified since last time, then reset the list Gatherer.remoteGreenstoneServer.uploadCollectionFiles( CollectionManager.getLoadedCollectionName(), (File[]) modified_metadata_xml_files.toArray(new File[0])); modified_metadata_xml_files.clear(); } } static public void unloadMetadataXMLFile(File metadata_xml_file_file) { DebugStream.println("Unloading metadata.xml file " + metadata_xml_file_file); // Find the metadata.xml file in the list of loaded files, and remove it for (int i = 0; i < metadata_xml_files.size(); i++) { MetadataXMLFile metadata_xml_file = (MetadataXMLFile) metadata_xml_files.get(i); if (metadata_xml_file_file.getAbsolutePath().equals(metadata_xml_file.getAbsolutePath())) { metadata_xml_files.remove(i); break; } } } static public void clearAllMetadataInCollection() { for (int j = 0; j < metadata_xml_files.size(); j++) { MetadataXMLFile metadata_xml_file = (MetadataXMLFile) metadata_xml_files.get(j); metadata_xml_file.clearAllMetadataInFile(); // Let any listeners know that the metadata has changed //fireMetadataChangedEvent(file_nodes); // sadly don't have file_nodes needed to do this // all are modified if (!modified_metadata_xml_files.contains(metadata_xml_file)) { modified_metadata_xml_files.add(metadata_xml_file); } saveMetadataXMLFiles(); // saves final modified metaXML and then takes care of uploading all modified metaXML files to remote gsdl } } /** * Comparator to order MetadataXMLFiles in ascending order from * those in a higher level folder to those in a lower level folder * It is based on the assumption that all MetadataXMLFiles sent to * it to compare will be linear descendants of one toplevel folder * E.g. /A/metadata.xml, /A/B/metadata.xml, /A/B/C/D/metadata.xml. * In other words, that each is a substring of one of the others until * the toplevel folder is reached. */ private static class MetadataXMLFileComparator implements Comparator { public int compare(Object o1, Object o2) { if(!(o1 instanceof MetadataXMLFile)) { return -1; } else if (!(o2 instanceof MetadataXMLFile)) { return 1; } // Both are MetadataXMLFiles objects. Remove the terminating // "metadata.xml" from their filenames to get their containing folder String filename1 = ((MetadataXMLFile)o1).getParentFile().getAbsolutePath(); String filename2 = ((MetadataXMLFile)o2).getParentFile().getAbsolutePath(); // if 1 is a prefix of 2, then 1 < 2 in the ordering (1 comes before 2) if(filename2.startsWith(filename1)) { return -1; } else if(filename1.startsWith(filename2)) { return 1; } else { // unlikely that the metadata.xml files will be the same // or that neither is a prefix of the other return filename1.compareTo(filename2); // sorts in ascending order } } public boolean equals(Object obj) { if(!(obj instanceof MetadataXMLFileComparator)) { return false; } // else it is the same sort of comparator return true; } } }