/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF 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 org.apache.manifoldcf.agents.output.opensearchserver;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TreeMap;
import org.apache.commons.io.FilenameUtils;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.HttpClientConnectionManager;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.protocol.HttpRequestExecutor;
import org.apache.manifoldcf.agents.interfaces.IOutputAddActivity;
import org.apache.manifoldcf.agents.interfaces.IOutputNotifyActivity;
import org.apache.manifoldcf.agents.interfaces.IOutputRemoveActivity;
import org.apache.manifoldcf.agents.interfaces.IOutputCheckActivity;
import org.apache.manifoldcf.agents.interfaces.RepositoryDocument;
import org.apache.manifoldcf.agents.interfaces.ServiceInterruption;
import org.apache.manifoldcf.agents.output.BaseOutputConnector;
import org.apache.manifoldcf.agents.output.opensearchserver.OpenSearchServerConnection.Result;
import org.apache.manifoldcf.core.interfaces.ConfigParams;
import org.apache.manifoldcf.core.interfaces.ConfigurationNode;
import org.apache.manifoldcf.core.interfaces.IHTTPOutput;
import org.apache.manifoldcf.core.interfaces.IPostParameters;
import org.apache.manifoldcf.core.interfaces.IThreadContext;
import org.apache.manifoldcf.core.interfaces.ManifoldCFException;
import org.apache.manifoldcf.core.interfaces.Specification;
import org.apache.manifoldcf.core.interfaces.SpecificationNode;
import org.apache.manifoldcf.core.interfaces.VersionContext;
public class OpenSearchServerConnector extends BaseOutputConnector
{
private final static String OPENSEARCHSERVER_INDEXATION_ACTIVITY = "Indexation";
private final static String OPENSEARCHSERVER_DELETION_ACTIVITY = "Deletion";
private final static String OPENSEARCHSERVER_SCHEDULER_ACTIVITY = "Scheduler";
private final static String[] OPENSEARCHSERVER_ACTIVITIES = {
OPENSEARCHSERVER_INDEXATION_ACTIVITY, OPENSEARCHSERVER_DELETION_ACTIVITY,
OPENSEARCHSERVER_SCHEDULER_ACTIVITY };
// Tab resources
private final static String OPENSEARCHSERVER_TAB_MESSAGE = "OpenSearchServerConnector.OpenSearchServer";
private final static String PARAMETERS_TAB_MESSAGE = "OpenSearchServerConnector.Parameters";
// Velocity templates
// These are not broken down by tabs because the design of this connector makes it difficult to do it that way.
/** Forward to the HTML template to edit the configuration parameters */
private static final String EDIT_CONFIG_FORWARD = "editConfiguration.html";
/** Forward to the HTML template to view the configuration parameters */
private static final String VIEW_CONFIG_FORWARD = "viewConfiguration.html";
/** Forward to the javascript to check the configuration parameters */
private static final String EDIT_CONFIG_HEADER_FORWARD = "editConfiguration.js";
/** Connection expiration interval */
private static final long EXPIRATION_INTERVAL = 60000L;
private HttpClientConnectionManager connectionManager = null;
private HttpClient client = null;
private long expirationTime = -1L;
public OpenSearchServerConnector()
{
}
@Override
public void connect(ConfigParams configParams)
{
super.connect(configParams);
}
protected HttpClient getSession()
throws ManifoldCFException
{
if (client == null)
{
final int executorTimeout = 300000;
final int socketTimeout = 60000;
final int connectionTimeout = 60000;
PoolingHttpClientConnectionManager poolingConnectionManager = new PoolingHttpClientConnectionManager();
poolingConnectionManager.setDefaultMaxPerRoute(1);
poolingConnectionManager.setValidateAfterInactivity(2000);
poolingConnectionManager.setDefaultSocketConfig(SocketConfig.custom()
.setTcpNoDelay(true)
.setSoTimeout(socketTimeout)
.build());
connectionManager = poolingConnectionManager;
RequestConfig.Builder requestBuilder = RequestConfig.custom()
.setCircularRedirectsAllowed(true).setSocketTimeout(socketTimeout)
.setExpectContinueEnabled(true)
.setConnectTimeout(connectionTimeout)
.setConnectionRequestTimeout(socketTimeout);
HttpClientBuilder clientBuilder = HttpClients
.custom()
.setConnectionManager(connectionManager)
.disableAutomaticRetries()
.setDefaultRequestConfig(requestBuilder.build())
.setRequestExecutor(new HttpRequestExecutor(executorTimeout));
client = clientBuilder.build();
}
expirationTime = System.currentTimeMillis() + EXPIRATION_INTERVAL;
return client;
}
protected void closeSession()
{
if (connectionManager != null)
{
connectionManager.shutdown();
connectionManager = null;
}
client = null;
expirationTime = -1L;
}
@Override
public void disconnect()
throws ManifoldCFException
{
super.disconnect();
closeSession();
}
@Override
public void poll()
throws ManifoldCFException
{
super.poll();
if (connectionManager != null)
{
if (System.currentTimeMillis() > expirationTime)
{
closeSession();
}
}
}
/**
* This method is called to assess whether to count this connector instance should
* actually be counted as being connected.
*
* @return true if the connector instance is actually connected.
*/
@Override
public boolean isConnected()
{
return connectionManager != null;
}
@Override
public String[] getActivitiesList()
{
return OPENSEARCHSERVER_ACTIVITIES;
}
/**
* Read the content of a resource, replace the variable ${PARAMNAME} with the
* value and copy it to the out.
*
* @param resName
* @param out
* @throws ManifoldCFException
*/
private static void outputResource(String resName, IHTTPOutput out,
Locale locale, OpenSearchServerParam params, String tabName,
Integer sequenceNumber, Integer actualSequenceNumber)
throws ManifoldCFException
{
Map<String, String> paramMap = null;
if (params != null)
{
paramMap = params.buildMap();
if (tabName != null)
{
paramMap.put("TabName", tabName);
}
if (actualSequenceNumber != null)
paramMap.put("SelectedNum", actualSequenceNumber.toString());
}
else
{
paramMap = new HashMap<String, String>();
}
if (sequenceNumber != null)
paramMap.put("SeqNum", sequenceNumber.toString());
Messages.outputResourceWithVelocity(out, locale, resName, paramMap, false);
}
@Override
public void outputConfigurationHeader(IThreadContext threadContext,
IHTTPOutput out, Locale locale, ConfigParams parameters,
List<String> tabsArray)
throws ManifoldCFException, IOException
{
super.outputConfigurationHeader(threadContext, out, locale, parameters,
tabsArray);
tabsArray.add(Messages.getString(locale, PARAMETERS_TAB_MESSAGE));
outputResource(EDIT_CONFIG_HEADER_FORWARD, out, locale, null, null, null,
null);
}
@Override
public void outputConfigurationBody(IThreadContext threadContext,
IHTTPOutput out, Locale locale, ConfigParams parameters, String tabName)
throws ManifoldCFException, IOException
{
super.outputConfigurationBody(threadContext, out, locale, parameters,
tabName);
OpenSearchServerConfig config = this.getConfigParameters(parameters);
outputResource(EDIT_CONFIG_FORWARD, out, locale, config, tabName, null,
null);
}
/**
* Build a Set of OpenSearchServer parameters. If configParams is null,
* getConfiguration() is used.
*
* @param configParams
*/
final private OpenSearchServerConfig getConfigParameters(
ConfigParams configParams)
{
if (configParams == null)
configParams = getConfiguration();
return new OpenSearchServerConfig(configParams);
}
@Override
public VersionContext getPipelineDescription(Specification os)
throws ManifoldCFException
{
return new VersionContext("", params, os);
}
@Override
public void viewConfiguration(IThreadContext threadContext, IHTTPOutput out,
Locale locale, ConfigParams parameters)
throws ManifoldCFException, IOException
{
outputResource(VIEW_CONFIG_FORWARD, out, locale,
getConfigParameters(parameters), null, null, null);
}
@Override
public String processConfigurationPost(IThreadContext threadContext,
IPostParameters variableContext, ConfigParams parameters)
throws ManifoldCFException
{
OpenSearchServerConfig.contextToConfig(variableContext, parameters);
return null;
}
// Apparently, only one connection to any given Open Search Server instance is allowed at a time.
private static Map<String, Integer> ossInstances = new TreeMap<String, Integer>();
private final Integer addInstance(OpenSearchServerConfig config)
{
synchronized (ossInstances)
{
String uii = config.getUniqueIndexIdentifier();
Integer count = ossInstances.get(uii);
if (count == null)
{
count = new Integer(1);
ossInstances.put(uii, count);
}
else
count++;
return count;
}
}
private final void removeInstance(OpenSearchServerConfig config)
{
synchronized (ossInstances)
{
String uii = config.getUniqueIndexIdentifier();
Integer count = ossInstances.get(uii);
if (count == null)
return;
if (--count == 0)
ossInstances.remove(uii);
}
}
/** Add (or replace) a document in the output data store using the connector.
* This method presumes that the connector object has been configured, and it is thus able to communicate with the output data store should that be
* necessary.
*@param documentURI is the URI of the document. The URI is presumed to be the unique identifier which the output data store will use to process
* and serve the document. This URI is constructed by the repository connector which fetches the document, and is thus universal across all output connectors.
*@param pipelineDescription includes the description string that was constructed for this document by the getOutputDescription() method.
*@param document is the document data to be processed (handed to the output data store).
*@param authorityNameString is the name of the authority responsible for authorizing any access tokens passed in with the repository document. May be null.
*@param activities is the handle to an object that the implementer of a pipeline connector may use to perform operations, such as logging processing activity,
* or sending a modified document to the next stage in the pipeline.
*@return the document status (accepted or permanently rejected).
*@throws IOException only if there's a stream error reading the document data.
*/
@Override
public int addOrReplaceDocumentWithException(String documentURI, VersionContext pipelineDescription,
RepositoryDocument document, String authorityNameString,
IOutputAddActivity activities)
throws ManifoldCFException, ServiceInterruption, IOException
{
HttpClient client = getSession();
OpenSearchServerConfig config = getConfigParameters(null);
Integer count = addInstance(config);
synchronized (count)
{
try
{
long startTime = System.currentTimeMillis();
OpenSearchServerIndex oi = new OpenSearchServerIndex(client,
documentURI, config, document, authorityNameString, activities);
activities.recordActivity(startTime,
OPENSEARCHSERVER_INDEXATION_ACTIVITY, document.getBinaryLength(),
documentURI, oi.getResultCode(), oi.getResultDescription());
if (oi.getResult() != Result.OK)
return DOCUMENTSTATUS_REJECTED;
}
finally
{
removeInstance(config);
}
return DOCUMENTSTATUS_ACCEPTED;
}
}
@Override
public void removeDocument(String documentURI, String outputDescription,
IOutputRemoveActivity activities)
throws ManifoldCFException, ServiceInterruption
{
HttpClient client = getSession();
long startTime = System.currentTimeMillis();
OpenSearchServerDelete od = new OpenSearchServerDelete(client, documentURI,
getConfigParameters(null));
activities.recordActivity(startTime, OPENSEARCHSERVER_DELETION_ACTIVITY,
null, documentURI, od.getResultCode(), od.getResultDescription());
}
@Override
public String check()
throws ManifoldCFException
{
HttpClient client = getSession();
OpenSearchServerSchema oss = new OpenSearchServerSchema(client,
getConfigParameters(null));
return oss.getResult().name() + " " + oss.getResultDescription();
}
@Override
public void noteJobComplete(IOutputNotifyActivity activities)
throws ManifoldCFException, ServiceInterruption
{
HttpClient client = getSession();
long startTime = System.currentTimeMillis();
OpenSearchServerConfig config = getConfigParameters(null);
String schedulerJob = config.getSchedulerJob();
if (schedulerJob != null && schedulerJob.trim().length() > 0)
{
OpenSearchServerScheduler oo = new OpenSearchServerScheduler(client,
getConfigParameters(null), schedulerJob.trim());
activities.recordActivity(startTime, OPENSEARCHSERVER_SCHEDULER_ACTIVITY,
null, oo.getCallUrlSnippet(), oo.getResultCode(),
oo.getResultDescription());
}
}
}