001: //=============================================================================
002: //=== Copyright (C) 2001-2007 Food and Agriculture Organization of the
003: //=== United Nations (FAO-UN), United Nations World Food Programme (WFP)
004: //=== and United Nations Environment Programme (UNEP)
005: //===
006: //=== This program is free software; you can redistribute it and/or modify
007: //=== it under the terms of the GNU General Public License as published by
008: //=== the Free Software Foundation; either version 2 of the License, or (at
009: //=== your option) any later version.
010: //===
011: //=== This program is distributed in the hope that it will be useful, but
012: //=== WITHOUT ANY WARRANTY; without even the implied warranty of
013: //=== MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: //=== General Public License for more details.
015: //===
016: //=== You should have received a copy of the GNU General Public License
017: //=== along with this program; if not, write to the Free Software
018: //=== Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
019: //===
020: //=== Contact: Jeroen Ticheler - FAO - Viale delle Terme di Caracalla 2,
021: //=== Rome - Italy. email: geonetwork@osgeo.org
022: //==============================================================================
023:
024: package org.fao.geonet.kernel.csw.services.getrecords;
025:
026: import java.util.ArrayList;
027: import java.util.Collections;
028: import java.util.Comparator;
029: import java.util.HashMap;
030: import java.util.List;
031: import java.util.Set;
032: import java.util.StringTokenizer;
033: import jeeves.resources.dbms.Dbms;
034: import jeeves.server.context.ServiceContext;
035: import jeeves.utils.Log;
036: import jeeves.utils.Util;
037: import jeeves.utils.Xml;
038: import org.apache.lucene.document.Document;
039: import org.apache.lucene.index.IndexReader;
040: import org.apache.lucene.index.Term;
041: import org.apache.lucene.search.BooleanClause;
042: import org.apache.lucene.search.BooleanQuery;
043: import org.apache.lucene.search.Hits;
044: import org.apache.lucene.search.IndexSearcher;
045: import org.apache.lucene.search.Query;
046: import org.apache.lucene.search.TermQuery;
047: import org.fao.geonet.GeonetContext;
048: import org.fao.geonet.constants.Geonet;
049: import org.fao.geonet.csw.common.Csw.TypeName;
050: import org.fao.geonet.csw.common.exceptions.CatalogException;
051: import org.fao.geonet.csw.common.exceptions.InvalidParameterValueEx;
052: import org.fao.geonet.csw.common.exceptions.NoApplicableCodeEx;
053: import org.fao.geonet.kernel.AccessManager;
054: import org.fao.geonet.kernel.search.LuceneSearcher;
055: import org.fao.geonet.kernel.search.SearchManager;
056: import org.fao.geonet.kernel.search.LuceneUtils;
057: import org.jdom.Element;
058:
059: //=============================================================================
060:
061: class CatalogSearcher {
062: //---------------------------------------------------------------------------
063: //---
064: //--- Main search method
065: //---
066: //---------------------------------------------------------------------------
067:
068: //--- should return a list of id that match the given filter, ordered by sortFields
069:
070: public List<ResultItem> search(ServiceContext context,
071: Element filterExpr, Set<TypeName> typeNames,
072: List<SortField> sortFields) throws CatalogException {
073: Element luceneExpr = filterToLucene(context, filterExpr);
074:
075: if (luceneExpr != null) {
076: checkForErrors(luceneExpr);
077: remapFields(luceneExpr);
078: convertPhrases(luceneExpr);
079: }
080: //System.out.println("============================================");
081: //System.out.println("LUCENE:\n"+Xml.getString(luceneExpr));
082:
083: try {
084: List<ResultItem> results = search(context, luceneExpr);
085:
086: sort(results, sortFields);
087:
088: return results;
089: } catch (Exception e) {
090: context.error("Error while searching metadata ");
091: context
092: .error(" (C) StackTrace:\n"
093: + Util.getStackTrace(e));
094:
095: throw new NoApplicableCodeEx(
096: "Rised exception while searching metadata : " + e);
097: }
098: }
099:
100: //---------------------------------------------------------------------------
101: //---
102: //--- Private methods
103: //---
104: //---------------------------------------------------------------------------
105:
106: private Element filterToLucene(ServiceContext context,
107: Element filterExpr) throws NoApplicableCodeEx {
108: if (filterExpr == null)
109: return null;
110:
111: String styleSheet = context.getAppPath() + Geonet.Path.CSW
112: + Geonet.File.FILTER_TO_LUCENE;
113:
114: try {
115: return Xml.transform(filterExpr, styleSheet);
116: } catch (Exception e) {
117: context.error("Error during Filter to Lucene conversion : "
118: + e);
119: context.error(" (C) StackTrace\n" + Util.getStackTrace(e));
120:
121: throw new NoApplicableCodeEx(
122: "Error during Filter to Lucene conversion : " + e);
123: }
124: }
125:
126: //---------------------------------------------------------------------------
127:
128: private void checkForErrors(Element elem)
129: throws InvalidParameterValueEx {
130: List children = elem.getChildren();
131:
132: if (elem.getName().equals("error")) {
133: String type = elem.getAttributeValue("type");
134: String oper = Xml.getString((Element) children.get(0));
135:
136: throw new InvalidParameterValueEx(type, oper);
137: }
138:
139: for (int i = 0; i < children.size(); i++)
140: checkForErrors((Element) children.get(i));
141: }
142:
143: //---------------------------------------------------------------------------
144:
145: private void convertPhrases(Element elem) {
146: if (elem.getName().equals("TermQuery")) {
147: String field = elem.getAttributeValue("fld");
148: String text = elem.getAttributeValue("txt");
149:
150: if (text.indexOf(" ") != -1) {
151: elem.setName("PhraseQuery");
152:
153: StringTokenizer st = new StringTokenizer(text, " ");
154:
155: while (st.hasMoreTokens()) {
156: Element term = new Element("TermQuery");
157: term.setAttribute("fld", field);
158: term.setAttribute("txt", st.nextToken());
159:
160: elem.addContent(term);
161: }
162: }
163: }
164:
165: else {
166: List children = elem.getChildren();
167:
168: for (int i = 0; i < children.size(); i++)
169: convertPhrases((Element) children.get(i));
170: }
171: }
172:
173: //---------------------------------------------------------------------------
174:
175: private void remapFields(Element elem) {
176: String field = elem.getAttributeValue("fld");
177:
178: if (field != null) {
179: if (field.equals(""))
180: field = "any";
181:
182: String mapped = FieldMapper.map(field);
183:
184: if (mapped != null)
185: elem.setAttribute("fld", mapped);
186: else
187: Log.info(Geonet.CSW_SEARCH,
188: "Unknown queryable field : " + field);
189: }
190:
191: List children = elem.getChildren();
192:
193: for (int i = 0; i < children.size(); i++)
194: remapFields((Element) children.get(i));
195: }
196:
197: //---------------------------------------------------------------------------
198:
199: private List<ResultItem> search(ServiceContext context,
200: Element luceneExpr) throws Exception {
201: GeonetContext gc = (GeonetContext) context
202: .getHandlerContext(Geonet.CONTEXT_NAME);
203: SearchManager sm = gc.getSearchmanager();
204:
205: if (luceneExpr != null)
206: Log.debug(Geonet.CSW_SEARCH, "Search criteria:\n"
207: + Xml.getString(luceneExpr));
208:
209: Query data = (luceneExpr == null) ? null : LuceneSearcher
210: .makeQuery(luceneExpr);
211: Query groups = getGroupsQuery(context);
212: Query templ = new TermQuery(new Term("_isTemplate", "n"));
213:
214: //--- put query on groups in AND with lucene query
215:
216: BooleanQuery query = new BooleanQuery();
217:
218: BooleanClause.Occur occur = LuceneUtils
219: .convertRequiredAndProhibitedToOccur(true, false);
220: if (data != null)
221: query.add(data, occur);
222:
223: query.add(groups, occur);
224: query.add(templ, occur);
225:
226: //--- proper search
227:
228: IndexReader reader = IndexReader.open(sm.getLuceneDir());
229: IndexSearcher searcher = new IndexSearcher(reader);
230:
231: try {
232: Hits hits = searcher.search(query);
233:
234: Log.debug(Geonet.CSW_SEARCH, "Records matched : "
235: + hits.length());
236:
237: //--- retrieve results
238:
239: ArrayList<ResultItem> results = new ArrayList<ResultItem>();
240:
241: for (int i = 0; i < hits.length(); i++) {
242: Document doc = hits.doc(i);
243: String id = doc.get("_id");
244:
245: ResultItem ri = new ResultItem(id);
246: results.add(ri);
247:
248: for (String field : FieldMapper.getMappedFields()) {
249: String value = doc.get(field);
250:
251: if (value != null)
252: ri.add(field, value);
253: }
254: }
255:
256: return results;
257: } finally {
258: searcher.close();
259: reader.close();
260: }
261: }
262:
263: //---------------------------------------------------------------------------
264:
265: private Query getGroupsQuery(ServiceContext context)
266: throws Exception {
267: Dbms dbms = (Dbms) context.getResourceManager().open(
268: Geonet.Res.MAIN_DB);
269:
270: GeonetContext gc = (GeonetContext) context
271: .getHandlerContext(Geonet.CONTEXT_NAME);
272: AccessManager am = gc.getAccessManager();
273: Set<String> hs = am.getUserGroups(dbms, context
274: .getUserSession(), context.getIpAddress());
275:
276: BooleanQuery query = new BooleanQuery();
277:
278: String operView = "_op0";
279:
280: BooleanClause.Occur occur = LuceneUtils
281: .convertRequiredAndProhibitedToOccur(false, false);
282: for (Object group : hs) {
283: TermQuery tq = new TermQuery(new Term(operView, group
284: .toString()));
285: query.add(tq, occur);
286: }
287:
288: return query;
289: }
290:
291: //---------------------------------------------------------------------------
292:
293: private void sort(List<ResultItem> results,
294: List<SortField> sortFields) {
295: if (sortFields.isEmpty())
296: return;
297:
298: ArrayList<SortField> fields = new ArrayList<SortField>();
299:
300: for (SortField sf : sortFields) {
301: String mapped = FieldMapper.map(sf.field);
302:
303: if (mapped != null) {
304: sf.field = mapped;
305: fields.add(sf);
306: } else
307: Log
308: .info(Geonet.CSW_SEARCH,
309: "Skipping unknown sortable field : "
310: + sf.field);
311: }
312:
313: Collections.sort(results, new ItemComparator(fields));
314: }
315: }
316:
317: //=============================================================================
318:
319: class ResultItem {
320: private String id;
321:
322: private HashMap<String, String> hmFields = new HashMap<String, String>();
323:
324: //---------------------------------------------------------------------------
325: //---
326: //--- Constructor
327: //---
328: //---------------------------------------------------------------------------
329:
330: public ResultItem(String id) {
331: this .id = id;
332: }
333:
334: //---------------------------------------------------------------------------
335: //---
336: //--- API methods
337: //---
338: //---------------------------------------------------------------------------
339:
340: public String getID() {
341: return id;
342: }
343:
344: //---------------------------------------------------------------------------
345:
346: public void add(String field, String value) {
347: hmFields.put(field, value);
348: }
349:
350: //---------------------------------------------------------------------------
351:
352: public String getValue(String field) {
353: return hmFields.get(field);
354: }
355: }
356:
357: //=============================================================================
358:
359: class ItemComparator implements Comparator<ResultItem> {
360: private List<SortField> sortFields;
361:
362: //---------------------------------------------------------------------------
363: //---
364: //--- Constructor
365: //---
366: //---------------------------------------------------------------------------
367:
368: public ItemComparator(List<SortField> sf) {
369: sortFields = sf;
370: }
371:
372: //---------------------------------------------------------------------------
373: //---
374: //--- Comparator interface
375: //---
376: //---------------------------------------------------------------------------
377:
378: public int compare(ResultItem ri1, ResultItem ri2) {
379: for (SortField sf : sortFields) {
380: String value1 = ri1.getValue(sf.field);
381: String value2 = ri2.getValue(sf.field);
382:
383: //--- some metadata may have null values for some fields
384: //--- in this case we push null values at the bottom
385:
386: if (value1 == null && value2 != null)
387: return 1;
388:
389: if (value1 != null && value2 == null)
390: return -1;
391:
392: if (value1 == null || value2 == null)
393: return 0;
394:
395: //--- values are ok, do a proper comparison
396:
397: int comp = value1.compareTo(value2);
398:
399: if (comp == 0)
400: continue;
401:
402: return (!sf.descend) ? comp : -comp;
403: }
404:
405: return 0;
406: }
407: }
408:
409: //=============================================================================
|