/** * The contents of this file are subject to the license and copyright * detailed in the LICENSE and NOTICE files at the root of the source * tree and available online at * * http://www.dspace.org/license/ */ package org.dspace.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 final 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 no one 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 circumstances * * @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 constrained 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 constrained 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 constrained 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 column 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<BrowseIndex> browseIndices = new ArrayList<BrowseIndex>(); 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 = browseIndices.toArray(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 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); } }