001 /*
002 * $Revision: 15331 $ $Date: 2009-06-08 15:50:11 +0200 (Mon, 08 Jun 2009) $
003 *
004 * This file is part of *** M y C o R e ***
005 * See http://www.mycore.de/ for details.
006 *
007 * This program is free software; you can use it, redistribute it
008 * and / or modify it under the terms of the GNU General Public License
009 * (GPL) as published by the Free Software Foundation; either version 2
010 * of the License or (at your option) any later version.
011 *
012 * This program is distributed in the hope that it will be useful, but
013 * WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
015 * GNU General Public License for more details.
016 *
017 * You should have received a copy of the GNU General Public License
018 * along with this program, in a file called gpl.txt or license.txt.
019 * If not, write to the Free Software Foundation Inc.,
020 * 59 Temple Place - Suite 330, Boston, MA 02111-1307 USA
021 */
022
023 package org.mycore.services.fieldquery;
024
025 import java.util.ArrayList;
026 import java.util.Hashtable;
027 import java.util.List;
028
029 import org.apache.log4j.Logger;
030 import org.jdom.Element;
031 import org.jdom.Namespace;
032 import org.jdom.output.XMLOutputter;
033 import org.mycore.common.MCRConstants;
034 import org.mycore.common.MCRException;
035 import org.mycore.common.xml.MCRURIResolver;
036
037 /**
038 * A search field definition. For each field in the configuration file
039 * searchfields.xml there is one MCRFieldDef instance with attributes that
040 * represent the configuration in the xml file.
041 *
042 * @author Frank Lützenkirchen
043 */
044 public class MCRFieldDef {
045 /** The logger */
046 private static final Logger LOGGER = Logger.getLogger(MCRFieldDef.class);
047
048 private static Hashtable<String, MCRFieldDef> fieldTable = new Hashtable<String, MCRFieldDef>();
049
050 public final static Namespace xalanns = Namespace.getNamespace("xalan",
051 "http://xml.apache.org/xalan");
052
053 public final static Namespace extns = Namespace.getNamespace("ext",
054 "xalan://org.mycore.services.fieldquery.MCRData2Fields");
055
056 private final static String configFile = "searchfields.xml";
057
058 /**
059 * Read searchfields.xml and build the MCRFiedDef objects
060 */
061 static {
062 Element def = getConfigFile();
063
064 List children = def.getChildren("index", MCRConstants.MCR_NAMESPACE);
065
066 for (int i = 0; i < children.size(); i++) {
067 Element index = (Element) (children.get(i));
068 String id = index.getAttributeValue("id");
069
070 List fields = index.getChildren("field", MCRConstants.MCR_NAMESPACE);
071
072 for (int j = 0; j < fields.size(); j++)
073 new MCRFieldDef(id, (Element) fields.get(j));
074 }
075 }
076
077 /**
078 * The index this field belongs to
079 */
080 private String index;
081
082 /**
083 * The unique name of the field
084 */
085 private String name;
086
087 /**
088 * The data type of the field, see fieldtypes.xml file
089 */
090 private String dataType;
091
092 /**
093 * If true, this field can be used to sort query results
094 */
095 private boolean sortable = false;
096
097 /**
098 * A list of object names this field is used for, separated by blanks
099 */
100 private String objects;
101
102 /**
103 * A keyword identifying where the values of this field come from
104 */
105 private String source;
106
107 /**
108 * If true, the content of this field will be added by searcher as metadata
109 * in hit
110 */
111 private boolean addable = false;
112
113 /**
114 * @return the searchfields-configuration file as jdom-element
115 */
116 public static Element getConfigFile() {
117 String uri = "resource:" + configFile;
118 return MCRURIResolver.instance().resolve(uri);
119 }
120
121 public MCRFieldDef(String index, Element def) {
122 this.index = index;
123 this.name = def.getAttributeValue("name");
124 this.dataType = def.getAttributeValue("type");
125 this.sortable = "true".equals(def.getAttributeValue("sortable"));
126 this.objects = def.getAttributeValue("objects", (String) null);
127 this.source = def.getAttributeValue("source");
128 this.addable = "true".equals(def.getAttributeValue("addable"));
129
130 if (!fieldTable.contains(name)) {
131 fieldTable.put(name, this);
132 } else {
133 throw new MCRException("Field \"" + name + "\" is defined repeatedly.");
134 }
135 buildForEachXSL(def);
136 }
137
138 /**
139 * Returns the MCRFieldDef with the given name, or null if no such field is
140 * defined in searchfields.xml
141 *
142 * @param name
143 * the name of the field
144 * @return the MCRFieldDef instance representing this field
145 */
146 public static MCRFieldDef getDef(String name) {
147 return fieldTable.get(name);
148 }
149
150 /**
151 * Returns all fields that belong to the given index
152 *
153 * @param index
154 * the ID of the index
155 * @return a List of MCRFieldDef objects for that index
156 */
157 public static List<MCRFieldDef> getFieldDefs(String index) {
158 List<MCRFieldDef> fields = new ArrayList<MCRFieldDef>();
159 for (MCRFieldDef field : fieldTable.values()) {
160 if (field.index.equals(index))
161 fields.add(field);
162 }
163 return fields;
164 }
165
166 /**
167 * Returns the ID of the index this field belongs to
168 *
169 * @return the index ID
170 */
171 public String getIndex() {
172 return index;
173 }
174
175 /**
176 * Returns the name of the field
177 *
178 * @return the field's name
179 */
180 public String getName() {
181 return name;
182 }
183
184 /**
185 * Returns the data type of this field as defined in fieldtypes.xml
186 *
187 * @return the data type
188 */
189 public String getDataType() {
190 return dataType;
191 }
192
193 /**
194 * Returns true if this field can be used as sort criteria
195 *
196 * @return true, if field can be used to sort query results
197 */
198 public boolean isSortable() {
199 return sortable;
200 }
201
202 /**
203 * Returns true if this field should be added by searcher to hit
204 *
205 * @return true, if this field should be added by searcher to hit
206 */
207 public boolean isAddable() {
208 return addable;
209 }
210
211 /**
212 * Returns true if this field is used for this type of object. For
213 * MCRObject, the type is the same as in MCRObject.getId().getTypeId(). For
214 * MCRFile, the type is the same as in MCRFile.getContentTypeID(). For plain
215 * XML data, the type is the name of the root element.
216 *
217 * @param objectType
218 * the type of object
219 */
220 public boolean isUsedForObjectType(String objectType) {
221 if (objects == null)
222 return true;
223
224 String a = " " + objects + " ";
225 String b = " " + objectType + " ";
226 return (a.indexOf(b) >= 0);
227 }
228
229 /**
230 * A keyword identifying that the source of the values of this field is the
231 * createXML() method of MCRObject
232 *
233 * @see org.mycore.datamodel.metadata.MCRObject#createXML()
234 */
235 public final static String OBJECT_METADATA = "objectMetadata";
236
237 /**
238 * A keyword identifying that the source of the values of this field is a
239 * classification category that a MCRObject belongs to
240 *
241 * @see org.mycore.datamodel.metadata.MCRObject#createXML()
242 */
243 public final static String OBJECT_CATEGORY = "objectCategory";
244
245 /**
246 * A keyword identifying that the source of the values of this field is the
247 * createXML() method of MCRFile
248 *
249 * @see org.mycore.datamodel.ifs.MCRFile#createXML()
250 */
251 public final static String FILE_METADATA = "fileMetadata";
252
253 /**
254 * A keyword identifying that the source of the values of this field is the
255 * getAllAdditionalData() method of MCRFile
256 *
257 * @see org.mycore.datamodel.ifs.MCRFilesystemNode#getAllAdditionalData()
258 */
259 public final static String FILE_ADDITIONAL_DATA = "fileAdditionalData";
260
261 /**
262 * A keyword identifying that the source of the values of this field is the
263 * XML content of the MCRFile
264 *
265 * @see org.mycore.datamodel.ifs.MCRFile#getContentAsJDOM()
266 */
267 public final static String FILE_XML_CONTENT = "fileXMLContent";
268
269 /**
270 * A keyword identifying that the source of the values of this field is the
271 * XML content of a pure JDOM xml document
272 */
273 public final static String XML = "xml";
274
275 /**
276 * A keyword identifying that the source of the values of this field is the
277 * text content of the MCRFile, using text filter plug-ins.
278 */
279 public final static String FILE_TEXT_CONTENT = "fileTextContent";
280
281 /**
282 * A keyword identifying that the source of the values of this field is the
283 * MCRSearcher that does the search, this means it is technical hit metadata
284 * added by the searcher when the query results are built.
285 */
286 public final static String SEARCHER_HIT_METADATA = "searcherHitMetadata";
287
288 /**
289 * Returns a keyword identifying where the values of this field come from.
290 *
291 * @see #FILE_METADATA
292 * @see #FILE_TEXT_CONTENT
293 * @see #FILE_XML_CONTENT
294 * @see #OBJECT_METADATA
295 * @see #OBJECT_CATEGORY
296 * @see #SEARCHER_HIT_METADATA
297 * @see #XML
298 */
299 public String getSource() {
300 return source;
301 }
302
303 /**
304 * The stylesheet fragment to build values for this field from XML source
305 * data
306 */
307 private Element xsl;
308
309 /** Returns a stylesheet to build values for this field from XML source data */
310 Element getXSL() {
311 if (xsl == null)
312 return null;
313 else
314 return (Element) (xsl.clone());
315 }
316
317 /**
318 * Builds the stylesheet fragment to build values for this field from XML
319 * source data
320 */
321 private void buildForEachXSL(Element fieldDef) {
322 String xpath = fieldDef.getAttributeValue("xpath");
323 if ((xpath == null) || (xpath.trim().length() == 0)) {
324 return;
325 }
326
327 // <xsl:if test="contains(@objects,$objecType)">
328 if (objects != null) {
329 Element xif = new Element("if", MCRConstants.XSL_NAMESPACE);
330 xif.setAttribute("test", "contains('" + objects.trim() + "',$objectType)");
331 xsl = xif;
332 }
333
334 // <xsl:for-each select="{@xpath}">
335 Element forEach1 = new Element("for-each", MCRConstants.XSL_NAMESPACE);
336 forEach1.setAttribute("select", xpath);
337 if (xsl == null)
338 xsl = forEach1;
339 else
340 xsl.addContent(forEach1);
341
342 Element current = fieldDef;
343 while (true) {
344 List<Namespace> namespaces = current.getAdditionalNamespaces();
345 for (Namespace ns : namespaces)
346 xsl.addNamespaceDeclaration(ns);
347 if (current.isRootElement())
348 break;
349 else
350 current = current.getParentElement();
351 }
352
353 if (MCRFieldDef.OBJECT_CATEGORY.equals(fieldDef.getAttributeValue("source"))) {
354 // current(): <format classid="DocPortal_class_00000006"
355 // categid="FORMAT0002"/>
356 // URI: classification:metadata:levels:parents:{class}:{categ}
357 Element forEach2 = new Element("for-each", MCRConstants.XSL_NAMESPACE);
358 forEach1.addContent(forEach2);
359 String uri = "document(concat('classification:metadata:0:parents:',current()/@classid,':',current()/@categid))//category";
360 forEach2.setAttribute("select", uri);
361 forEach1 = forEach2;
362 }
363
364 // <name>value</name>
365 Element fieldValue = new Element(getName());
366 forEach1.addContent(fieldValue);
367
368 // <xsl:value-of select="{@value}" />
369 String valueExpr = fieldDef.getAttributeValue("value");
370 Element valueOf = new Element("value-of", MCRConstants.XSL_NAMESPACE);
371 valueOf.setAttribute("select", valueExpr);
372 fieldValue.addContent(valueOf);
373
374 if (LOGGER.isDebugEnabled()) {
375 LOGGER.debug("---------- XSL for search field \"" + name + "\" ----------");
376 XMLOutputter out = new XMLOutputter(org.jdom.output.Format.getPrettyFormat());
377 LOGGER.debug("\n" + out.outputString(xsl));
378 }
379 }
380 }