001: /*
002: * BrowseIndex.java
003: *
004: * Version: $Revision: $
005: *
006: * Date: $Date: $
007: *
008: * Copyright (c) 2002-2007, Hewlett-Packard Company and Massachusetts
009: * Institute of Technology. All rights reserved.
010: *
011: * Redistribution and use in source and binary forms, with or without
012: * modification, are permitted provided that the following conditions are
013: * met:
014: *
015: * - Redistributions of source code must retain the above copyright
016: * notice, this list of conditions and the following disclaimer.
017: *
018: * - Redistributions in binary form must reproduce the above copyright
019: * notice, this list of conditions and the following disclaimer in the
020: * documentation and/or other materials provided with the distribution.
021: *
022: * - Neither the name of the Hewlett-Packard Company nor the name of the
023: * Massachusetts Institute of Technology nor the names of their
024: * contributors may be used to endorse or promote products derived from
025: * this software without specific prior written permission.
026: *
027: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
028: * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
029: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
030: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
031: * HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
032: * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
033: * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
034: * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
035: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
036: * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
037: * USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
038: * DAMAGE.
039: */
040: package org.dspace.browse;
041:
042: import java.io.IOException;
043: import java.util.ArrayList;
044: import java.util.StringTokenizer;
045: import java.util.regex.Matcher;
046: import java.util.regex.Pattern;
047:
048: import org.dspace.core.ConfigurationManager;
049: import org.dspace.sort.SortOption;
050: import org.dspace.sort.SortException;
051:
052: /**
053: * This class holds all the information about a specifically configured
054: * BrowseIndex. It is responsible for parsing the configuration, understanding
055: * about what sort options are available, and what the names of the database
056: * tables that hold all the information are actually called.
057: *
058: * @author Richard Jones
059: */
060: public class BrowseIndex {
061: /** the configuration number, as specified in the config */
062: /** used for single metadata browse tables for generating the table name */
063: private int number;
064:
065: /** the name of the browse index, as specified in the config */
066: private String name;
067:
068: /** the SortOption for this index (only valid for item indexes) */
069: private SortOption sortOption;
070:
071: /** the value of the metadata, as specified in the config */
072: private String metadataAll;
073:
074: /** the metadata fields, as an array */
075: private String[] metadata;
076:
077: /** the datatype of the index, as specified in the config */
078: private String datatype;
079:
080: /** the display type of the metadata, as specified in the config */
081: private String displayType;
082:
083: /** base name for tables, sequences */
084: private String tableBaseName;
085:
086: /** a three part array of the metadata bits (e.g. dc.contributor.author) */
087: private String[][] mdBits;
088:
089: /** default order (asc / desc) for this index */
090: private String defaultOrder = SortOption.ASCENDING;
091:
092: /** additional 'internal' tables that are always defined */
093: private static BrowseIndex itemIndex = new BrowseIndex("bi_item");
094: private static BrowseIndex withdrawnIndex = new BrowseIndex(
095: "bi_withdrawn");
096:
097: /**
098: * Ensure noone else can create these
099: */
100: private BrowseIndex() {
101: }
102:
103: /**
104: * Constructor for creating generic / internal index objects
105: * @param baseName The base of the table name
106: */
107: private BrowseIndex(String baseName) {
108: try {
109: number = -1;
110: tableBaseName = baseName;
111: displayType = "item";
112: sortOption = SortOption.getDefaultSortOption();
113: } catch (SortException se) {
114: // FIXME Exception handling
115: }
116: }
117:
118: /**
119: * Create a new BrowseIndex object using the definition from the configuration,
120: * and the number of the configuration option. The definition should be of
121: * the form:
122: *
123: * <code>
124: * [name]:[metadata]:[data type]:[display type]
125: * </code>
126: *
127: * [name] is a freetext name for the field
128: * [metadata] is the usual format of the metadata such as dc.contributor.author
129: * [data type] must be either "title", "date" or "text"
130: * [display type] must be either "single" or "full"
131: *
132: * @param definition the configuration definition of this index
133: * @param number the configuration number of this index
134: * @throws BrowseException
135: */
136: private BrowseIndex(String definition, int number)
137: throws BrowseException {
138: try {
139: boolean valid = true;
140: this .defaultOrder = SortOption.ASCENDING;
141: this .number = number;
142:
143: String rx = "(\\w+):(\\w+):([\\w\\.\\*,]+):?(\\w*):?(\\w*)";
144: Pattern pattern = Pattern.compile(rx);
145: Matcher matcher = pattern.matcher(definition);
146:
147: if (matcher.matches()) {
148: name = matcher.group(1);
149: displayType = matcher.group(2);
150:
151: if (isMetadataIndex()) {
152: metadataAll = matcher.group(3);
153: datatype = matcher.group(4);
154:
155: if (metadataAll != null)
156: metadata = metadataAll.split(",");
157:
158: if (metadata == null || metadata.length == 0)
159: valid = false;
160:
161: if (datatype == null || datatype.equals(""))
162: valid = false;
163:
164: // If an optional ordering configuration is supplied,
165: // set the defaultOrder appropriately (asc or desc)
166: if (matcher.groupCount() > 4) {
167: String order = matcher.group(5);
168: if (SortOption.DESCENDING
169: .equalsIgnoreCase(order))
170: this .defaultOrder = SortOption.DESCENDING;
171: }
172:
173: tableBaseName = getItemBrowseIndex().tableBaseName;
174: } else if (isItemIndex()) {
175: String sortName = matcher.group(3);
176:
177: for (SortOption so : SortOption.getSortOptions()) {
178: if (so.getName().equals(sortName))
179: sortOption = so;
180: }
181:
182: if (sortOption == null)
183: valid = false;
184:
185: // If an optional ordering configuration is supplied,
186: // set the defaultOrder appropriately (asc or desc)
187: if (matcher.groupCount() > 3) {
188: String order = matcher.group(4);
189: if (SortOption.DESCENDING
190: .equalsIgnoreCase(order))
191: this .defaultOrder = SortOption.DESCENDING;
192: }
193:
194: tableBaseName = getItemBrowseIndex().tableBaseName;
195: } else {
196: valid = false;
197: }
198: } else {
199: valid = false;
200: }
201:
202: if (!valid) {
203: throw new BrowseException(
204: "Browse Index configuration is not valid: webui.browse.index."
205: + number + " = " + definition);
206: }
207: } catch (SortException se) {
208: throw new BrowseException("Error in SortOptions", se);
209: }
210: }
211:
212: /**
213: * @return Default order for this index, null if not specified
214: */
215: public String getDefaultOrder() {
216: return defaultOrder;
217: }
218:
219: /**
220: * @return Returns the datatype.
221: */
222: public String getDataType() {
223: if (sortOption != null)
224: return sortOption.getType();
225:
226: return datatype;
227: }
228:
229: /**
230: * @return Returns the displayType.
231: */
232: public String getDisplayType() {
233: return displayType;
234: }
235:
236: /**
237: * @return Returns the number of metadata fields for this index
238: */
239: public int getMetadataCount() {
240: if (isMetadataIndex())
241: return metadata.length;
242:
243: return 0;
244: }
245:
246: /**
247: * @return Returns the mdBits.
248: */
249: public String[] getMdBits(int idx) {
250: if (isMetadataIndex())
251: return mdBits[idx];
252:
253: return null;
254: }
255:
256: /**
257: * @return Returns the metadata.
258: */
259: public String getMetadata() {
260: return metadataAll;
261: }
262:
263: public String getMetadata(int idx) {
264: return metadata[idx];
265: }
266:
267: /**
268: * @return Returns the name.
269: */
270: public String getName() {
271: return name;
272: }
273:
274: /**
275: * @param name The name to set.
276: */
277: // public void setName(String name)
278: // {
279: // this.name = name;
280: // }
281: /**
282: * Get the SortOption associated with this index.
283: */
284: public SortOption getSortOption() {
285: return sortOption;
286: }
287:
288: /**
289: * Populate the internal array containing the bits of metadata, for
290: * ease of use later
291: */
292: public void generateMdBits() {
293: try {
294: if (isMetadataIndex()) {
295: mdBits = new String[metadata.length][];
296: for (int i = 0; i < metadata.length; i++) {
297: mdBits[i] = interpretField(metadata[i], null);
298: }
299: }
300: } catch (IOException e) {
301: // it's not obvious what we really ought to do here
302: //log.error("caught exception: ", e);
303: }
304: }
305:
306: /**
307: * Get the name of the sequence that will be used in the given circumnstances
308: *
309: * @param isDistinct is a distinct table
310: * @param isMap is a map table
311: * @return the name of the sequence
312: */
313: public String getSequenceName(boolean isDistinct, boolean isMap) {
314: if (isDistinct || isMap)
315: return BrowseIndex.getSequenceName(number, isDistinct,
316: isMap);
317:
318: return BrowseIndex.getSequenceName(tableBaseName, isDistinct,
319: isMap);
320: }
321:
322: /**
323: * Get the name of the sequence that will be used in the given circumstances
324: *
325: * @param number the index configuration number
326: * @param isDistinct is a distinct table
327: * @param isMap is a map table
328: * @return the name of the sequence
329: */
330: public static String getSequenceName(int number,
331: boolean isDistinct, boolean isMap) {
332: return BrowseIndex.getSequenceName(makeTableBaseName(number),
333: isDistinct, isMap);
334: }
335:
336: /**
337: * Generate a sequence name from the given base
338: * @param baseName
339: * @param isDistinct
340: * @param isMap
341: * @return
342: */
343: private static String getSequenceName(String baseName,
344: boolean isDistinct, boolean isMap) {
345: if (isDistinct) {
346: baseName = baseName + "_dis";
347: } else if (isMap) {
348: baseName = baseName + "_dmap";
349: }
350:
351: baseName = baseName + "_seq";
352:
353: return baseName;
354: }
355:
356: /**
357: * Get the name of the table for the given set of circumstances
358: * This is provided solely for cleaning the database, where you are
359: * trying to create table names that may not be reflected in the current index
360: *
361: * @param number the index configuration number
362: * @param isCommunity whether this is a community constrained index (view)
363: * @param isCollection whether this is a collection constrainted index (view)
364: * @param isDistinct whether this is a distinct table
365: * @param isMap whether this is a distinct map table
366: * @return the name of the table
367: * @deprecated 1.5
368: */
369: public static String getTableName(int number, boolean isCommunity,
370: boolean isCollection, boolean isDistinct, boolean isMap) {
371: return BrowseIndex.getTableName(makeTableBaseName(number),
372: isCommunity, isCollection, isDistinct, isMap);
373: }
374:
375: /**
376: * Generate a table name from the given base
377: * @param baseName
378: * @param isCommunity
379: * @param isCollection
380: * @param isDistinct
381: * @param isMap
382: * @return
383: */
384: private static String getTableName(String baseName,
385: boolean isCommunity, boolean isCollection,
386: boolean isDistinct, boolean isMap) {
387: // isDistinct is meaningless in relation to isCommunity and isCollection
388: // so we bounce that back first, ignoring other arguments
389: if (isDistinct) {
390: return baseName + "_dis";
391: }
392:
393: // isCommunity and isCollection are mutually exclusive
394: if (isCommunity) {
395: baseName = baseName + "_com";
396: } else if (isCollection) {
397: baseName = baseName + "_col";
398: }
399:
400: // isMap is additive to isCommunity and isCollection
401: if (isMap) {
402: baseName = baseName + "_dmap";
403: }
404:
405: return baseName;
406: }
407:
408: /**
409: * Get the name of the table in the given circumstances
410: *
411: * @param isCommunity whether this is a community constrained index (view)
412: * @param isCollection whether this is a collection constrainted index (view)
413: * @param isDistinct whether this is a distinct table
414: * @param isMap whether this is a distinct map table
415: * @return the name of the table
416: * @deprecated 1.5
417: */
418: public String getTableName(boolean isCommunity,
419: boolean isCollection, boolean isDistinct, boolean isMap) {
420: if (isDistinct || isMap)
421: return BrowseIndex.getTableName(number, isCommunity,
422: isCollection, isDistinct, isMap);
423:
424: return BrowseIndex.getTableName(tableBaseName, isCommunity,
425: isCollection, isDistinct, isMap);
426: }
427:
428: /**
429: * Get the name of the table in the given circumstances. This is the same as calling
430: *
431: * <code>
432: * getTableName(isCommunity, isCollection, false, false);
433: * </code>
434: *
435: * @param isCommunity whether this is a community constrained index (view)
436: * @param isCollection whether this is a collection constrainted index (view)
437: * @return the name of the table
438: * @deprecated 1.5
439: */
440: public String getTableName(boolean isCommunity, boolean isCollection) {
441: return getTableName(isCommunity, isCollection, false, false);
442: }
443:
444: /**
445: * Get the default index table name. This is the same as calling
446: *
447: * <code>
448: * getTableName(false, false, false, false);
449: * </code>
450: *
451: * @return
452: */
453: public String getTableName() {
454: return getTableName(false, false, false, false);
455: }
456:
457: /**
458: * Get the table name for the given set of circumstances
459: *
460: * This is the same as calling:
461: *
462: * <code>
463: * getTableName(isCommunity, isCollection, isDistinct, false);
464: * </code>
465: *
466: * @param isDistinct is this a distinct table
467: * @param isCommunity
468: * @param isCollection
469: * @return
470: * @deprecated 1.5
471: */
472: public String getTableName(boolean isDistinct, boolean isCommunity,
473: boolean isCollection) {
474: return getTableName(isCommunity, isCollection, isDistinct,
475: false);
476: }
477:
478: /**
479: * Get the default name of the distinct map table. This is the same as calling
480: *
481: * <code>
482: * getTableName(false, false, false, true);
483: * </code>
484: *
485: * @return
486: */
487: public String getMapTableName() {
488: return getTableName(false, false, false, true);
489: }
490:
491: /**
492: * Get the default name of the distinct table. This is the same as calling
493: *
494: * <code>
495: * getTableName(false, false, true, false);
496: * </code>
497: *
498: * @return
499: */
500: public String getDistinctTableName() {
501: return getTableName(false, false, true, false);
502: }
503:
504: /**
505: * Get the name of the colum that is used to store the default value column
506: *
507: * @return the name of the value column
508: */
509: public String getValueColumn() {
510: if (!isDate()) {
511: return "sort_text_value";
512: } else {
513: return "text_value";
514: }
515: }
516:
517: /**
518: * Get the name of the primary key index column
519: *
520: * @return the name of the primary key index column
521: */
522: public String getIndexColumn() {
523: return "id";
524: }
525:
526: /**
527: * Is this browse index type for a title?
528: *
529: * @return true if title type, false if not
530: */
531: // public boolean isTitle()
532: // {
533: // return "title".equals(getDataType());
534: // }
535: /**
536: * Is the browse index type for a date?
537: *
538: * @return true if date type, false if not
539: */
540: public boolean isDate() {
541: return "date".equals(getDataType());
542: }
543:
544: /**
545: * Is the browse index type for a plain text type?
546: *
547: * @return true if plain text type, false if not
548: */
549: // public boolean isText()
550: // {
551: // return "text".equals(getDataType());
552: // }
553: /**
554: * Is the browse index of display type single?
555: *
556: * @return true if singe, false if not
557: */
558: public boolean isMetadataIndex() {
559: return "metadata".equals(displayType);
560: }
561:
562: /**
563: * Is the browse index of display type full?
564: *
565: * @return true if full, false if not
566: */
567: public boolean isItemIndex() {
568: return "item".equals(displayType);
569: }
570:
571: /**
572: * Get the field for sorting associated with this index
573: * @return
574: * @throws BrowseException
575: */
576: public String getSortField(boolean isSecondLevel)
577: throws BrowseException {
578: String focusField;
579: if (isMetadataIndex() && !isSecondLevel) {
580: focusField = "sort_value";
581: } else {
582: if (sortOption != null)
583: focusField = "sort_" + sortOption.getNumber();
584: else
585: focusField = "sort_1"; // Use the first sort column
586: }
587:
588: return focusField;
589: }
590:
591: /**
592: * @deprecated
593: * @return
594: * @throws BrowseException
595: */
596: public static String[] tables() throws BrowseException {
597: BrowseIndex[] bis = getBrowseIndices();
598: String[] returnTables = new String[bis.length];
599: for (int i = 0; i < bis.length; i++) {
600: returnTables[i] = bis[i].getTableName();
601: }
602:
603: return returnTables;
604: }
605:
606: /**
607: * Get an array of all the browse indices for the current configuration
608: *
609: * @return an array of all the current browse indices
610: * @throws BrowseException
611: */
612: public static BrowseIndex[] getBrowseIndices()
613: throws BrowseException {
614: int idx = 1;
615: String definition;
616: ArrayList browseIndices = new ArrayList();
617:
618: while (((definition = ConfigurationManager
619: .getProperty("webui.browse.index." + idx))) != null) {
620: BrowseIndex bi = new BrowseIndex(definition, idx);
621: browseIndices.add(bi);
622: idx++;
623: }
624:
625: BrowseIndex[] bis = new BrowseIndex[browseIndices.size()];
626: bis = (BrowseIndex[]) browseIndices
627: .toArray((BrowseIndex[]) bis);
628:
629: return bis;
630: }
631:
632: /**
633: * Get the browse index from configuration with the specified name.
634: * The name is the first part of the browse configuration
635: *
636: * @param name the name to retrieve
637: * @return the specified browse index
638: * @throws BrowseException
639: */
640: public static BrowseIndex getBrowseIndex(String name)
641: throws BrowseException {
642: for (BrowseIndex bix : BrowseIndex.getBrowseIndices()) {
643: if (bix.getName().equals(name))
644: return bix;
645: }
646:
647: return null;
648: }
649:
650: /**
651: * Get the configured browse index that is defined to use this sort option
652: *
653: * @param so
654: * @return
655: * @throws BrowseException
656: */
657: public static BrowseIndex getBrowseIndex(SortOption so)
658: throws BrowseException {
659: for (BrowseIndex bix : BrowseIndex.getBrowseIndices()) {
660: if (bix.getSortOption() == so)
661: return bix;
662: }
663:
664: return null;
665: }
666:
667: /**
668: * Get the internally defined browse index for archived items
669: *
670: * @return
671: */
672: public static BrowseIndex getItemBrowseIndex() {
673: return BrowseIndex.itemIndex;
674: }
675:
676: /**
677: * Get the internally defined browse index for withdrawn items
678: * @return
679: */
680: public static BrowseIndex getWithdrawnBrowseIndex() {
681: return BrowseIndex.withdrawnIndex;
682: }
683:
684: /**
685: * Take a string representation of a metadata field, and return it as an array.
686: * This is just a convenient utility method to basically break the metadata
687: * representation up by its delimiter (.), and stick it in an array, inserting
688: * the value of the init parameter when there is no metadata field part.
689: *
690: * @param mfield the string representation of the metadata
691: * @param init the default value of the array elements
692: * @return a three element array with schema, element and qualifier respectively
693: */
694: public String[] interpretField(String mfield, String init)
695: throws IOException {
696: StringTokenizer sta = new StringTokenizer(mfield, ".");
697: String[] field = { init, init, init };
698:
699: int i = 0;
700: while (sta.hasMoreTokens()) {
701: field[i++] = sta.nextToken();
702: }
703:
704: // error checks to make sure we have at least a schema and qualifier for both
705: if (field[0] == null || field[1] == null) {
706: throw new IOException("at least a schema and element be "
707: + "specified in configuration. You supplied: "
708: + mfield);
709: }
710:
711: return field;
712: }
713:
714: /**
715: * Does the browse index represent one of the internal item indexes
716: *
717: * @param bi
718: * @return
719: */
720: public static boolean isInternalIndex(BrowseIndex bi) {
721: return (bi == itemIndex || bi == withdrawnIndex);
722: }
723:
724: /**
725: * Does this browse index represent one of the internal item indexes
726: *
727: * @return
728: */
729: public boolean isInternalIndex() {
730: return (this == itemIndex || this == withdrawnIndex);
731: }
732:
733: /**
734: * Generate a base table name
735: * @param number
736: * @return
737: */
738: private static String makeTableBaseName(int number) {
739: return "bi_" + Integer.toString(number);
740: }
741: }
|