/* * BrowseIndex.java * * Version: $Revision: 4365 $ * * Date: $Date: 2009-10-05 23:52:42 +0000 (Mon, 05 Oct 2009) $ * * Copyright (c) 2002-2009, The DSpace Foundation. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: * * - Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * - Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * - Neither the name of the DSpace Foundation nor the names of its * contributors may be used to endorse or promote products derived from * this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. */ package org.dspace.browse; import java.io.IOException; import java.util.ArrayList; import java.util.StringTokenizer; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.dspace.core.ConfigurationManager; import org.dspace.sort.SortOption; import org.dspace.sort.SortException; /** * This class holds all the information about a specifically configured * BrowseIndex. It is responsible for parsing the configuration, understanding * about what sort options are available, and what the names of the database * tables that hold all the information are actually called. * * @author Richard Jones */ public class BrowseIndex { /** the configuration number, as specified in the config */ /** used for single metadata browse tables for generating the table name */ private int number; /** the name of the browse index, as specified in the config */ private String name; /** the SortOption for this index (only valid for item indexes) */ private SortOption sortOption; /** the value of the metadata, as specified in the config */ private String metadataAll; /** the metadata fields, as an array */ private String[] metadata; /** the datatype of the index, as specified in the config */ private String datatype; /** the display type of the metadata, as specified in the config */ private String displayType; /** base name for tables, sequences */ private String tableBaseName; /** a three part array of the metadata bits (e.g. dc.contributor.author) */ private String[][] mdBits; /** default order (asc / desc) for this index */ private String defaultOrder = SortOption.ASCENDING; /** additional 'internal' tables that are always defined */ private static BrowseIndex itemIndex = new BrowseIndex("bi_item"); private static BrowseIndex withdrawnIndex = new BrowseIndex("bi_withdrawn"); /** * Ensure noone else can create these */ private BrowseIndex() { } /** * Constructor for creating generic / internal index objects * @param baseName The base of the table name */ private BrowseIndex(String baseName) { try { number = -1; tableBaseName = baseName; displayType = "item"; sortOption = SortOption.getDefaultSortOption(); } catch (SortException se) { // FIXME Exception handling } } /** * Create a new BrowseIndex object using the definition from the configuration, * and the number of the configuration option. The definition should be of * the form: * * <code> * [name]:[metadata]:[data type]:[display type] * </code> * * [name] is a freetext name for the field * [metadata] is the usual format of the metadata such as dc.contributor.author * [data type] must be either "title", "date" or "text" * [display type] must be either "single" or "full" * * @param definition the configuration definition of this index * @param number the configuration number of this index * @throws BrowseException */ private BrowseIndex(String definition, int number) throws BrowseException { try { boolean valid = true; this.defaultOrder = SortOption.ASCENDING; this.number = number; String rx = "(\\w+):(\\w+):([\\w\\.\\*,]+):?(\\w*):?(\\w*)"; Pattern pattern = Pattern.compile(rx); Matcher matcher = pattern.matcher(definition); if (matcher.matches()) { name = matcher.group(1); displayType = matcher.group(2); if (isMetadataIndex()) { metadataAll = matcher.group(3); datatype = matcher.group(4); if (metadataAll != null) metadata = metadataAll.split(","); if (metadata == null || metadata.length == 0) valid = false; if (datatype == null || datatype.equals("")) valid = false; // If an optional ordering configuration is supplied, // set the defaultOrder appropriately (asc or desc) if (matcher.groupCount() > 4) { String order = matcher.group(5); if (SortOption.DESCENDING.equalsIgnoreCase(order)) this.defaultOrder = SortOption.DESCENDING; } tableBaseName = getItemBrowseIndex().tableBaseName; } else if (isItemIndex()) { String sortName = matcher.group(3); for (SortOption so : SortOption.getSortOptions()) { if (so.getName().equals(sortName)) sortOption = so; } if (sortOption == null) valid = false; // If an optional ordering configuration is supplied, // set the defaultOrder appropriately (asc or desc) if (matcher.groupCount() > 3) { String order = matcher.group(4); if (SortOption.DESCENDING.equalsIgnoreCase(order)) this.defaultOrder = SortOption.DESCENDING; } tableBaseName = getItemBrowseIndex().tableBaseName; } else { valid = false; } } else { valid = false; } if (!valid) { throw new BrowseException("Browse Index configuration is not valid: webui.browse.index." + number + " = " + definition); } } catch (SortException se) { throw new BrowseException("Error in SortOptions", se); } } /** * @return Default order for this index, null if not specified */ public String getDefaultOrder() { return defaultOrder; } /** * @return Returns the datatype. */ public String getDataType() { if (sortOption != null) return sortOption.getType(); return datatype; } /** * @return Returns the displayType. */ public String getDisplayType() { return displayType; } /** * @return Returns the number of metadata fields for this index */ public int getMetadataCount() { if (isMetadataIndex()) return metadata.length; return 0; } /** * @return Returns the mdBits. */ public String[] getMdBits(int idx) { if (isMetadataIndex()) return mdBits[idx]; return null; } /** * @return Returns the metadata. */ public String getMetadata() { return metadataAll; } public String getMetadata(int idx) { return metadata[idx]; } /** * @return Returns the name. */ public String getName() { return name; } /** * @param name The name to set. */ // public void setName(String name) // { // this.name = name; // } /** * Get the SortOption associated with this index. */ public SortOption getSortOption() { return sortOption; } /** * Populate the internal array containing the bits of metadata, for * ease of use later */ public void generateMdBits() { try { if (isMetadataIndex()) { mdBits = new String[metadata.length][]; for (int i = 0; i < metadata.length; i++) { mdBits[i] = interpretField(metadata[i], null); } } } catch(IOException e) { // it's not obvious what we really ought to do here //log.error("caught exception: ", e); } } /** * Get the name of the sequence that will be used in the given circumnstances * * @param isDistinct is a distinct table * @param isMap is a map table * @return the name of the sequence */ public String getSequenceName(boolean isDistinct, boolean isMap) { if (isDistinct || isMap) return BrowseIndex.getSequenceName(number, isDistinct, isMap); return BrowseIndex.getSequenceName(tableBaseName, isDistinct, isMap); } /** * Get the name of the sequence that will be used in the given circumstances * * @param number the index configuration number * @param isDistinct is a distinct table * @param isMap is a map table * @return the name of the sequence */ public static String getSequenceName(int number, boolean isDistinct, boolean isMap) { return BrowseIndex.getSequenceName(makeTableBaseName(number), isDistinct, isMap); } /** * Generate a sequence name from the given base * @param baseName * @param isDistinct * @param isMap * @return */ private static String getSequenceName(String baseName, boolean isDistinct, boolean isMap) { if (isDistinct) { baseName = baseName + "_dis"; } else if (isMap) { baseName = baseName + "_dmap"; } baseName = baseName + "_seq"; return baseName; } /** * Get the name of the table for the given set of circumstances * This is provided solely for cleaning the database, where you are * trying to create table names that may not be reflected in the current index * * @param number the index configuration number * @param isCommunity whether this is a community constrained index (view) * @param isCollection whether this is a collection constrainted index (view) * @param isDistinct whether this is a distinct table * @param isMap whether this is a distinct map table * @return the name of the table * @deprecated 1.5 */ public static String getTableName(int number, boolean isCommunity, boolean isCollection, boolean isDistinct, boolean isMap) { return BrowseIndex.getTableName(makeTableBaseName(number), isCommunity, isCollection, isDistinct, isMap); } /** * Generate a table name from the given base * @param baseName * @param isCommunity * @param isCollection * @param isDistinct * @param isMap * @return */ private static String getTableName(String baseName, boolean isCommunity, boolean isCollection, boolean isDistinct, boolean isMap) { // isDistinct is meaningless in relation to isCommunity and isCollection // so we bounce that back first, ignoring other arguments if (isDistinct) { return baseName + "_dis"; } // isCommunity and isCollection are mutually exclusive if (isCommunity) { baseName = baseName + "_com"; } else if (isCollection) { baseName = baseName + "_col"; } // isMap is additive to isCommunity and isCollection if (isMap) { baseName = baseName + "_dmap"; } return baseName; } /** * Get the name of the table in the given circumstances * * @param isCommunity whether this is a community constrained index (view) * @param isCollection whether this is a collection constrainted index (view) * @param isDistinct whether this is a distinct table * @param isMap whether this is a distinct map table * @return the name of the table * @deprecated 1.5 */ public String getTableName(boolean isCommunity, boolean isCollection, boolean isDistinct, boolean isMap) { if (isDistinct || isMap) return BrowseIndex.getTableName(number, isCommunity, isCollection, isDistinct, isMap); return BrowseIndex.getTableName(tableBaseName, isCommunity, isCollection, isDistinct, isMap); } /** * Get the name of the table in the given circumstances. This is the same as calling * * <code> * getTableName(isCommunity, isCollection, false, false); * </code> * * @param isCommunity whether this is a community constrained index (view) * @param isCollection whether this is a collection constrainted index (view) * @return the name of the table * @deprecated 1.5 */ public String getTableName(boolean isCommunity, boolean isCollection) { return getTableName(isCommunity, isCollection, false, false); } /** * Get the default index table name. This is the same as calling * * <code> * getTableName(false, false, false, false); * </code> * * @return */ public String getTableName() { return getTableName(false, false, false, false); } /** * Get the table name for the given set of circumstances * * This is the same as calling: * * <code> * getTableName(isCommunity, isCollection, isDistinct, false); * </code> * * @param isDistinct is this a distinct table * @param isCommunity * @param isCollection * @return * @deprecated 1.5 */ public String getTableName(boolean isDistinct, boolean isCommunity, boolean isCollection) { return getTableName(isCommunity, isCollection, isDistinct, false); } /** * Get the default name of the distinct map table. This is the same as calling * * <code> * getTableName(false, false, false, true); * </code> * * @return */ public String getMapTableName() { return getTableName(false, false, false, true); } /** * Get the default name of the distinct table. This is the same as calling * * <code> * getTableName(false, false, true, false); * </code> * * @return */ public String getDistinctTableName() { return getTableName(false, false, true, false); } /** * Get the name of the colum that is used to store the default value column * * @return the name of the value column */ public String getValueColumn() { if (!isDate()) { return "sort_text_value"; } else { return "text_value"; } } /** * Get the name of the primary key index column * * @return the name of the primary key index column */ public String getIndexColumn() { return "id"; } /** * Is this browse index type for a title? * * @return true if title type, false if not */ // public boolean isTitle() // { // return "title".equals(getDataType()); // } /** * Is the browse index type for a date? * * @return true if date type, false if not */ public boolean isDate() { return "date".equals(getDataType()); } /** * Is the browse index type for a plain text type? * * @return true if plain text type, false if not */ // public boolean isText() // { // return "text".equals(getDataType()); // } /** * Is the browse index of display type single? * * @return true if singe, false if not */ public boolean isMetadataIndex() { return displayType != null && displayType.startsWith("metadata"); } /** * Is the browse index authority value? * * @return true if authority, false if not */ public boolean isAuthorityIndex() { return "metadataAuthority".equals(displayType); } /** * Is the browse index of display type full? * * @return true if full, false if not */ public boolean isItemIndex() { return "item".equals(displayType); } /** * Get the field for sorting associated with this index * @return * @throws BrowseException */ public String getSortField(boolean isSecondLevel) throws BrowseException { String focusField; if (isMetadataIndex() && !isSecondLevel) { focusField = "sort_value"; } else { if (sortOption != null) focusField = "sort_" + sortOption.getNumber(); else focusField = "sort_1"; // Use the first sort column } return focusField; } /** * @deprecated * @return * @throws BrowseException */ public static String[] tables() throws BrowseException { BrowseIndex[] bis = getBrowseIndices(); String[] returnTables = new String[bis.length]; for (int i = 0; i < bis.length; i++) { returnTables[i] = bis[i].getTableName(); } return returnTables; } /** * Get an array of all the browse indices for the current configuration * * @return an array of all the current browse indices * @throws BrowseException */ public static BrowseIndex[] getBrowseIndices() throws BrowseException { int idx = 1; String definition; ArrayList browseIndices = new ArrayList(); while ( ((definition = ConfigurationManager.getProperty("webui.browse.index." + idx))) != null) { BrowseIndex bi = new BrowseIndex(definition, idx); browseIndices.add(bi); idx++; } BrowseIndex[] bis = new BrowseIndex[browseIndices.size()]; bis = (BrowseIndex[]) browseIndices.toArray((BrowseIndex[]) bis); return bis; } /** * Get the browse index from configuration with the specified name. * The name is the first part of the browse configuration * * @param name the name to retrieve * @return the specified browse index * @throws BrowseException */ public static BrowseIndex getBrowseIndex(String name) throws BrowseException { for (BrowseIndex bix : BrowseIndex.getBrowseIndices()) { if (bix.getName().equals(name)) return bix; } return null; } /** * Get the configured browse index that is defined to use this sort option * * @param so * @return * @throws BrowseException */ public static BrowseIndex getBrowseIndex(SortOption so) throws BrowseException { for (BrowseIndex bix : BrowseIndex.getBrowseIndices()) { if (bix.getSortOption() == so) return bix; } return null; } /** * Get the internally defined browse index for archived items * * @return */ public static BrowseIndex getItemBrowseIndex() { return BrowseIndex.itemIndex; } /** * Get the internally defined browse index for withdrawn items * @return */ public static BrowseIndex getWithdrawnBrowseIndex() { return BrowseIndex.withdrawnIndex; } /** * Take a string representation of a metadata field, and return it as an array. * This is just a convenient utility method to basically break the metadata * representation up by its delimiter (.), and stick it in an array, inserting * the value of the init parameter when there is no metadata field part. * * @param mfield the string representation of the metadata * @param init the default value of the array elements * @return a three element array with schema, element and qualifier respectively */ public String[] interpretField(String mfield, String init) throws IOException { StringTokenizer sta = new StringTokenizer(mfield, "."); String[] field = {init, init, init}; int i = 0; while (sta.hasMoreTokens()) { field[i++] = sta.nextToken(); } // error checks to make sure we have at least a schema and qualifier for both if (field[0] == null || field[1] == null) { throw new IOException("at least a schema and element be " + "specified in configuration. You supplied: " + mfield); } return field; } /** * Does the browse index represent one of the internal item indexes * * @param bi * @return */ public static boolean isInternalIndex(BrowseIndex bi) { return (bi == itemIndex || bi == withdrawnIndex); } /** * Does this browse index represent one of the internal item indexes * * @return */ public boolean isInternalIndex() { return (this == itemIndex || this == withdrawnIndex); } /** * Generate a base table name * @param number * @return */ private static String makeTableBaseName(int number) { return "bi_" + Integer.toString(number); } }