/* See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * Esri Inc. licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.esri.gpt.control.georss; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.lucene.document.Document; import org.apache.lucene.document.Fieldable; import com.esri.gpt.catalog.discovery.DiscoveredRecord; import com.esri.gpt.catalog.discovery.DiscoveredRecords; import com.esri.gpt.catalog.discovery.DiscoveryException; import com.esri.gpt.catalog.discovery.rest.RestQuery; import com.esri.gpt.catalog.lucene.LuceneQueryAdapter; import com.esri.gpt.catalog.search.OpenSearchProperties; import com.esri.gpt.catalog.search.ResourceIdentifier; import com.esri.gpt.framework.context.RequestContext; import com.esri.gpt.framework.jsf.FacesContextBroker; import com.esri.gpt.framework.jsf.MessageBroker; import com.esri.gpt.framework.util.Val; import java.util.AbstractList; /** * Performs search to generate search results for dcat response. */ public abstract class DcatJsonSearchEngine extends JsonSearchEngine { /** * Logger. */ private static final Logger LOG = Logger.getLogger(DcatJsonSearchEngine.class.getCanonicalName()); /** * Default search engine. */ private static final DcatJsonSearchEngine defaultEngine = new DcatJsonSearchEngine() { @Override public IFeedRecords search(HttpServletRequest request, HttpServletResponse response, RequestContext context, RestQuery query) throws Exception { if(query.getFilter().getMaxRecords() > 10000){ query.getFilter().setMaxRecords(10000); } return doSearch(request, response, context, query); } }; /** * Creates instance of the search engine. * @return instance of {@link JsonSearchEngine} */ public static DcatJsonSearchEngine createInstance() { String className = getConfigParam("dcat.json.engine.className"); if (className.isEmpty()) { return defaultEngine; } else { try { Class engineClass = Class.forName(className); return (DcatJsonSearchEngine) engineClass.newInstance(); } catch (Exception ex) { LOG.log(Level.INFO, "Error creating JSON search engine: " + className +". Using default JSON search engine instead.", ex); return defaultEngine; } } } /** * Performs search operation. * @param request HTTP servlet request * @param response HTTP servlet response * @param context request context * @param query query * @return records * @throws Exception if searching fails */ @Override public IFeedRecords doSearch(HttpServletRequest request, HttpServletResponse response, RequestContext context, RestQuery query) throws Exception { MessageBroker msgBroker = new FacesContextBroker(request, response).extractMessageBroker(); final Map<DiscoveredRecord, Map<String, List<String>>> mapping = new HashMap<DiscoveredRecord, Map<String, List<String>>>(); List<IFeedRecords.FieldMeta> fields = new ArrayList<IFeedRecords.FieldMeta>(); loadStdFieldMeta(fields); int startRecord = query.getFilter().getStartRecord(); boolean returnIdsOnly = Val.chkBool(Val.chkStr(request.getParameter("returnIdsOnly")), false); if (returnIdsOnly) { startRecord = 1; query.getFilter().setMaxRecords(1); LuceneQueryAdapter tmp = new LuceneQueryAdapter(); tmp.execute(context, query); query.getFilter().setMaxRecords(query.getResult().getNumberOfHits()); } query.getFilter().setStartRecord(startRecord); LuceneQueryAdapter lqa = new LuceneQueryAdapter() { @Override protected void onRecord(DiscoveredRecord record, Document document) { Map<String, List<String>> fieldMap = new HashMap<String, List<String>>(); for (Fieldable field : document.getFields()) { String name = field.name(); List<String> fieldValues = fieldMap.get(name); if (fieldValues == null) { fieldValues = new ArrayList<String>(); fieldMap.put(name, fieldValues); } fieldValues.add(field.stringValue()); } mapping.put(record, fieldMap); } }; lqa.execute(context, query); startRecord += query.getFilter().getMaxRecords(); loadLuceneMeta(context, fields); OpenSearchProperties osProps = new OpenSearchProperties(); osProps.setShortName(msgBroker.retrieveMessage("catalog.openSearch.shortName")); osProps.setNumberOfHits(query.getResult().getNumberOfHits()); osProps.setStartRecord(query.getFilter().getStartRecord()); osProps.setRecordsPerPage(query.getFilter().getMaxRecords()); ResourceIdentifier resourceIdentifier = ResourceIdentifier.newIdentifier(context); DiscoveredRecordsAdapter discoveredRecordsAdapter = new DiscoveredRecordsAdapter(resourceIdentifier, osProps, fields, query.getResult().getRecords(), mapping); FeedLinkBuilder linkBuilder = new FeedLinkBuilder(context, RequestContext.resolveBaseContextPath(request), msgBroker); for (IFeedRecord record : discoveredRecordsAdapter) { linkBuilder.build(record); } return discoveredRecordsAdapter; /* MessageBroker msgBroker = new FacesContextBroker(request, response).extractMessageBroker(); FeedLinkBuilder linkBuilder = new FeedLinkBuilder(RequestContext.resolveBaseContextPath(request), msgBroker); DcatRecordsAdapter discoveredRecordsAdapter = new DcatRecordsAdapter(msgBroker, linkBuilder, context, query); return discoveredRecordsAdapter; */ } /** * DCAT records adapter. */ public static class DcatRecordsAdapter extends AbstractList<IFeedRecord> implements IFeedRecords { private static List<FieldMeta> fieldMetaList = new ArrayList<FieldMeta>(); private MessageBroker msgBroker; private FeedLinkBuilder linkBuilder; private RequestContext context; private RestQuery query; /* page buffer */ private ArrayList<IFeedRecord> feedRecords = new ArrayList<IFeedRecord>(); private boolean initialized; /** * Creates instance of the adapter. * @param msgBroker message broker * @param linkBuilder link builder * @param context request context * @param query query */ public DcatRecordsAdapter(MessageBroker msgBroker, FeedLinkBuilder linkBuilder, RequestContext context, RestQuery query) { this.msgBroker = msgBroker; this.linkBuilder = linkBuilder; this.context = context; this.query = query; } @Override public IFeedRecord get(int index) { try { // assure initialization initialize(); // get currently loaded page number int currentPage = getCurrentPage(); // get page for the requested record int pageNumber = getPageForRecord(index); // if current page is different than requested, load a correct page if (currentPage!=pageNumber) { loadPage(pageNumber); } // calculate offset of the record within the current page int offset = index - (query.getFilter().getStartRecord()-1); // get record IFeedRecord record = feedRecords.get(offset); return record; } catch (DiscoveryException ex) { throw new IndexOutOfBoundsException(ex.getMessage()); } } @Override public int size() { try { initialize(); return query.getResult().getNumberOfHits(); } catch (DiscoveryException ex) { return 0; } } /** * First time initialization. * @throws DiscoveryException if initialization fails */ private void initialize() throws DiscoveryException { if (!initialized) { loadPage(0); initialized = true; } } /** * Loads page of information. * @param pageNumber page number * @throws DiscoveryException if loading page fails */ private void loadPage(int pageNumber) throws DiscoveryException { // clear buffers feedRecords.clear(); query.getResult().getRecords().clear(); // prepare and execute query int startRecord = getStartRecordOfPage(pageNumber); query.getFilter().setStartRecord(startRecord); LuceneQueryAdapterImpl lqa = new LuceneQueryAdapterImpl(); lqa.execute(context, query); ResourceIdentifier resourceIdentifier = ResourceIdentifier.newIdentifier(context); // traverse through discovered records and build IFeedRecord for each of them (as DiscoveredRecordAdapter) DiscoveredRecords records = query.getResult().getRecords(); for (DiscoveredRecord dr: records) { DiscoveredRecordAdapter record = new DiscoveredRecordAdapter(resourceIdentifier,dr); Map<String, IFeedAttribute> attrs = new HashMap<String, IFeedAttribute>(); Map<String,List<String>> data = lqa.getMapping().get(dr); if (data!=null) { for (Map.Entry<String,List<String>> e: data.entrySet()) { List<IFeedAttribute> l = new ArrayList<IFeedAttribute>(); for (String s: e.getValue()) { l.add(IFeedAttribute.Factory.create(s,256)); } attrs.put(e.getKey(), IFeedAttribute.Factory.create(l)); } record.getData(IFeedRecord.STD_COLLECTION_INDEX).putAll(attrs); } linkBuilder.build(record); feedRecords.add(record); } } /** * Gets first record of a page. * @param pageNumber page number (0-based index) * @return number of the first record of the page (1-based index) */ private int getStartRecordOfPage(int pageNumber) { int maxRecords = query.getFilter().getMaxRecords(); return pageNumber*maxRecords + 1; } /** * Gets current page. * @return current page (0-based index) */ private int getCurrentPage() { int startRecord = query.getFilter().getStartRecord(); return getPageForRecord(startRecord-1); } /** * Gets page for the record. * @param recordNumber record number (1-based index) * @return page number (0-based index) */ private int getPageForRecord(int recordNumber) { int maxRecords = query.getFilter().getMaxRecords(); return recordNumber/maxRecords; } /** * Gets number of pages. * @return number of pages */ private int getPagesCount() { int numberOfHits = query.getResult().getNumberOfHits(); int maxRecords = query.getFilter().getMaxRecords(); return numberOfHits>0? numberOfHits/maxRecords+1: 0; } @Override public OpenSearchProperties getOpenSearchProperties() { OpenSearchProperties osProps = new OpenSearchProperties(); osProps.setShortName(msgBroker.retrieveMessage("catalog.openSearch.shortName")); osProps.setNumberOfHits(query.getResult().getNumberOfHits()); osProps.setStartRecord(query.getFilter().getStartRecord()); osProps.setRecordsPerPage(query.getResult().getNumberOfHits()); return osProps; } @Override public List<FieldMeta> getMetaData() { return fieldMetaList; } } /** * Specific Lucene query adapter. */ private static class LuceneQueryAdapterImpl extends LuceneQueryAdapter { private Map<DiscoveredRecord, Map<String, List<String>>> mapping = new HashMap<DiscoveredRecord, Map<String, List<String>>>(); @Override protected void onRecord(DiscoveredRecord record, Document document) { Map<String, List<String>> fieldMap = new HashMap<String, List<String>>(); for (Fieldable field : document.getFields()) { String name = field.name(); List<String> fieldValues = fieldMap.get(name); if (fieldValues == null) { fieldValues = new ArrayList<String>(); fieldMap.put(name, fieldValues); } fieldValues.add(field.stringValue()); } mapping.put(record, fieldMap); } /** * Gets mapping. * @return mapping */ public Map<DiscoveredRecord, Map<String, List<String>>> getMapping() { return mapping; } } }