/*
* Copyright 2012 Esri.
*
* Licensed 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 com.esri.gpt.catalog.context.CatalogIndexException;
import com.esri.gpt.catalog.discovery.DiscoveredRecord;
import com.esri.gpt.catalog.discovery.rest.RestQuery;
import com.esri.gpt.catalog.lucene.LuceneIndexAdapter;
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.ApplicationConfiguration;
import com.esri.gpt.framework.context.ApplicationContext;
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.sql.ConnectionBroker;
import com.esri.gpt.framework.sql.ManagedConnection;
import com.esri.gpt.framework.util.Val;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
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 org.apache.lucene.index.IndexReader;
import org.apache.lucene.search.IndexSearcher;
/**
* JSON search engine.
* <p/>
* Uses <i>json.engine.className</i> parameter from <b>gpt.xml</b> to create instance.
* Creates default instance if parameter is empty.
* <p/>
* Two parameters: <i>json.meta.lucene</i> and <i>json.meta.catalog</i> from gpt.xml
* configuration file determines whether certain family of output parameters
* is allowed to be print to the output or not. Default: <code>true</code>.
* @see ExtJsonFeedWriter
*/
public abstract class JsonSearchEngine {
/**
* Logger.
*/
private static final Logger LOG = Logger.getLogger(JsonSearchEngine.class.getCanonicalName());
/**
* Default search engine.
*/
private static final JsonSearchEngine defaultEngine = new JsonSearchEngine() {
@Override
public IFeedRecords search(HttpServletRequest request, HttpServletResponse response, RequestContext context, RestQuery query) throws Exception {
return doSearch(request, response, context, query);
}
};
/**
* Creates instance of the search engine.
* @return instance of {@link JsonSearchEngine}
*/
public static JsonSearchEngine createInstance() {
String className = getConfigParam("json.engine.className");
if (className.isEmpty()) {
return defaultEngine;
} else {
try {
Class engineClass = Class.forName(className);
return (JsonSearchEngine) 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
*/
public abstract IFeedRecords search(HttpServletRequest request, HttpServletResponse response, RequestContext context, RestQuery query) throws Exception;
/**
* Loads standard field meta info.
* @param fields list of fields
*/
protected void loadStdFieldMeta(List<IFeedRecords.FieldMeta> fields) {
fields.add(new IFeedRecords.FieldMeta("objectid", "esriFieldTypeOID", "OBJECTID"));
fields.add(new IFeedRecords.FieldMeta("title", "esriFieldTypeString", "Title", 256));
fields.add(new IFeedRecords.FieldMeta("id", "esriFieldTypeGUID", "UUID", 38));
fields.add(new IFeedRecords.FieldMeta("updated", "esriFieldTypeDate", "Updated", 20));
fields.add(new IFeedRecords.FieldMeta("contentType", "esriFieldTypeString", "Content Type", 64));
fields.add(new IFeedRecords.FieldMeta("summary", "esriFieldTypeString", "Summary", 256));
fields.add(new IFeedRecords.FieldMeta("shape", "esriFieldTypeGeometry", "Shape"));
}
/**
* 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
*/
protected 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);
loadCatalog(context, discoveredRecordsAdapter);
FeedLinkBuilder linkBuilder = new FeedLinkBuilder(context, RequestContext.resolveBaseContextPath(request), msgBroker);
for (IFeedRecord record : discoveredRecordsAdapter) {
linkBuilder.build(record);
}
return discoveredRecordsAdapter;
}
/**
* Loads Lucene index metadata.
* @param context request context
* @param fields list of fields
* @throws CatalogIndexException if accessing index fails
*/
protected void loadLuceneMeta(RequestContext context, List<IFeedRecords.FieldMeta> fields) throws CatalogIndexException {
if (!isLuceneMetaAllowed()) {
return;
}
LuceneIndexAdapter indexAdapter = new LuceneIndexAdapter(context);
IndexSearcher searcher = null;
try {
searcher = indexAdapter.newSearcher();
IndexReader indexReader = searcher.getIndexReader();
for (String fieldName : indexReader.getFieldNames(IndexReader.FieldOption.ALL)) {
fields.add(new IFeedRecords.FieldMeta(IFeedRecord.STD_COLLECTION_INDEX + "." + fieldName, "esriFieldTypeString", fieldName));
}
} catch (Exception e) {
String sMsg = "Error accessing index:\n " + Val.chkStr(e.getMessage());
throw new CatalogIndexException(sMsg, e);
} finally {
indexAdapter.closeSearcher(searcher);
}
}
/**
* Loads catalog meta.
* @param context request context
* @param records records
* @throws SQLException if accessing catalog database fails
*/
protected void loadCatalog(RequestContext context, IFeedRecords records) throws SQLException {
if (!isCatalogMetaAllowed()) {
return;
}
ConnectionBroker connectionBroker = context.getConnectionBroker();
ManagedConnection managedConnection = connectionBroker.returnConnection("");
Connection conn = managedConnection.getJdbcConnection();
boolean firstRecord = true;
for (IFeedRecord record : records) {
PreparedStatement st = null;
ResultSet rs = null;
try {
st = conn.prepareStatement("SELECT * FROM GPT_RESOURCE WHERE DOCUUID=?");
st.setString(1, record.getUuid());
rs = st.executeQuery();
if (rs.next()) {
ResultSetMetaData metaData = rs.getMetaData();
for (int i = 1; i <= metaData.getColumnCount(); i++) {
String name = metaData.getColumnName(i);
Object value = rs.getObject(i);
IFeedAttribute attribute = IFeedAttribute.Factory.create(value, metaData.getColumnDisplaySize(i));
if (firstRecord) {
records.getMetaData().add(new IFeedRecords.FieldMeta(IFeedRecord.STD_COLLECTION_CATALOG + "." + name, attribute.getEsriType(), name, attribute.getLength()));
}
record.getData(IFeedRecord.STD_COLLECTION_CATALOG).put(name, attribute);
}
}
firstRecord = false;
} finally {
if (rs != null) {
try {
rs.close();
} catch (SQLException e) {
}
}
if (st != null) {
try {
st.close();
} catch (SQLException e) {
}
}
}
}
}
/**
* Gets gpt.xml configuration parameter.
* @param paramName parameter name
* @return parameter value
*/
protected static String getConfigParam(String paramName) {
ApplicationContext appCtx = ApplicationContext.getInstance();
ApplicationConfiguration appCfg = appCtx.getConfiguration();
return Val.chkStr(appCfg.getCatalogConfiguration().getParameters().getValue(paramName));
}
/**
* Checks if Lucene data is allowed.
* Reads "json.meta.lucene" configuration parameter.
* @return <code>true</code> if data is allowed
*/
protected boolean isLuceneMetaAllowed() {
String sMeta = getConfigParam("json.meta.lucene");
return Val.chkBool(sMeta, true);
}
/**
* Checks if catalog data is allowed.
* Reads "json.meta.catalog" configuration parameter.
* @return <code>true</code> if data is allowed
*/
protected boolean isCatalogMetaAllowed() {
String sMeta = getConfigParam("json.meta.catalog");
return Val.chkBool(sMeta, true);
}
}