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    }