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