001    package org.mycore.frontend.indexbrowser.lucene;
002    
003    import java.util.Iterator;
004    import java.util.List;
005    import java.util.Vector;
006    
007    import org.apache.log4j.Logger;
008    import org.jdom.Attribute;
009    import org.jdom.Content;
010    import org.jdom.Document;
011    import org.jdom.Element;
012    import org.jdom.JDOMException;
013    import org.jdom.filter.ElementFilter;
014    import org.jdom.output.XMLOutputter;
015    import org.jdom.xpath.XPath;
016    import org.mycore.common.MCRConfiguration;
017    import org.mycore.common.MCRSession;
018    import org.mycore.common.MCRSessionMgr;
019    import org.mycore.datamodel.common.MCRXMLTableManager;
020    import org.mycore.datamodel.metadata.MCRObjectID;
021    
022    /**
023     * Xml generator class for the index browser.
024     * <p>
025     * This class is excluded from MCRIndexBrowserData.
026     * </p>
027     * @author Matthias Eichner
028     */
029    public class MCRIndexBrowserXmlGenerator {
030    
031        protected static Logger LOGGER = Logger.getLogger(MCRIndexBrowserXmlGenerator.class);
032        
033        protected static final String defaultlang = MCRConfiguration.instance().getString("MCR.Metadata.DefaultLang", "de");
034    
035        protected Element page;
036    
037        protected MCRIndexBrowserIncomingData browseData;
038        
039        protected MCRIndexBrowserConfig indexConfig;
040        
041        protected List<MCRIndexBrowserEntry> resultList;
042        
043        public MCRIndexBrowserXmlGenerator(List<MCRIndexBrowserEntry> resultList, MCRIndexBrowserIncomingData browseData, MCRIndexBrowserConfig indexConfig) {
044            this.browseData = browseData;
045            this.indexConfig = indexConfig;
046            this.resultList = resultList;
047    
048            page = buildPageElement(browseData);
049            Element resultsElement = buildResultsElement(page, browseData);
050            int numRows = resultList.size();
051    
052            int from = Math.max(0, browseData.getFrom());
053            int to = Math.min(numRows, browseData.getTo() + 1);
054            int numSelectedRows = to - from;
055    
056            resultsElement.setAttribute("numHits", String.valueOf(numRows));
057            if (browseData.getSearch() != null) {
058                resultsElement.setAttribute("search", browseData.getSearch());
059            }
060    
061            if (numSelectedRows <= indexConfig.getMaxPerPage()) {
062                // set the metadata for the details only for the shown objects
063                fillHitListWithMetadata(from, to);
064    
065                // build the xml-result tree with detailed Informations
066                for (int i = from; i < to; i++) {
067                    MCRIndexBrowserEntry entry = resultList.get(i);
068                    Element v = new Element("value");
069                    v.setAttribute("pos", String.valueOf(i));
070    
071                    List<String> sortValues = entry.getSortValues();
072                    
073                    v.addContent(new Element("sort").addContent(sortValues.get(0)));
074                    String idx = sortValues.get(0);
075                    if(sortValues.size() > 1)
076                        idx = sortValues.get(1);
077                    v.addContent(new Element("idx").addContent(idx));
078                    v.addContent(new Element("id").addContent(entry.getObjectId()));
079    
080                    for(int index = 0; index < indexConfig.getOutputList().size(); index++) {
081                        Element col = new Element("col");
082                        col.setAttribute("name", indexConfig.getOutputList().get(index));
083                        col.addContent(entry.getOutputValue(index));
084                        v.addContent(col);
085                    }
086                    resultsElement.addContent(v);
087                }
088            } else {
089                // to much results to show all details
090                // build the xml-result tree with the range of the searchresults
091                int stepSize = calculateStepSize(numSelectedRows, indexConfig.getMaxPerPage());
092                List<MyRangeDelim> delims = new Vector<MyRangeDelim>();
093    
094                int index = from;
095                do {  
096                    MCRIndexBrowserEntry firstEntry = resultList.get(index);
097                    delims.add(new MyRangeDelim(index, firstEntry.getSortValue(0)));
098                    index += stepSize;
099                    String secondValue = "";
100                    if(index >= to) {
101                        index = to - 1;
102                    }
103                    secondValue = resultList.get(index).getSortValue(0);
104                    delims.add(new MyRangeDelim(index, secondValue));
105                } while((++index) < to);
106                buildPrefixDifference(delims);
107                buildXML(resultsElement, delims);
108            }
109        }
110    
111        /**
112         * Calculates the step size of the selection. 
113         * 
114         * @param numSelectedRows the number of selected elements
115         * @param maxPerPage maximum displayable elements
116         * @return step size
117         */
118        private int calculateStepSize(int numSelectedRows, int maxPerPage) {
119            for (int i = 1;; i++) {
120                double dNum = numSelectedRows;
121                double dI = 1.0 / ((double) i);
122                double root = Math.pow(dNum, dI);
123                if (root <= maxPerPage)
124                    return (int) (Math.floor(dNum / root));
125            }
126        }
127    
128        /**
129         * Returns the final xml document.
130         * @return xml document
131         */
132        public Document getXMLContent() {
133            if (LOGGER.isDebugEnabled()) {
134                XMLOutputter out = new XMLOutputter(org.jdom.output.Format.getPrettyFormat());
135                LOGGER.debug("Results: \n" + out.outputString(page));
136            }
137            return new Document(page);
138        }
139    
140        /**
141         * Goes through the result list to expand each entry with
142         * output values.
143         * @param from start from that element
144         * @param to end at this element
145         */
146        private void fillHitListWithMetadata(int from, int to) {
147            // now we take only the objects from metadata for the dataobjects that
148            // we will shown in detail
149            // this occurres if we have only a small count of results, or we show
150            // only a part
151            if (to - (from + 1) <= indexConfig.getMaxPerPage() && indexConfig.getOutputList() != null) {
152                for (int i = from; i < to; i++) {
153                    MCRIndexBrowserEntry entry = resultList.get(i);
154                    String id = entry.getObjectId();
155                    Document jdomDoc = MCRXMLTableManager.instance().readDocument(new MCRObjectID(id));
156                    // outputfields came only from metadataobject
157                    setListeElm(jdomDoc, indexConfig.getOutputList(), entry);
158                }
159            }
160        }
161    
162        /**
163         * Adds the output values for an index browser entry.
164         * @param od the xml document of the current mcr object
165         * @param outputList the output list from the index configuration
166         * @param entry the index browser entry
167         */
168        private void setListeElm(Document od, List<String> outputList, MCRIndexBrowserEntry entry) {
169            MCRSession mcrSession = MCRSessionMgr.getCurrentSession();
170            String currentlang = mcrSession.getCurrentLanguage();
171            for (String sField : outputList) {
172                String value = "";
173                String attribute = "";
174    
175                // Eintrag der ID
176                if (sField.equalsIgnoreCase("id")) {
177                    value = od.getRootElement().getAttributeValue("ID");
178                } else {
179                    if (sField.contains("/")) {
180                        // evaluate an XPath-Expression
181                        try {
182                            XPath xpath = XPath.newInstance(sField);
183                            List<?> xpathResults = xpath.selectNodes(od);
184                            value = "";
185                            for (int i = 0; i < xpathResults.size(); i++) {
186                                if (i > 0) {
187                                    value += " - ";
188                                }
189                                if (xpathResults.get(i) instanceof Attribute) {
190                                    value += ((Attribute) xpathResults.get(i)).getValue();
191                                } else {
192                                    value += ((Content) xpathResults.get(i)).getValue();
193                                }
194                            }
195    
196                        } catch (JDOMException jde) {
197                            value = "";
198                        }
199                    } else {
200                        // get for current lang
201                        Iterator<?> it = od.getDescendants(new ElementFilter(sField));
202                        // only the atribute different texts are taken!!
203                        int counter = 0;
204                        boolean hasdefault = false;
205                        while (it.hasNext()) {
206                            Element el = (Element) it.next();
207                            String lang = el.getAttributeValue("lang", org.jdom.Namespace.XML_NAMESPACE);
208                            if ((lang != null) && (lang.equals(currentlang))) {
209                                if (attribute != el.getAttributeValue("type")) {
210                                    if (value.length() > 0) {
211                                        value += " - ";
212                                    }
213                                    value += el.getText();
214                                    attribute = el.getAttributeValue("type");
215                                }
216                                counter++;
217                            }
218                            if ((lang != null) && (lang.equals(currentlang))) {
219                                hasdefault = true;
220                            }
221                        }
222                        if (counter == 0) {
223                            // get for current lang
224                            it = od.getDescendants(new ElementFilter(sField));
225                            // only the atribute different texts are taken!!
226                            if (hasdefault) {
227                                while (it.hasNext()) {
228                                    Element el = (Element) it.next();
229                                    String lang = el.getAttributeValue("lang", org.jdom.Namespace.XML_NAMESPACE);
230                                    if ((lang != null) && (lang.equals(defaultlang))) {
231                                        if (attribute != el.getAttributeValue("type")) {
232                                            if (value.length() > 0) {
233                                                value += " - ";
234                                            }
235                                            value += el.getText();
236                                            attribute = el.getAttributeValue("type");
237                                        }
238                                        break;
239                                    }
240                                }
241                            } else {
242                                while (it.hasNext()) {
243                                    Element el = (Element) it.next();
244                                    if (attribute != el.getAttributeValue("type")) {
245                                        if (value.length() > 0) {
246                                            value += " - ";
247                                        }
248                                        value += el.getText();
249                                        attribute = el.getAttributeValue("type");
250                                    }
251                                    break;
252                                }
253                            }
254                        }
255    
256                    }
257                }
258                entry.addOutputValue(value);
259            }
260        }
261    
262        /**
263         * Creates the root element of the browser index.
264         * @param browseData the incoming data from the browser
265         * @return the new root element
266         */
267        public static Element buildPageElement(MCRIndexBrowserIncomingData browseData) {
268            // Build output index page
269            Element page = new Element("indexpage");
270            page.setAttribute("path", browseData.getPath());
271    
272            Element eIndex = new Element("index");
273            page.addContent(eIndex);
274    
275            eIndex.setAttribute("id", browseData.getSearchclass());
276            return page;
277        }
278    
279        /**
280         * Adds a result element to the given page element.
281         * @param pageElement the parent element
282         * @param browseData the incoming data from the browser
283         * @return the new results element.
284         */
285        public static Element buildResultsElement(Element pageElement, MCRIndexBrowserIncomingData browseData) {
286            Element results = new Element("results");
287            pageElement.addContent(results);
288            results.setAttribute("mode", browseData.getMode());
289            return results;
290        }
291    
292        /**
293         * Builds the prefix difference value for each delimiter.
294         * @param delims a list of delimiter.
295         */
296        protected void buildPrefixDifference(List<MyRangeDelim> delims) {
297            for (int i = 0; i < delims.size(); i++) {
298                MyRangeDelim curr = (delims.get(i));
299                MyRangeDelim prev = (delims.get(Math.max(0, i - 1)));
300                MyRangeDelim next = (delims.get(Math.min(i + 1, delims.size() - 1)));
301    
302                String vCurr = curr.value;
303                String vPrev = (i > 0 ? prev.value : "");
304                String vNext = (i < delims.size() - 1 ? next.value : "");
305    
306                String a = buildPrefixDifference(vCurr, vPrev);
307                String b = buildPrefixDifference(vCurr, vNext);
308                curr.diff = (a.length() > b.length() ? a : b);
309            }
310        }
311    
312        /**
313         * Compares two strings and returns the prefix difference.
314         * @param a the first string
315         * @param b the second string
316         * @return the prefix which is equal in both string
317         */
318        protected String buildPrefixDifference(String a, String b) {
319            if (a.equals(b))
320                return a;
321    
322            StringBuffer pdiff = new StringBuffer();
323    
324            for (int i = 0; i < Math.min(a.length(), b.length()); i++) {
325                pdiff.append(a.charAt(i));
326                if (a.charAt(i) != b.charAt(i))
327                    break;
328            }
329    
330            if ((a.length() > b.length()) && (b.equals(pdiff.toString())))
331                pdiff.append(a.charAt(pdiff.length()));
332    
333            return pdiff.toString();
334        }
335    
336        /**
337         * Adds each delimiter entry to the results element.
338         * @param results the parent results element
339         * @param delims a list of delimiters
340         */
341        protected void buildXML(Element results, List<MyRangeDelim> delims) {
342            for (int i = 0; i < delims.size(); i += 2) {
343                MyRangeDelim start = (delims.get(i));
344                MyRangeDelim end = (delims.get(i + 1));
345    
346                Element range = new Element("range");
347                results.addContent(range);
348    
349                Element eFrom = new Element("from");
350                eFrom.setAttribute("pos", String.valueOf(start.pos));
351                eFrom.setAttribute("short", start.diff);
352                eFrom.addContent(start.value);
353                range.addContent(eFrom);
354    
355                Element eTo = new Element("to");
356                eTo.setAttribute("pos", String.valueOf(end.pos));
357                eTo.setAttribute("short", end.diff);
358                eTo.addContent(end.value);
359                range.addContent(eTo);
360            }
361        }
362    
363        /**
364         * Holder class for an index browser delimiter entry. A delimiter entry
365         * is used, when a range of elements are displayed (folder style).
366         */
367        private static class MyRangeDelim {
368            int pos;
369    
370            String value;
371    
372            String diff;
373    
374            MyRangeDelim(int pos, String value) {
375                this.pos = pos;
376                this.value = value;
377            }
378        }
379    
380    }
381