blob: 4128d0b9147dd97f062e36e253d4cdb661e1ab50 [file] [log] [blame]
/* $Id: JDBCConnector.java 988245 2010-08-23 18:39:35Z kwright $ */
/**
* 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.crawler.connectors.jdbc;
import org.apache.manifoldcf.core.interfaces.*;
import org.apache.manifoldcf.agents.interfaces.*;
import org.apache.manifoldcf.crawler.interfaces.*;
import org.apache.manifoldcf.crawler.system.Logging;
import org.apache.manifoldcf.core.database.*;
import org.apache.manifoldcf.connectorcommon.interfaces.*;
import org.apache.manifoldcf.jdbc.JDBCConnection;
import org.apache.manifoldcf.jdbc.JDBCConstants;
import org.apache.manifoldcf.jdbc.IDynamicResultSet;
import org.apache.manifoldcf.jdbc.IDynamicResultRow;
import java.nio.charset.StandardCharsets;
import java.sql.*;
import javax.naming.*;
import javax.sql.*;
import java.io.*;
import java.util.*;
/** JDBC repository connector.
*/
public class JDBCConnector extends org.apache.manifoldcf.crawler.connectors.BaseRepositoryConnector
{
public static final String _rcsid = "@(#)$Id: JDBCConnector.java 988245 2010-08-23 18:39:35Z kwright $";
// Activities that we know about
protected final static String ACTIVITY_EXTERNAL_QUERY = "external query";
protected final static String ACTIVITY_FETCH = "fetch";
// Activities list
protected static final String[] activitiesList = new String[]{ACTIVITY_EXTERNAL_QUERY, ACTIVITY_FETCH};
/** Deny access token for default authority */
private final static String defaultAuthorityDenyToken = "DEAD_AUTHORITY";
protected JDBCConnection connection = null;
protected String jdbcProvider = null;
protected String accessMethod = null;
protected String host = null;
protected String databaseName = null;
protected String rawDriverString = null;
protected String userName = null;
protected String password = null;
/** Constructor.
*/
public JDBCConnector()
{
}
/** Set up a session */
protected void getSession()
throws ManifoldCFException
{
if (connection == null)
{
if (jdbcProvider == null || jdbcProvider.length() == 0)
throw new ManifoldCFException("Missing parameter '"+JDBCConstants.providerParameter+"'");
if ((host == null || host.length() == 0) && (rawDriverString == null || rawDriverString.length() == 0))
throw new ManifoldCFException("Missing parameter '"+JDBCConstants.hostParameter+"' or '"+JDBCConstants.driverStringParameter+"'");
connection = new JDBCConnection(jdbcProvider,(accessMethod==null || accessMethod.equals("name")),host,databaseName,rawDriverString,userName,password);
}
}
/** Return the list of activities that this connector supports (i.e. writes into the log).
*@return the list.
*/
@Override
public String[] getActivitiesList()
{
return activitiesList;
}
/** Model. Depending on what people enter for the seeding query, this could be either ALL or
* could be less than that. So, I've decided it will be at least the adds and changes, and
* won't include the deletes.
*/
@Override
public int getConnectorModel()
{
return MODEL_ADD_CHANGE;
}
/** Connect. The configuration parameters are included.
*@param configParams are the configuration parameters for this connection.
*/
@Override
public void connect(ConfigParams configParams)
{
super.connect(configParams);
jdbcProvider = configParams.getParameter(JDBCConstants.providerParameter);
accessMethod = configParams.getParameter(JDBCConstants.methodParameter);
host = configParams.getParameter(JDBCConstants.hostParameter);
databaseName = configParams.getParameter(JDBCConstants.databaseNameParameter);
rawDriverString = configParams.getParameter(JDBCConstants.driverStringParameter);
userName= configParams.getParameter(JDBCConstants.databaseUserName);
password = configParams.getObfuscatedParameter(JDBCConstants.databasePassword);
}
/** Check status of connection.
*/
@Override
public String check()
throws ManifoldCFException
{
try
{
getSession();
// Attempt to fetch a connection; if this succeeds we pass
connection.testConnection();
return super.check();
}
catch (ServiceInterruption e)
{
if (Logging.connectors.isDebugEnabled())
Logging.connectors.debug("Service interruption in check(): "+e.getMessage(),e);
return "Transient error: "+e.getMessage();
}
}
/** Close the connection. Call this before discarding the repository connector.
*/
@Override
public void disconnect()
throws ManifoldCFException
{
connection = null;
host = null;
jdbcProvider = null;
accessMethod = null;
databaseName = null;
rawDriverString = null;
userName = null;
password = null;
super.disconnect();
}
/** Get the bin name string for a document identifier. The bin name describes the queue to which the
* document will be assigned for throttling purposes. Throttling controls the rate at which items in a
* given queue are fetched; it does not say anything about the overall fetch rate, which may operate on
* multiple queues or bins.
* For example, if you implement a web crawler, a good choice of bin name would be the server name, since
* that is likely to correspond to a real resource that will need real throttle protection.
*@param documentIdentifier is the document identifier.
*@return the bin name.
*/
@Override
public String[] getBinNames(String documentIdentifier)
{
return new String[]{(rawDriverString==null||rawDriverString.length()==0)?host:rawDriverString};
}
/** Queue "seed" documents. Seed documents are the starting places for crawling activity. Documents
* are seeded when this method calls appropriate methods in the passed in ISeedingActivity object.
*
* This method can choose to find repository changes that happen only during the specified time interval.
* The seeds recorded by this method will be viewed by the framework based on what the
* getConnectorModel() method returns.
*
* It is not a big problem if the connector chooses to create more seeds than are
* strictly necessary; it is merely a question of overall work required.
*
* The end time and seeding version string passed to this method may be interpreted for greatest efficiency.
* For continuous crawling jobs, this method will
* be called once, when the job starts, and at various periodic intervals as the job executes.
*
* When a job's specification is changed, the framework automatically resets the seeding version string to null. The
* seeding version string may also be set to null on each job run, depending on the connector model returned by
* getConnectorModel().
*
* Note that it is always ok to send MORE documents rather than less to this method.
* The connector will be connected before this method can be called.
*@param activities is the interface this method should use to perform whatever framework actions are desired.
*@param spec is a document specification (that comes from the job).
*@param seedTime is the end of the time range of documents to consider, exclusive.
*@param lastSeedVersionString is the last seeding version string for this job, or null if the job has no previous seeding version string.
*@param jobMode is an integer describing how the job is being run, whether continuous or once-only.
*@return an updated seeding version string, to be stored with the job.
*/
@Override
public String addSeedDocuments(ISeedingActivity activities, Specification spec,
String lastSeedVersion, long seedTime, int jobMode)
throws ManifoldCFException, ServiceInterruption
{
long startTime;
if (lastSeedVersion == null)
startTime = 0L;
else
{
// Unpack seed time from seed version string
startTime = new Long(lastSeedVersion).longValue();
}
getSession();
// Set up the query
TableSpec ts = new TableSpec(spec);
VariableMap vm = new VariableMap();
addConstant(vm,JDBCConstants.idReturnVariable,JDBCConstants.idReturnColumnName);
addVariable(vm,JDBCConstants.startTimeVariable,startTime);
addVariable(vm,JDBCConstants.endTimeVariable,seedTime);
// Do the substitution
ArrayList paramList = new ArrayList();
StringBuilder sb = new StringBuilder();
substituteQuery(ts.idQuery,vm,sb,paramList);
IDynamicResultSet idSet;
String queryText = sb.toString();
long startQueryTime = System.currentTimeMillis();
// Contract for IDynamicResultset indicates that if successfully obtained, it MUST
// be closed.
try
{
idSet = connection.executeUncachedQuery(queryText,paramList,-1);
}
catch (ServiceInterruption e)
{
// If failure, record the failure.
activities.recordActivity(new Long(startQueryTime), ACTIVITY_EXTERNAL_QUERY, null,
createQueryString(queryText,paramList), "ERROR", e.getMessage(), null);
throw e;
}
catch (ManifoldCFException e)
{
// If failure, record the failure.
activities.recordActivity(new Long(startQueryTime), ACTIVITY_EXTERNAL_QUERY, null,
createQueryString(queryText,paramList), "ERROR", e.getMessage(), null);
throw e;
}
try
{
// If success, record that too.
activities.recordActivity(new Long(startQueryTime), ACTIVITY_EXTERNAL_QUERY, null,
createQueryString(queryText,paramList), "OK", null, null);
while (true)
{
IDynamicResultRow row = idSet.getNextRow();
if (row == null)
break;
try
{
Object o = row.getValue(JDBCConstants.idReturnColumnName);
if (o == null)
throw new ManifoldCFException("Bad seed query; doesn't return $(IDCOLUMN) column. Try using quotes around $(IDCOLUMN) variable, e.g. \"$(IDCOLUMN)\", or, for MySQL, select \"by label\" in your repository connection.");
String idValue = JDBCConnection.readAsString(o);
activities.addSeedDocument(idValue);
}
finally
{
row.close();
}
}
}
finally
{
idSet.close();
}
return new Long(seedTime).toString();
}
/** Process a set of documents.
* This is the method that should cause each document to be fetched, processed, and the results either added
* to the queue of documents for the current job, and/or entered into the incremental ingestion manager.
* The document specification allows this class to filter what is done based on the job.
* The connector will be connected before this method can be called.
*@param documentIdentifiers is the set of document identifiers to process.
*@param statuses are the currently-stored document versions for each document in the set of document identifiers
* passed in above.
*@param activities is the interface this method should use to queue up new document references
* and ingest documents.
*@param jobMode is an integer describing how the job is being run, whether continuous or once-only.
*@param usesDefaultAuthority will be true only if the authority in use for these documents is the default one.
*/
@Override
public void processDocuments(String[] documentIdentifiers, IExistingVersions statuses, Specification spec,
IProcessActivity activities, int jobMode, boolean usesDefaultAuthority)
throws ManifoldCFException, ServiceInterruption
{
TableSpec ts = new TableSpec(spec);
Set<String> acls = ts.getAcls();
String[] versionsReturned = new String[documentIdentifiers.length];
// If there is no version query, then always return empty string for all documents.
// This will mean that processDocuments will be called
// for all. ProcessDocuments will then be responsible for doing document deletes itself,
// based on the query results.
Map<String,String> documentVersions = new HashMap<String,String>();
if (ts.versionQuery != null && ts.versionQuery.length() > 0)
{
// If there IS a versions query, do it. First set up the variables, then do the substitution.
VariableMap vm = new VariableMap();
addConstant(vm,JDBCConstants.idReturnVariable,JDBCConstants.idReturnColumnName);
addConstant(vm,JDBCConstants.versionReturnVariable,JDBCConstants.versionReturnColumnName);
if (addIDList(vm,JDBCConstants.idListVariable,documentIdentifiers,null))
{
// Do the substitution
ArrayList paramList = new ArrayList();
StringBuilder sb = new StringBuilder();
substituteQuery(ts.versionQuery,vm,sb,paramList);
// Now, build a result return, and a hash table so we can correlate the returned values with the place to put them.
// We presume that if the row is missing, the document is gone.
// Fire off the query!
getSession();
IDynamicResultSet result;
String queryText = sb.toString();
long startTime = System.currentTimeMillis();
// Get a dynamic resultset. Contract for dynamic resultset is that if
// one is returned, it MUST be closed, or a connection will leak.
try
{
result = connection.executeUncachedQuery(queryText,paramList,-1);
}
catch (ManifoldCFException e)
{
// If failure, record the failure.
if (e.getErrorCode() != ManifoldCFException.INTERRUPTED)
activities.recordActivity(new Long(startTime), ACTIVITY_EXTERNAL_QUERY, null,
createQueryString(queryText,paramList), "ERROR", e.getMessage(), null);
throw e;
}
try
{
// If success, record that too.
activities.recordActivity(new Long(startTime), ACTIVITY_EXTERNAL_QUERY, null,
createQueryString(queryText,paramList), "OK", null, null);
// Now, go through resultset
while (true)
{
IDynamicResultRow row = result.getNextRow();
if (row == null)
break;
try
{
Object o = row.getValue(JDBCConstants.idReturnColumnName);
if (o == null)
throw new ManifoldCFException("Bad version query; doesn't return $(IDCOLUMN) column. Try using quotes around $(IDCOLUMN) variable, e.g. \"$(IDCOLUMN)\", or, for MySQL, select \"by label\" in your repository connection.");
String idValue = JDBCConnection.readAsString(o);
o = row.getValue(JDBCConstants.versionReturnColumnName);
String versionValue;
// Null version is OK; make it a ""
if (o == null)
versionValue = "";
else
versionValue = JDBCConnection.readAsString(o);
documentVersions.put(idValue,versionValue);
}
finally
{
row.close();
}
}
}
finally
{
result.close();
}
}
}
else
{
for (String documentIdentifier : documentIdentifiers)
{
documentVersions.put(documentIdentifier,"");
}
}
// Delete the documents that had no version, and work only on ones that did
Set<String> fetchDocuments = documentVersions.keySet();
for (String documentIdentifier : documentIdentifiers)
{
String documentVersion = documentVersions.get(documentIdentifier);
if (documentVersion == null)
{
activities.deleteDocument(documentIdentifier);
continue;
}
}
// Pick up document acls
Map<String,Set<String>> documentAcls = new HashMap<String,Set<String>>();
if (ts.securityOn)
{
if (acls.size() == 0 && ts.aclQuery != null && ts.aclQuery.length() > 0)
{
// If there IS an acls query, do it. First set up the variables, then do the substitution.
VariableMap vm = new VariableMap();
addConstant(vm,JDBCConstants.idReturnVariable,JDBCConstants.idReturnColumnName);
addConstant(vm,JDBCConstants.tokenReturnVariable,JDBCConstants.tokenReturnColumnName);
if (addIDList(vm,JDBCConstants.idListVariable,documentIdentifiers,fetchDocuments))
{
// Do the substitution
ArrayList paramList = new ArrayList();
StringBuilder sb = new StringBuilder();
substituteQuery(ts.aclQuery,vm,sb,paramList);
// Fire off the query!
getSession();
IDynamicResultSet result;
String queryText = sb.toString();
long startTime = System.currentTimeMillis();
// Get a dynamic resultset. Contract for dynamic resultset is that if
// one is returned, it MUST be closed, or a connection will leak.
try
{
result = connection.executeUncachedQuery(queryText,paramList,-1);
}
catch (ManifoldCFException e)
{
// If failure, record the failure.
if (e.getErrorCode() != ManifoldCFException.INTERRUPTED)
activities.recordActivity(new Long(startTime), ACTIVITY_EXTERNAL_QUERY, null,
createQueryString(queryText,paramList), "ERROR", e.getMessage(), null);
throw e;
}
try
{
// If success, record that too.
activities.recordActivity(new Long(startTime), ACTIVITY_EXTERNAL_QUERY, null,
createQueryString(queryText,paramList), "OK", null, null);
// Now, go through resultset
while (true)
{
IDynamicResultRow row = result.getNextRow();
if (row == null)
break;
try
{
Object o = row.getValue(JDBCConstants.idReturnColumnName);
if (o == null)
throw new ManifoldCFException("Bad acl query; doesn't return $(IDCOLUMN) column. Try using quotes around $(IDCOLUMN) variable, e.g. \"$(IDCOLUMN)\", or, for MySQL, select \"by label\" in your repository connection.");
String idValue = JDBCConnection.readAsString(o);
o = row.getValue(JDBCConstants.tokenReturnColumnName);
String tokenValue;
if (o == null)
tokenValue = "";
else
tokenValue = JDBCConnection.readAsString(o);
// Versions that are "", when processed, will have their acls fetched at that time...
Set<String> dcs = documentAcls.get(idValue);
if (dcs == null)
{
dcs = new HashSet<String>();
documentAcls.put(idValue,dcs);
}
dcs.add(tokenValue);
}
finally
{
row.close();
}
}
}
finally
{
result.close();
}
}
}
else
{
for (String documentIdentifier : fetchDocuments)
{
documentAcls.put(documentIdentifier,acls);
}
}
}
Map<String,String> map = new HashMap<String,String>();
for (String documentIdentifier : fetchDocuments)
{
String documentVersion = documentVersions.get(documentIdentifier);
if (documentVersion.length() == 0)
{
map.put(documentIdentifier,documentVersion);
}
else
{
// Compute a full version string
StringBuilder sb = new StringBuilder();
Set<String> dAcls = documentAcls.get(documentIdentifier);
if (dAcls == null)
sb.append('-');
else
{
sb.append('+');
String[] aclValues = new String[dAcls.size()];
int k = 0;
for (String acl : dAcls)
{
aclValues[k++] = acl;
}
java.util.Arrays.sort(aclValues);
packList(sb,aclValues,'+');
}
sb.append(documentVersion).append("=").append(ts.dataQuery);
String versionValue = sb.toString();
if (activities.checkDocumentNeedsReindexing(documentIdentifier,versionValue))
{
map.put(documentIdentifier,versionValue);
}
}
}
// We have a primary query and a number of attribute queries to execute.
// We execute the attribute queries first because those do not include binary data.
final Map<String, Map<String, Set<String>>> attributeValues = new HashMap<String, Map<String, Set<String>>>();
int index = 0;
while (index < spec.getChildCount())
{
SpecificationNode sn = spec.getChild(index++);
if (sn.getType().equals(JDBCConstants.attributeQueryNode))
{
final String attributeName = sn.getAttributeValue(JDBCConstants.attributeName);
final String attributeQuery = sn.getValue();
// Fire off attribute query
VariableMap attrVm = new VariableMap();
addConstant(attrVm,JDBCConstants.idReturnVariable,JDBCConstants.idReturnColumnName);
addConstant(attrVm,JDBCConstants.dataReturnVariable,JDBCConstants.dataReturnColumnName);
if (!addIDList(attrVm,JDBCConstants.idListVariable,documentIdentifiers,map.keySet()))
continue;
// Do the substitution
ArrayList paramList = new ArrayList();
StringBuilder sb = new StringBuilder();
substituteQuery(attributeQuery,attrVm,sb,paramList);
// Fire off the query!
getSession();
IDynamicResultSet result;
String queryText = sb.toString();
long startTime = System.currentTimeMillis();
// Get a dynamic resultset. Contract for dynamic resultset is that if
// one is returned, it MUST be closed, or a connection will leak.
try
{
result = connection.executeUncachedQuery(queryText,paramList,-1);
}
catch (ManifoldCFException e)
{
// If failure, record the failure.
if (e.getErrorCode() != ManifoldCFException.INTERRUPTED)
activities.recordActivity(new Long(startTime), ACTIVITY_EXTERNAL_QUERY, null,
createQueryString(queryText,paramList), "ERROR", e.getMessage(), null);
throw e;
}
try
{
// If success, record that too.
activities.recordActivity(new Long(startTime), ACTIVITY_EXTERNAL_QUERY, null,
createQueryString(queryText,paramList), "OK", null, null);
// Now, go through resultset
while (true)
{
IDynamicResultRow row = result.getNextRow();
if (row == null)
break;
try
{
Object o = row.getValue(JDBCConstants.idReturnColumnName);
if (o == null)
throw new ManifoldCFException("Bad acl query; doesn't return $(IDCOLUMN) column. Try using quotes around $(IDCOLUMN) variable, e.g. \"$(IDCOLUMN)\", or, for MySQL, select \"by label\" in your repository connection.");
String idValue = JDBCConnection.readAsString(o);
o = row.getValue(JDBCConstants.dataReturnColumnName);
String dataValue;
if (o == null)
dataValue = "";
else
dataValue = JDBCConnection.readAsString(o);
// Versions that are "", when processed, will have their acls fetched at that time...
Map<String, Set<String>> avs = attributeValues.get(idValue);
if (avs == null)
{
avs = new HashMap<String, Set<String>>();
attributeValues.put(idValue,avs);
}
Set<String> dataValues = avs.get(attributeName);
if (dataValues == null)
{
dataValues = new HashSet<String>();
avs.put(attributeName, dataValues);
}
dataValues.add(dataValue);
}
finally
{
row.close();
}
}
}
finally
{
result.close();
}
}
}
// For all the documents not marked "scan only", form a query and pick up the contents.
// If the contents is not found, then explicitly call the delete action method.
VariableMap vm = new VariableMap();
addConstant(vm,JDBCConstants.idReturnVariable,JDBCConstants.idReturnColumnName);
addConstant(vm,JDBCConstants.urlReturnVariable,JDBCConstants.urlReturnColumnName);
addConstant(vm,JDBCConstants.dataReturnVariable,JDBCConstants.dataReturnColumnName);
addConstant(vm,JDBCConstants.contentTypeReturnVariable,JDBCConstants.contentTypeReturnColumnName);
if (!addIDList(vm,JDBCConstants.idListVariable,documentIdentifiers,map.keySet()))
return;
// Do the substitution
ArrayList paramList = new ArrayList();
StringBuilder sb = new StringBuilder();
substituteQuery(ts.dataQuery,vm,sb,paramList);
// Execute the query
getSession();
IDynamicResultSet result;
String queryText = sb.toString();
long startTime = System.currentTimeMillis();
// Get a dynamic resultset. Contract for dynamic resultset is that if
// one is returned, it MUST be closed, or a connection will leak.
try
{
result = connection.executeUncachedQuery(queryText,paramList,-1);
}
catch (ManifoldCFException e)
{
// If failure, record the failure.
activities.recordActivity(new Long(startTime), ACTIVITY_EXTERNAL_QUERY, null,
createQueryString(queryText,paramList), "ERROR", e.getMessage(), null);
throw e;
}
try
{
// If success, record that too.
activities.recordActivity(new Long(startTime), ACTIVITY_EXTERNAL_QUERY, null,
createQueryString(queryText,paramList), "OK", null, null);
while (true)
{
IDynamicResultRow row = result.getNextRow();
if (row == null)
break;
try
{
Object o = row.getValue(JDBCConstants.idReturnColumnName);
if (o == null)
throw new ManifoldCFException("Bad document query; doesn't return $(IDCOLUMN) column. Try using quotes around $(IDCOLUMN) variable, e.g. \"$(IDCOLUMN)\", or, for MySQL, select \"by label\" in your repository connection.");
String id = JDBCConnection.readAsString(o);
String errorCode = null;
String errorDesc = null;
Long fileLengthLong = null;
long fetchStartTime = System.currentTimeMillis();
try
{
String version = map.get(id);
if (version == null)
// Does not need refetching
continue;
// This document was marked as "not scan only", so we expect to find it.
if (Logging.connectors.isDebugEnabled())
Logging.connectors.debug("JDBC: Document data result found for '"+id+"'");
o = row.getValue(JDBCConstants.urlReturnColumnName);
if (o == null)
{
Logging.connectors.debug("JDBC: Document '"+id+"' has a null url - skipping");
errorCode = activities.NULL_URL;
errorDesc = "Excluded because document had a null URL";
activities.noDocument(id,version);
continue;
}
// This is not right - url can apparently be a BinaryInput
String url = JDBCConnection.readAsString(o);
boolean validURL;
try
{
// Check to be sure url is valid
new java.net.URI(url);
validURL = true;
}
catch (java.net.URISyntaxException e)
{
validURL = false;
}
if (!validURL)
{
Logging.connectors.debug("JDBC: Document '"+id+"' has an illegal url: '"+url+"' - skipping");
errorCode = activities.BAD_URL;
errorDesc = "Excluded because document had illegal URL ('"+url+"')";
activities.noDocument(id,version);
continue;
}
// Process the document itself
Object contents = row.getValue(JDBCConstants.dataReturnColumnName);
// Null data is allowed; we just ignore these
if (contents == null)
{
Logging.connectors.debug("JDBC: Document '"+id+"' seems to have null data - skipping");
errorCode = "NULLDATA";
errorDesc = "Excluded because document had null data";
activities.noDocument(id,version);
continue;
}
// We will ingest something, so remove this id from the map in order that we know what we still
// need to delete when all done.
map.remove(id);
String contentType;
o = row.getValue(JDBCConstants.contentTypeReturnColumnName);
if (o != null)
contentType = JDBCConnection.readAsString(o);
else
{
if (contents instanceof BinaryInput)
contentType = "application/octet-stream";
else if (contents instanceof CharacterInput)
contentType = "text/plain; charset=utf-8";
else
contentType = "text/plain";
}
if (!activities.checkMimeTypeIndexable(contentType))
{
Logging.connectors.debug("JDBC: Document '"+id+"' excluded because of mime type - skipping");
errorCode = activities.EXCLUDED_MIMETYPE;
errorDesc = "Excluded because of mime type ("+contentType+")";
activities.noDocument(id,version);
continue;
}
if (!activities.checkURLIndexable(url))
{
Logging.connectors.debug("JDBC: Document '"+id+"' excluded because of url - skipping");
errorCode = activities.EXCLUDED_URL;
errorDesc = "Excluded because of URL ('"+url+"')";
activities.noDocument(id,version);
continue;
}
// An ingestion will take place for this document.
RepositoryDocument rd = new RepositoryDocument();
rd.setMimeType(contentType);
applyMultiAttributeValues(rd,attributeValues.get(id));
applyAccessTokens(rd,documentAcls.get(id));
applyMetadata(rd,row);
if (contents instanceof BinaryInput)
{
BinaryInput bi = (BinaryInput)contents;
long fileLength = bi.getLength();
if (!activities.checkLengthIndexable(fileLength))
{
Logging.connectors.debug("JDBC: Document '"+id+"' excluded because of length - skipping");
errorCode = activities.EXCLUDED_LENGTH;
errorDesc = "Excluded because of length ("+fileLength+")";
activities.noDocument(id, version);
continue;
}
try
{
// Read the stream
InputStream is = bi.getStream();
try
{
rd.setBinary(is,fileLength);
activities.ingestDocumentWithException(id, version, url, rd);
errorCode = "OK";
fileLengthLong = new Long(fileLength);
}
finally
{
is.close();
}
}
catch (IOException e)
{
errorCode = e.getClass().getSimpleName().toUpperCase(Locale.ROOT);
errorDesc = e.getMessage();
handleIOException(id,e);
}
}
else if (contents instanceof CharacterInput)
{
CharacterInput ci = (CharacterInput)contents;
long fileLength = ci.getUtf8StreamLength();
if (!activities.checkLengthIndexable(fileLength))
{
Logging.connectors.debug("JDBC: Document '"+id+"' excluded because of length - skipping");
errorCode = activities.EXCLUDED_LENGTH;
errorDesc = "Excluded because of length ("+fileLength+")";
activities.noDocument(id, version);
continue;
}
try
{
// Read the stream
InputStream is = ci.getUtf8Stream();
try
{
rd.setBinary(is,fileLength);
activities.ingestDocumentWithException(id, version, url, rd);
errorCode = "OK";
fileLengthLong = new Long(fileLength);
}
finally
{
is.close();
}
}
catch (IOException e)
{
errorCode = e.getClass().getSimpleName().toUpperCase(Locale.ROOT);
errorDesc = e.getMessage();
handleIOException(id,e);
}
}
else
{
// Turn it into a string, and then into a stream
String value = contents.toString();
byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
long fileLength = bytes.length;
if (!activities.checkLengthIndexable(fileLength))
{
Logging.connectors.debug("JDBC: Document '"+id+"' excluded because of length - skipping");
errorCode = activities.EXCLUDED_LENGTH;
errorDesc = "Excluded because of length ("+fileLength+")";
activities.noDocument(id, version);
continue;
}
try
{
InputStream is = new ByteArrayInputStream(bytes);
try
{
rd.setBinary(is,fileLength);
activities.ingestDocumentWithException(id, version, url, rd);
errorCode = "OK";
fileLengthLong = new Long(fileLength);
}
finally
{
is.close();
}
}
catch (IOException e)
{
errorCode = e.getClass().getSimpleName().toUpperCase(Locale.ROOT);
errorDesc = e.getMessage();
handleIOException(id,e);
}
}
}
catch (ManifoldCFException e)
{
if (e.getErrorCode() == ManifoldCFException.INTERRUPTED)
errorCode = null;
throw e;
}
finally
{
if (errorCode != null)
activities.recordActivity(new Long(fetchStartTime), ACTIVITY_FETCH,
fileLengthLong, id, errorCode, errorDesc, null);
}
}
finally
{
row.close();
}
}
}
finally
{
result.close();
}
// Now, go through the original id's, and see which ones are still in the map. These
// did not appear in the result and are presumed to be gone from the database, and thus must be deleted.
for (String documentIdentifier : documentIdentifiers)
{
if (fetchDocuments.contains(documentIdentifier))
{
String documentVersion = map.get(documentIdentifier);
if (documentVersion != null)
{
// This means we did not see it (or data for it) in the result set. Delete it!
activities.noDocument(documentIdentifier,documentVersion);
activities.recordActivity(null, ACTIVITY_FETCH,
null, documentIdentifier, "NOTFETCHED", "Document was not seen by processing query", null);
}
}
}
}
protected static void handleIOException(String id, IOException e)
throws ManifoldCFException, ServiceInterruption
{
if (e instanceof java.net.SocketTimeoutException)
{
throw new ManifoldCFException("Socket timeout reading database data: "+e.getMessage(),e);
}
if (e instanceof InterruptedIOException)
{
throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED);
}
throw new ManifoldCFException("Error reading database data: "+e.getMessage(),e);
}
// UI support methods.
//
// These support methods come in two varieties. The first bunch is involved in setting up connection configuration information. The second bunch
// is involved in presenting and editing document specification information for a job. The two kinds of methods are accordingly treated differently,
// in that the first bunch cannot assume that the current connector object is connected, while the second bunch can. That is why the first bunch
// receives a thread context argument for all UI methods, while the second bunch does not need one (since it has already been applied via the connect()
// method, above).
/** Output the configuration header section.
* This method is called in the head section of the connector's configuration page. Its purpose is to add the required tabs to the list, and to output any
* javascript methods that might be needed by the configuration editing HTML.
*@param threadContext is the local thread context.
*@param out is the output to which any HTML should be sent.
*@param parameters are the configuration parameters, as they currently exist, for this connection being configured.
*@param tabsArray is an array of tab names. Add to this array any tab names that are specific to the connector.
*/
@Override
public void outputConfigurationHeader(IThreadContext threadContext, IHTTPOutput out,
Locale locale, ConfigParams parameters, List<String> tabsArray)
throws ManifoldCFException, IOException
{
tabsArray.add(Messages.getString(locale,"JDBCConnector.DatabaseType"));
tabsArray.add(Messages.getString(locale,"JDBCConnector.Server"));
tabsArray.add(Messages.getString(locale,"JDBCConnector.Credentials"));
out.print(
"<script type=\"text/javascript\">\n"+
"<!--\n"+
"function checkConfigForSave()\n"+
"{\n"+
" if (editconnection.databasehost.value == \"\" && editconnection.rawjdbcstring.value == \"\")\n"+
" {\n"+
" alert(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.PleaseFillInADatabaseServerName") + "\");\n"+
" SelectTab(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.Server") + "\");\n"+
" editconnection.databasehost.focus();\n"+
" return false;\n"+
" }\n"+
" if (editconnection.databasename.value == \"\" && editconnection.rawjdbcstring.value == \"\")\n"+
" {\n"+
" alert(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.PleaseFillInTheNameOfTheDatabase") + "\");\n"+
" SelectTab(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.Server") + "\");\n"+
" editconnection.databasename.focus();\n"+
" return false;\n"+
" }\n"+
" if (editconnection.username.value == \"\")\n"+
" {\n"+
" alert(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.PleaseSupplyTheDatabaseUsernameForThisConnection") + "\");\n"+
" SelectTab(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.Credentials") + "\");\n"+
" editconnection.username.focus();\n"+
" return false;\n"+
" }\n"+
" return true;\n"+
"}\n"+
"\n"+
"//-->\n"+
"</script>\n"
);
}
/** Output the configuration body section.
* This method is called in the body section of the connector's configuration page. Its purpose is to present the required form elements for editing.
* The coder can presume that the HTML that is output from this configuration will be within appropriate <html>, <body>, and <form> tags. The name of the
* form is "editconnection".
*@param threadContext is the local thread context.
*@param out is the output to which any HTML should be sent.
*@param parameters are the configuration parameters, as they currently exist, for this connection being configured.
*@param tabName is the current tab name.
*/
@Override
public void outputConfigurationBody(IThreadContext threadContext, IHTTPOutput out,
Locale locale, ConfigParams parameters, String tabName)
throws ManifoldCFException, IOException
{
String jdbcProvider = parameters.getParameter(JDBCConstants.providerParameter);
if (jdbcProvider == null)
jdbcProvider = "oracle:thin:@";
String accessMethod = parameters.getParameter(JDBCConstants.methodParameter);
if (accessMethod == null)
accessMethod = "name";
String host = parameters.getParameter(JDBCConstants.hostParameter);
if (host == null)
host = "localhost";
String databaseName = parameters.getParameter(JDBCConstants.databaseNameParameter);
if (databaseName == null)
databaseName = "database";
String rawJDBCString = parameters.getParameter(JDBCConstants.driverStringParameter);
if (rawJDBCString == null)
rawJDBCString = "";
String databaseUser = parameters.getParameter(JDBCConstants.databaseUserName);
if (databaseUser == null)
databaseUser = "";
String databasePassword = parameters.getObfuscatedParameter(JDBCConstants.databasePassword);
if (databasePassword == null)
databasePassword = "";
else
databasePassword = out.mapPasswordToKey(databasePassword);
// "Database Type" tab
if (tabName.equals(Messages.getString(locale,"JDBCConnector.DatabaseType")))
{
out.print(
"<table class=\"displaytable\">\n"+
" <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n"+
" <tr>\n"+
" <td class=\"description\"><nobr>" + Messages.getBodyString(locale,"JDBCConnector.DatabaseType2") + "</nobr></td><td class=\"value\">\n"+
" <select multiple=\"false\" name=\"databasetype\" size=\"2\">\n"+
" <option value=\"oracle:thin:@\" "+(jdbcProvider.equals("oracle:thin:@")?"selected=\"selected\"":"")+">Oracle</option>\n"+
" <option value=\"postgresql:\" "+(jdbcProvider.equals("postgresql:")?"selected=\"selected\"":"")+">Postgres SQL</option>\n"+
" <option value=\"jtds:sqlserver:\" "+(jdbcProvider.equals("jtds:sqlserver:")?"selected=\"selected\"":"")+">MS SQL Server (&gt; V6.5)</option>\n"+
" <option value=\"jtds:sybase:\" "+(jdbcProvider.equals("jtds:sybase:")?"selected=\"selected\"":"")+">Sybase (&gt;= V10)</option>\n"+
" <option value=\"mysql:\" "+(jdbcProvider.equals("mysql:")?"selected=\"selected\"":"")+">MySQL (&gt;= V5)</option>\n"+
" </select>\n"+
" </td>\n"+
" </tr>\n"+
" <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n"+
" <tr>\n"+
" <td class=\"description\"><nobr>" + Messages.getBodyString(locale,"JDBCConnector.AccessMethod") + "</nobr></td><td class=\"value\">\n"+
" <select multiple=\"false\" name=\"accessmethod\" size=\"2\">\n"+
" <option value=\"name\" "+(accessMethod.equals("name")?"selected=\"selected\"":"")+">"+Messages.getBodyString(locale,"JDBCConnector.ByName")+"</option>\n"+
" <option value=\"label\" "+(accessMethod.equals("label")?"selected=\"selected\"":"")+">"+Messages.getBodyString(locale,"JDBCConnector.ByLabel")+"</option>\n"+
" </select>\n"+
" </td>\n"+
" </tr>\n"+
"</table>\n"
);
}
else
{
out.print(
"<input type=\"hidden\" name=\"databasetype\" value=\""+jdbcProvider+"\"/>\n"+
"<input type=\"hidden\" name=\"accessmethod\" value=\""+accessMethod+"\"/>\n"
);
}
// "Server" tab
if (tabName.equals(Messages.getString(locale,"JDBCConnector.Server")))
{
out.print(
"<table class=\"displaytable\">\n"+
" <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n"+
" <tr>\n"+
" <td class=\"description\"><nobr>" + Messages.getBodyString(locale,"JDBCConnector.DatabaseHostAndPort") + "</nobr></td><td class=\"value\"><input type=\"text\" size=\"64\" name=\"databasehost\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(host)+"\"/></td>\n"+
" </tr>\n"+
" <tr>\n"+
" <td class=\"description\"><nobr>" + Messages.getBodyString(locale,"JDBCConnector.DatabaseServiceNameOrInstanceDatabase") + "</nobr></td><td class=\"value\"><input type=\"text\" size=\"32\" name=\"databasename\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(databaseName)+"\"/></td>\n"+
" </tr>\n"+
" <tr>\n"+
" <td class=\"description\"><nobr>" + Messages.getBodyString(locale,"JDBCConnector.RawDatabaseConnectString") + "</nobr></td><td class=\"value\"><input type=\"text\" size=\"80\" name=\"rawjdbcstring\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(rawJDBCString)+"\"/></td>\n"+
" </tr>\n"+
"</table>\n"
);
}
else
{
out.print(
"<input type=\"hidden\" name=\"databasehost\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(host)+"\"/>\n"+
"<input type=\"hidden\" name=\"databasename\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(databaseName)+"\"/>\n"+
"<input type=\"hidden\" name=\"rawjdbcstring\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(rawJDBCString)+"\"/>\n"
);
}
// "Credentials" tab
if (tabName.equals(Messages.getString(locale,"JDBCConnector.Credentials")))
{
out.print(
"<table class=\"displaytable\">\n"+
" <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n"+
" <tr>\n"+
" <td class=\"description\"><nobr>" + Messages.getBodyString(locale,"JDBCConnector.UserName") + "</nobr></td><td class=\"value\"><input type=\"text\" size=\"32\" name=\"username\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(databaseUser)+"\"/></td>\n"+
" </tr>\n"+
" <tr>\n"+
" <td class=\"description\"><nobr>" + Messages.getBodyString(locale,"JDBCConnector.Password") + "</nobr></td><td class=\"value\"><input type=\"password\" size=\"32\" name=\"password\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(databasePassword)+"\"/></td>\n"+
" </tr>\n"+
"</table>\n"
);
}
else
{
out.print(
"<input type=\"hidden\" name=\"username\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(databaseUser)+"\"/>\n"+
"<input type=\"hidden\" name=\"password\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(databasePassword)+"\"/>\n"
);
}
}
/** Process a configuration post.
* This method is called at the start of the connector's configuration page, whenever there is a possibility that form data for a connection has been
* posted. Its purpose is to gather form information and modify the configuration parameters accordingly.
* The name of the posted form is "editconnection".
*@param threadContext is the local thread context.
*@param variableContext is the set of variables available from the post, including binary file post information.
*@param parameters are the configuration parameters, as they currently exist, for this connection being configured.
*@return null if all is well, or a string error message if there is an error that should prevent saving of the connection (and cause a redirection to an error page).
*/
@Override
public String processConfigurationPost(IThreadContext threadContext, IPostParameters variableContext,
Locale locale, ConfigParams parameters)
throws ManifoldCFException
{
String type = variableContext.getParameter("databasetype");
if (type != null)
parameters.setParameter(JDBCConstants.providerParameter,type);
String accessMethod = variableContext.getParameter("accessmethod");
if (accessMethod != null)
parameters.setParameter(JDBCConstants.methodParameter,accessMethod);
String host = variableContext.getParameter("databasehost");
if (host != null)
parameters.setParameter(JDBCConstants.hostParameter,host);
String databaseName = variableContext.getParameter("databasename");
if (databaseName != null)
parameters.setParameter(JDBCConstants.databaseNameParameter,databaseName);
String rawJDBCString = variableContext.getParameter("rawjdbcstring");
if (rawJDBCString != null)
parameters.setParameter(JDBCConstants.driverStringParameter,rawJDBCString);
String userName = variableContext.getParameter("username");
if (userName != null)
parameters.setParameter(JDBCConstants.databaseUserName,userName);
String password = variableContext.getParameter("password");
if (password != null)
parameters.setObfuscatedParameter(JDBCConstants.databasePassword,variableContext.mapKeyToPassword(password));
return null;
}
/** View configuration.
* This method is called in the body section of the connector's view configuration page. Its purpose is to present the connection information to the user.
* The coder can presume that the HTML that is output from this configuration will be within appropriate <html> and <body> tags.
*@param threadContext is the local thread context.
*@param out is the output to which any HTML should be sent.
*@param parameters are the configuration parameters, as they currently exist, for this connection being configured.
*/
@Override
public void viewConfiguration(IThreadContext threadContext, IHTTPOutput out,
Locale locale, ConfigParams parameters)
throws ManifoldCFException, IOException
{
out.print(
"<table class=\"displaytable\">\n"+
" <tr>\n"+
" <td class=\"description\" colspan=\"1\"><nobr>" + Messages.getBodyString(locale,"JDBCConnector.Parameters") + "</nobr></td>\n"+
" <td class=\"value\" colspan=\"3\">\n"
);
Iterator iter = parameters.listParameters();
while (iter.hasNext())
{
String param = (String)iter.next();
String value = parameters.getParameter(param);
if (param.length() >= "password".length() && param.substring(param.length()-"password".length()).equalsIgnoreCase("password"))
{
out.print(
" <nobr>"+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(param)+"=********</nobr><br/>\n"
);
}
else if (param.length() >="keystore".length() && param.substring(param.length()-"keystore".length()).equalsIgnoreCase("keystore"))
{
IKeystoreManager kmanager = KeystoreManagerFactory.make("",value);
out.print(
" <nobr>"+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(param)+"=&lt;"+Integer.toString(kmanager.getContents().length)+" certificate(s)&gt;</nobr><br/>\n"
);
}
else
{
out.print(
" <nobr>"+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(param)+"="+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(value)+"</nobr><br/>\n"
);
}
}
out.print(
" </td>\n"+
" </tr>\n"+
"</table>\n"
);
}
/** Output the specification header section.
* This method is called in the head section of a job page which has selected a repository connection of the
* current type. Its purpose is to add the required tabs to the list, and to output any javascript methods
* that might be needed by the job editing HTML.
* The connector will be connected before this method can be called.
*@param out is the output to which any HTML should be sent.
*@param locale is the locale the output is preferred to be in.
*@param ds is the current document specification for this job.
*@param connectionSequenceNumber is the unique number of this connection within the job.
*@param tabsArray is an array of tab names. Add to this array any tab names that are specific to the connector.
*/
@Override
public void outputSpecificationHeader(IHTTPOutput out, Locale locale, Specification ds,
int connectionSequenceNumber, List<String> tabsArray)
throws ManifoldCFException, IOException
{
tabsArray.add(Messages.getString(locale,"JDBCConnector.Queries"));
tabsArray.add(Messages.getString(locale,"JDBCConnector.Security"));
String seqPrefix = "s"+connectionSequenceNumber+"_";
out.print(
"<script type=\"text/javascript\">\n"+
"<!--\n"+
"\n"+
"function "+seqPrefix+"SpecOp(n, opValue, anchorvalue)\n"+
"{\n"+
" eval(\"editjob.\"+n+\".value = \\\"\"+opValue+\"\\\"\");\n"+
" postFormSetAnchor(anchorvalue);\n"+
"}\n"+
"\n"+
"function "+seqPrefix+"DeleteAttr(index)\n"+
"{\n"+
" "+seqPrefix+"SpecOp(\""+seqPrefix+"attr_\"+index+\"_op\", \"Delete\", \""+seqPrefix+"attr_\" + index);\n"+
"}\n"+
"\n"+
"function "+seqPrefix+"AddAttr(index)\n"+
"{\n"+
" if (editjob."+seqPrefix+"attr_name.value == \"\")\n"+
" {\n"+
" alert(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.TypeInAnAttributeName") + "\");\n"+
" editjob."+seqPrefix+"attr_name.focus();\n"+
" return;\n"+
" }\n"+
" if (editjob."+seqPrefix+"attr_query.value == \"\")\n"+
" {\n"+
" alert(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.AttributeQueryCannotBeNull") + "\");\n"+
" editjob."+seqPrefix+"attr_query.focus();\n"+
" return;\n"+
" }\n"+
" if (editjob."+seqPrefix+"attr_query.value.indexOf(\"$(IDCOLUMN)\") == -1)\n"+
" {\n"+
" alert(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.MustReturnIDCOLUMNInTheResult") + "\");\n"+
" editjob."+seqPrefix+"attr_query.focus();\n"+
" return;\n"+
" }\n"+
" if (editjob."+seqPrefix+"attr_query.value.indexOf(\"$(DATACOLUMN)\") == -1)\n"+
" {\n"+
" alert(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.MustReturnDATACOLUMNInTheResult") + "\");\n"+
" editjob."+seqPrefix+"attr_query.focus();\n"+
" return;\n"+
" }\n"+
" if (editjob."+seqPrefix+"attr_query.value.indexOf(\"$(IDLIST)\") == -1)\n"+
" {\n"+
" alert(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.MustUseIDLISTInWHEREClause") + "\");\n"+
" editjob."+seqPrefix+"attr_query.focus();\n"+
" return;\n"+
" }\n"+
" "+seqPrefix+"SpecOp(\""+seqPrefix+"attr_op\", \"Add\", \""+seqPrefix+"attr_\"+(index+1));\n"+
"}\n"+
"\n"+
"function "+seqPrefix+"SpecAddToken(anchorvalue)\n"+
"{\n"+
" if (editjob."+seqPrefix+"spectoken.value == \"\")\n"+
" {\n"+
" alert(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.TypeInAnAccessToken") + "\");\n"+
" editjob."+seqPrefix+"spectoken.focus();\n"+
" return;\n"+
" }\n"+
" "+seqPrefix+"SpecOp(\""+seqPrefix+"accessop\",\"Add\",anchorvalue);\n"+
"}\n"+
"\n"+
"function "+seqPrefix+"checkSpecification()\n"+
"{\n"+
" if (editjob."+seqPrefix+"idquery.value == \"\")\n"+
" {\n"+
" alert(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.EnterASeedingQuery") + "\");\n"+
" editjob."+seqPrefix+"idquery.focus();\n"+
" return false;\n"+
" }\n"+
" if (editjob."+seqPrefix+"idquery.value.indexOf(\"$(IDCOLUMN)\") == -1)\n"+
" {\n"+
" alert(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.MustReturnIDCOLUMNInTheResult") + "\");\n"+
" editjob."+seqPrefix+"idquery.focus();\n"+
" return false;\n"+
" }\n"+
" if (editjob."+seqPrefix+"versionquery.value != \"\")\n"+
" {\n"+
" if (editjob."+seqPrefix+"versionquery.value.indexOf(\"$(IDCOLUMN)\") == -1)\n"+
" {\n"+
" alert(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.MustReturnIDCOLUMNInTheResult") + "\");\n"+
" editjob."+seqPrefix+"versionquery.focus();\n"+
" return false;\n"+
" }\n"+
" if (editjob."+seqPrefix+"versionquery.value.indexOf(\"$(VERSIONCOLUMN)\") == -1)\n"+
" {\n"+
" alert(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.MustReturnVERSIONCOLUMNInTheResult") + "\");\n"+
" editjob."+seqPrefix+"versionquery.focus();\n"+
" return false;\n"+
" }\n"+
" if (editjob."+seqPrefix+"versionquery.value.indexOf(\"$(IDLIST)\") == -1)\n"+
" {\n"+
" alert(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.MustUseIDLISTInWHEREClause") + "\");\n"+
" editjob."+seqPrefix+"versionquery.focus();\n"+
" return false;\n"+
" }\n"+
" }\n"+
" if (editjob."+seqPrefix+"aclquery.value != \"\")\n"+
" {\n"+
" if (editjob."+seqPrefix+"aclquery.value.indexOf(\"$(IDCOLUMN)\") == -1)\n"+
" {\n"+
" alert(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.MustReturnIDCOLUMNInTheResult") + "\");\n"+
" editjob."+seqPrefix+"aclquery.focus();\n"+
" return false;\n"+
" }\n"+
" if (editjob."+seqPrefix+"aclquery.value.indexOf(\"$(TOKENCOLUMN)\") == -1)\n"+
" {\n"+
" alert(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.MustReturnTOKENCOLUMNInTheResult") + "\");\n"+
" editjob."+seqPrefix+"aclquery.focus();\n"+
" return false;\n"+
" }\n"+
" if (editjob."+seqPrefix+"aclquery.value.indexOf(\"$(IDLIST)\") == -1)\n"+
" {\n"+
" alert(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.MustUseIDLISTInWHEREClause") + "\");\n"+
" editjob."+seqPrefix+"aclquery.focus();\n"+
" return false;\n"+
" }\n"+
" }\n"+
" if (editjob."+seqPrefix+"dataquery.value == \"\")\n"+
" {\n"+
" alert(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.EnterADataQuery") + "\");\n"+
" editjob."+seqPrefix+"dataquery.focus();\n"+
" return false;\n"+
" }\n"+
" if (editjob."+seqPrefix+"dataquery.value.indexOf(\"$(IDCOLUMN)\") == -1)\n"+
" {\n"+
" alert(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.MustReturnIDCOLUMNInTheResult2") + "\");\n"+
" editjob."+seqPrefix+"dataquery.focus();\n"+
" return false;\n"+
" }\n"+
" if (editjob."+seqPrefix+"dataquery.value.indexOf(\"$(URLCOLUMN)\") == -1)\n"+
" {\n"+
" alert(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.MustReturnURLCOLUMNInTheResult") + "\");\n"+
" editjob."+seqPrefix+"dataquery.focus();\n"+
" return false;\n"+
" }\n"+
" if (editjob."+seqPrefix+"dataquery.value.indexOf(\"$(DATACOLUMN)\") == -1)\n"+
" {\n"+
" alert(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.MustReturnDATACOLUMNInTheResult") + "\");\n"+
" editjob."+seqPrefix+"dataquery.focus();\n"+
" return false;\n"+
" }\n"+
" if (editjob."+seqPrefix+"dataquery.value.indexOf(\"$(IDLIST)\") == -1)\n"+
" {\n"+
" alert(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.MustUseIDLISTInWHEREClause") + "\");\n"+
" editjob."+seqPrefix+"dataquery.focus();\n"+
" return false;\n"+
" }\n"+
"\n"+
" return true;\n"+
"}\n"+
"\n"+
"//-->\n"+
"</script>\n"
);
}
/** Output the specification body section.
* This method is called in the body section of a job page which has selected a repository connection of the
* current type. Its purpose is to present the required form elements for editing.
* The coder can presume that the HTML that is output from this configuration will be within appropriate
* <html>, <body>, and <form> tags. The name of the form is always "editjob".
* The connector will be connected before this method can be called.
*@param out is the output to which any HTML should be sent.
*@param locale is the locale the output is preferred to be in.
*@param ds is the current document specification for this job.
*@param connectionSequenceNumber is the unique number of this connection within the job.
*@param actualSequenceNumber is the connection within the job that has currently been selected.
*@param tabName is the current tab name. (actualSequenceNumber, tabName) form a unique tuple within
* the job.
*/
@Override
public void outputSpecificationBody(IHTTPOutput out, Locale locale, Specification ds,
int connectionSequenceNumber, int actualSequenceNumber, String tabName)
throws ManifoldCFException, IOException
{
String seqPrefix = "s"+connectionSequenceNumber+"_";
String idQuery = "SELECT idfield AS $(IDCOLUMN) FROM documenttable WHERE modifydatefield > $(STARTTIME) AND modifydatefield <= $(ENDTIME)";
String versionQuery = "SELECT idfield AS $(IDCOLUMN), versionfield AS $(VERSIONCOLUMN) FROM documenttable WHERE idfield IN $(IDLIST)";
String dataQuery = "SELECT idfield AS $(IDCOLUMN), urlfield AS $(URLCOLUMN), datafield AS $(DATACOLUMN) FROM documenttable WHERE idfield IN $(IDLIST)";
String aclQuery = "SELECT docidfield AS $(IDCOLUMN), aclfield AS $(TOKENCOLUMN) FROM acltable WHERE docidfield IN $(IDLIST)";
final Map<String, String> attributeQueryMap = new HashMap<String, String>();
int i = 0;
while (i < ds.getChildCount())
{
SpecificationNode sn = ds.getChild(i++);
if (sn.getType().equals(JDBCConstants.idQueryNode))
{
idQuery = sn.getValue();
if (idQuery == null)
idQuery = "";
}
else if (sn.getType().equals(JDBCConstants.versionQueryNode))
{
versionQuery = sn.getValue();
if (versionQuery == null)
versionQuery = "";
}
else if (sn.getType().equals(JDBCConstants.dataQueryNode))
{
dataQuery = sn.getValue();
if (dataQuery == null)
dataQuery = "";
}
else if (sn.getType().equals(JDBCConstants.aclQueryNode))
{
aclQuery = sn.getValue();
if (aclQuery == null)
aclQuery = "";
}
else if (sn.getType().equals(JDBCConstants.attributeQueryNode))
{
String attributeName = sn.getAttributeValue(JDBCConstants.attributeName);
String attributeQuery = sn.getValue();
attributeQueryMap.put(attributeName, attributeQuery);
}
}
// Sort the attribute query list
final String[] attributeNames = attributeQueryMap.keySet().toArray(new String[0]);
java.util.Arrays.sort(attributeNames);
// The Queries tab
if (tabName.equals(Messages.getString(locale,"JDBCConnector.Queries")) && connectionSequenceNumber == actualSequenceNumber)
{
out.print(
"<table class=\"displaytable\">\n"+
" <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n"+
" <tr>\n"+
" <td class=\"description\"><nobr>" + Messages.getBodyString(locale,"JDBCConnector.SeedingQuery") + "</nobr><br/><nobr>" + Messages.getBodyString(locale,"JDBCConnector.returnIdsThatNeedToBeChecked") + "</nobr></td>\n"+
" <td class=\"value\"><textarea name=\""+seqPrefix+"idquery\" cols=\"64\" rows=\"6\">"+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(idQuery)+"</textarea></td>\n"+
" </tr>\n"+
" <tr>\n"+
" <td class=\"description\"><nobr>" + Messages.getBodyString(locale,"JDBCConnector.VersionCheckQuery") + "</nobr><br/><nobr>" + Messages.getBodyString(locale,"JDBCConnector.returnIdsAndVersionsForASetOfDocuments") + "</nobr><br/><nobr>" + Messages.getBodyString(locale,"JDBCConnector.leaveBlankIfNoVersioningCapability") + "</nobr></td>\n"+
" <td class=\"value\"><textarea name=\""+seqPrefix+"versionquery\" cols=\"64\" rows=\"6\">"+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(versionQuery)+"</textarea></td>\n"+
" </tr>\n"+
" <tr>\n"+
" <td class=\"description\"><nobr>" + Messages.getBodyString(locale,"JDBCConnector.AccessTokenQuery") + "</nobr><br/><nobr>" + Messages.getBodyString(locale,"JDBCConnector.returnIdsAndAccessTokensForASetOfDocuments") + "</nobr><br/><nobr>" + Messages.getBodyString(locale,"JDBCConnector.leaveBlankIfNoSecurityCapability") + "</nobr></td>\n"+
" <td class=\"value\"><textarea name=\""+seqPrefix+"aclquery\" cols=\"64\" rows=\"6\">"+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(aclQuery)+"</textarea></td>\n"+
" </tr>\n"+
" <tr>\n"+
" <td class=\"description\"><nobr>" + Messages.getBodyString(locale,"JDBCConnector.DataQuery") + "</nobr><br/><nobr>" + Messages.getBodyString(locale,"JDBCConnector.returnIdsUrlsAndDataForASetOfDocuments") + "</nobr></td>\n"+
" <td class=\"value\"><textarea name=\""+seqPrefix+"dataquery\" cols=\"64\" rows=\"6\">"+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(dataQuery)+"</textarea></td>\n"+
" </tr>\n");
out.print(
" <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n"+
" <tr>"+
" <td class=\"description\"><nobr>" + Messages.getBodyString(locale,"JDBCConnector.AttributeQueries") + "</nobr></td>\n"+
" <td class=\"boxcell\">\n"+
" <table class=\"formtable\">\n"+
" <tr class=\"formheaderrow\">\n"+
" <td class=\"formcolumnheader\"></td>\n"+
" <td class=\"formcolumnheader\"><nobr>" + Messages.getBodyString(locale,"JDBCConnector.AttributeName") + "</nobr></td>\n"+
" <td class=\"formcolumnheader\"><nobr>" + Messages.getBodyString(locale,"JDBCConnector.AttributeQuery") + "</nobr></td>\n"+
" </tr>\n"
);
int attributeIndex = 0;
for (final String attributeName : attributeNames) {
final String attributeQuery = attributeQueryMap.get(attributeName);
if (attributeIndex % 2 == 0)
{
out.print(
" <tr class=\"evenformrow\">\n"
);
}
else
{
out.print(
" <tr class=\"oddformrow\">\n"
);
}
// Delete button
out.print(
" <td class=\"formcolumncell\">\n"+
" <a name=\""+seqPrefix+"attr_"+attributeIndex+"\">\n"+
" <nobr>\n"+
" <input type=\"button\" value=\""+Messages.getAttributeString(locale,"JDBCConnector.Delete")+"\"\n"+
" alt=\""+Messages.getAttributeString(locale,"JDBCConnector.DeleteAttributeQueryNumber")+attributeIndex+"\" onclick=\"javascript:"+seqPrefix+"DeleteAttr("+attributeIndex+");\"/>\n"+
" </nobr>\n"+
" </a>\n"+
" <input type=\"hidden\" name=\""+seqPrefix+"attr_"+attributeIndex+"_op"+"\" value=\"Continue\"/>\n"+
" <input type=\"hidden\" name=\""+seqPrefix+"attr_"+attributeIndex+"_name\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(attributeName)+"\"/>\n"+
" </td>\n"
);
// Attribute name
out.print(
" <td class=\"formcolumncell\">\n"+
" "+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(attributeName)+"\n"+
" </td>\n"
);
// Query
out.print(
" <td class=\"formcolumncell\">\n"+
" <textarea name=\""+seqPrefix+"attr_"+attributeIndex+"_query\" cols=\"64\" rows=\"6\">"+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(attributeQuery)+"</textarea>\n"+
" </td>\n"
);
out.print(
" </tr>\n"
);
attributeIndex++;
}
if (attributeIndex == 0)
{
out.print(
" <tr><td class=\"formmessage\" colspan=\"3\">"+Messages.getBodyString(locale,"JDBCConnector.NoAttributeQueries")+"</td></tr>\n"
);
}
// Add button
out.print(
" <tr><td class=\"formseparator\" colspan=\"3\"><hr/></td></tr>\n"+
" <tr class=\"formrow\">\n"+
" <td class=\"formcolumncell\">\n"+
" <a name=\""+seqPrefix+"attr_"+attributeIndex+"\">\n"+
" <input type=\"button\" value=\""+Messages.getAttributeString(locale,"JDBCConnector.Add")+"\"\n"+
" alt=\""+Messages.getAttributeString(locale,"JDBCConnector.AddAttribute")+"\" onclick=\"javascript:"+seqPrefix+"AddAttr("+attributeIndex+");\"/>\n"+
" </a>\n"+
" <input type=\"hidden\" name=\""+seqPrefix+"attr_count\" value=\""+attributeIndex+"\"/>\n"+
" <input type=\"hidden\" name=\""+seqPrefix+"attr_op\" value=\"Continue\"/>\n"+
" </td>\n"+
" <td class=\"formcolumncell\"><nobr><input name=\""+seqPrefix+"attr_name\" type=\"text\" size=\"16\" value=\"\"/></nobr></td>\n"+
" <td class=\"formcolumncell\">\n"+
" <textarea name=\""+seqPrefix+"attr_query\" cols=\"64\" rows=\"6\">SELECT idfield AS $(IDCOLUMN), datafield AS $(DATACOLUMN) FROM attributetable WHERE idfield IN $(IDLIST)</textarea>\n"+
" </td>\n"+
" </tr>\n"
);
out.print(
" </table>\n"+
" </td>\n"+
" </tr>\n"+
"</table>\n"
);
}
else
{
out.print(
"<input type=\"hidden\" name=\""+seqPrefix+"idquery\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(idQuery)+"\"/>\n"+
"<input type=\"hidden\" name=\""+seqPrefix+"versionquery\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(versionQuery)+"\"/>\n"+
"<input type=\"hidden\" name=\""+seqPrefix+"aclquery\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(aclQuery)+"\"/>\n"+
"<input type=\"hidden\" name=\""+seqPrefix+"dataquery\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(dataQuery)+"\"/>\n"
);
int attributeIndex = 0;
for (final String attributeName : attributeNames) {
final String attributeQuery = attributeQueryMap.get(attributeName);
out.print(
"<input type=\"hidden\" name=\""+seqPrefix+"attr_"+attributeIndex+"_name\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(attributeName)+"\"/>\n"+
"<input type=\"hidden\" name=\""+seqPrefix+"attr_"+attributeIndex+"_query\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(attributeQuery)+"\"/>\n"
);
attributeIndex++;
}
out.print(
"<input type=\"hidden\" name=\""+seqPrefix+"attr_count\" value=\""+attributeIndex+"\"/>\n"
);
}
// Security tab
// There is no native security, so all we care about are the tokens.
i = 0;
boolean securityOn = true;
while (i < ds.getChildCount())
{
SpecificationNode sn = ds.getChild(i++);
if (sn.getType().equals("security"))
{
String securityValue = sn.getAttributeValue("value");
if (securityValue.equals("off"))
securityOn = false;
else if (securityValue.equals("on"))
securityOn = true;
}
}
if (tabName.equals(Messages.getString(locale,"JDBCConnector.Security")) && connectionSequenceNumber == actualSequenceNumber)
{
out.print(
"<table class=\"displaytable\">\n"+
" <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n"+
" <tr>\n"+
" <td class=\"description\"><nobr>"+Messages.getBodyString(locale,"JDBCConnector.SecurityColon")+"</nobr></td>\n"+
" <td class=\"value\">\n"+
" <input type=\"radio\" name=\""+seqPrefix+"specsecurity\" value=\"on\" "+(securityOn?"checked=\"true\"":"")+" />"+Messages.getBodyString(locale,"JDBCConnector.Enabled")+"\n"+
" <input type=\"radio\" name=\""+seqPrefix+"specsecurity\" value=\"off\" "+((securityOn==false)?"checked=\"true\"":"")+" />"+Messages.getBodyString(locale,"JDBCConnector.Disabled")+"\n"+
" </td>\n"+
" </tr>\n"+
" <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n"
);
// Go through forced ACL
i = 0;
int k = 0;
while (i < ds.getChildCount())
{
SpecificationNode sn = ds.getChild(i++);
if (sn.getType().equals("access"))
{
String accessDescription = "_"+Integer.toString(k);
String accessOpName = seqPrefix+"accessop"+accessDescription;
String token = sn.getAttributeValue("token");
out.print(
" <tr>\n"+
" <td class=\"description\">\n"+
" <input type=\"hidden\" name=\""+accessOpName+"\" value=\"\"/>\n"+
" <input type=\"hidden\" name=\""+seqPrefix+"spectoken"+accessDescription+"\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(token)+"\"/>\n"+
" <a name=\""+seqPrefix+"token_"+Integer.toString(k)+"\">\n"+
" <input type=\"button\" value=\"Delete\" onClick='Javascript:"+seqPrefix+"SpecOp(\""+accessOpName+"\",\"Delete\",\""+seqPrefix+"token_"+Integer.toString(k)+"\")' alt=\"" + Messages.getAttributeString(locale,"JDBCConnector.DeleteToken") + "\""+Integer.toString(k)+"\"/>\n"+
" </a>&nbsp;\n"+
" </td>\n"+
" <td class=\"value\">\n"+
" "+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(token)+"\n"+
" </td>\n"+
" </tr>\n"
);
k++;
}
}
if (k == 0)
{
out.print(
" <tr>\n"+
" <td class=\"message\" colspan=\"2\">" + Messages.getBodyString(locale,"JDBCConnector.NoAccessTokensPresent") + "</td>\n"+
" </tr>\n"
);
}
out.print(
" <tr><td class=\"lightseparator\" colspan=\"2\"><hr/></td></tr>\n"+
" <tr>\n"+
" <td class=\"description\">\n"+
" <input type=\"hidden\" name=\""+seqPrefix+"tokencount\" value=\""+Integer.toString(k)+"\"/>\n"+
" <input type=\"hidden\" name=\""+seqPrefix+"accessop\" value=\"\"/>\n"+
" <a name=\""+seqPrefix+"token_"+Integer.toString(k)+"\">\n"+
" <input type=\"button\" value=\"Add\" onClick='Javascript:"+seqPrefix+"SpecAddToken(\""+seqPrefix+"token_"+Integer.toString(k+1)+"\")' alt=\"" + Messages.getAttributeString(locale,"JDBCConnector.AddAccessToken") + "\"/>\n"+
" </a>&nbsp;\n"+
" </td>\n"+
" <td class=\"value\">\n"+
" <input type=\"text\" size=\"30\" name=\""+seqPrefix+"spectoken\" value=\"\"/>\n"+
" </td>\n"+
" </tr>\n"+
"</table>\n"
);
}
else
{
out.print(
"<input type=\"hidden\" name=\""+seqPrefix+"specsecurity\" value=\""+(securityOn?"on":"off")+"\"/>\n"
);
// Finally, go through forced ACL
i = 0;
int k = 0;
while (i < ds.getChildCount())
{
SpecificationNode sn = ds.getChild(i++);
if (sn.getType().equals("access"))
{
String accessDescription = "_"+Integer.toString(k);
String token = sn.getAttributeValue("token");
out.print(
"<input type=\"hidden\" name=\""+seqPrefix+"spectoken"+accessDescription+"\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(token)+"\"/>\n"
);
k++;
}
}
out.print(
"<input type=\"hidden\" name=\""+seqPrefix+"tokencount\" value=\""+Integer.toString(k)+"\"/>\n"
);
}
}
/** Process a specification post.
* This method is called at the start of job's edit or view page, whenever there is a possibility that form
* data for a connection has been posted. Its purpose is to gather form information and modify the
* document specification accordingly. The name of the posted form is always "editjob".
* The connector will be connected before this method can be called.
*@param variableContext contains the post data, including binary file-upload information.
*@param locale is the locale the output is preferred to be in.
*@param ds is the current document specification for this job.
*@param connectionSequenceNumber is the unique number of this connection within the job.
*@return null if all is well, or a string error message if there is an error that should prevent saving of
* the job (and cause a redirection to an error page).
*/
@Override
public String processSpecificationPost(IPostParameters variableContext, Locale locale, Specification ds,
int connectionSequenceNumber)
throws ManifoldCFException
{
String seqPrefix = "s"+connectionSequenceNumber+"_";
String idQuery = variableContext.getParameter(seqPrefix+"idquery");
String versionQuery = variableContext.getParameter(seqPrefix+"versionquery");
String dataQuery = variableContext.getParameter(seqPrefix+"dataquery");
String aclQuery = variableContext.getParameter(seqPrefix+"aclquery");
SpecificationNode sn;
if (idQuery != null)
{
int i = 0;
while (i < ds.getChildCount())
{
if (ds.getChild(i).getType().equals(JDBCConstants.idQueryNode))
ds.removeChild(i);
else
i++;
}
sn = new SpecificationNode(JDBCConstants.idQueryNode);
sn.setValue(idQuery);
ds.addChild(ds.getChildCount(),sn);
}
if (versionQuery != null)
{
int i = 0;
while (i < ds.getChildCount())
{
if (ds.getChild(i).getType().equals(JDBCConstants.versionQueryNode))
ds.removeChild(i);
else
i++;
}
sn = new SpecificationNode(JDBCConstants.versionQueryNode);
sn.setValue(versionQuery);
ds.addChild(ds.getChildCount(),sn);
}
if (aclQuery != null)
{
int i = 0;
while (i < ds.getChildCount())
{
if (ds.getChild(i).getType().equals(JDBCConstants.aclQueryNode))
ds.removeChild(i);
else
i++;
}
sn = new SpecificationNode(JDBCConstants.aclQueryNode);
sn.setValue(aclQuery);
ds.addChild(ds.getChildCount(),sn);
}
if (dataQuery != null)
{
int i = 0;
while (i < ds.getChildCount())
{
if (ds.getChild(i).getType().equals(JDBCConstants.dataQueryNode))
ds.removeChild(i);
else
i++;
}
sn = new SpecificationNode(JDBCConstants.dataQueryNode);
sn.setValue(dataQuery);
ds.addChild(ds.getChildCount(),sn);
}
String xc;
xc = variableContext.getParameter(seqPrefix+"attr_count");
if (xc != null)
{
// Delete all attribute queries first
int i = 0;
while (i < ds.getChildCount())
{
sn = ds.getChild(i);
if (sn.getType().equals(JDBCConstants.attributeQueryNode))
ds.removeChild(i);
else
i++;
}
// Now, maybe do add
final String newAttributeName;
final String newAttributeOp = variableContext.getParameter(seqPrefix+"attr_op");
if (newAttributeOp != null && newAttributeOp.equals("Add"))
{
final String attributeName = variableContext.getParameter(seqPrefix+"attr_name");
final String attributeQuery = variableContext.getParameter(seqPrefix+"attr_query");
SpecificationNode node = new SpecificationNode(JDBCConstants.attributeQueryNode);
node.setAttribute(JDBCConstants.attributeName, attributeName);
newAttributeName = attributeName;
node.setValue(attributeQuery);
ds.addChild(ds.getChildCount(),node);
}
else
{
newAttributeName = null;
}
int attributeCount = Integer.parseInt(xc);
for (int attributeIndex = 0; attributeIndex < attributeCount; attributeIndex++)
{
final String attributeOp = variableContext.getParameter(seqPrefix+"attr_"+attributeIndex+"_op");
if (!(attributeOp != null && attributeOp.equals("Delete")))
{
// Include this!!
final String attributeName = variableContext.getParameter(seqPrefix+"attr_"+attributeIndex+"_name");
if (newAttributeName == null || !attributeName.equals(newAttributeName)) {
final String attributeQuery = variableContext.getParameter(seqPrefix+"attr_"+attributeIndex+"_query");
SpecificationNode node = new SpecificationNode(JDBCConstants.attributeQueryNode);
node.setAttribute(JDBCConstants.attributeName, attributeName);
node.setValue(attributeQuery);
ds.addChild(ds.getChildCount(),node);
}
}
}
}
xc = variableContext.getParameter(seqPrefix+"specsecurity");
if (xc != null)
{
// Delete all security entries first
int i = 0;
while (i < ds.getChildCount())
{
sn = ds.getChild(i);
if (sn.getType().equals("security"))
ds.removeChild(i);
else
i++;
}
SpecificationNode node = new SpecificationNode("security");
node.setAttribute("value",xc);
ds.addChild(ds.getChildCount(),node);
}
xc = variableContext.getParameter(seqPrefix+"tokencount");
if (xc != null)
{
// Delete all tokens first
int i = 0;
while (i < ds.getChildCount())
{
sn = ds.getChild(i);
if (sn.getType().equals("access"))
ds.removeChild(i);
else
i++;
}
int accessCount = Integer.parseInt(xc);
i = 0;
while (i < accessCount)
{
String accessDescription = "_"+Integer.toString(i);
String accessOpName = seqPrefix+"accessop"+accessDescription;
xc = variableContext.getParameter(accessOpName);
if (xc != null && xc.equals("Delete"))
{
// Next row
i++;
continue;
}
// Get the stuff we need
String accessSpec = variableContext.getParameter(seqPrefix+"spectoken"+accessDescription);
SpecificationNode node = new SpecificationNode("access");
node.setAttribute("token",accessSpec);
ds.addChild(ds.getChildCount(),node);
i++;
}
String op = variableContext.getParameter(seqPrefix+"accessop");
if (op != null && op.equals("Add"))
{
String accessspec = variableContext.getParameter(seqPrefix+"spectoken");
SpecificationNode node = new SpecificationNode("access");
node.setAttribute("token",accessspec);
ds.addChild(ds.getChildCount(),node);
}
}
return null;
}
/** View specification.
* This method is called in the body section of a job's view page. Its purpose is to present the document
* specification information to the user. The coder can presume that the HTML that is output from
* this configuration will be within appropriate <html> and <body> tags.
* The connector will be connected before this method can be called.
*@param out is the output to which any HTML should be sent.
*@param locale is the locale the output is preferred to be in.
*@param ds is the current document specification for this job.
*@param connectionSequenceNumber is the unique number of this connection within the job.
*/
@Override
public void viewSpecification(IHTTPOutput out, Locale locale, Specification ds,
int connectionSequenceNumber)
throws ManifoldCFException, IOException
{
String idQuery = "";
String versionQuery = "";
String dataQuery = "";
String aclQuery = "";
final Map<String, String> attributeQueryMap = new HashMap<String, String>();
int i = 0;
while (i < ds.getChildCount())
{
SpecificationNode sn = ds.getChild(i++);
if (sn.getType().equals(JDBCConstants.idQueryNode))
{
idQuery = sn.getValue();
if (idQuery == null)
idQuery = "";
}
else if (sn.getType().equals(JDBCConstants.versionQueryNode))
{
versionQuery = sn.getValue();
if (versionQuery == null)
versionQuery = "";
}
else if (sn.getType().equals(JDBCConstants.dataQueryNode))
{
dataQuery = sn.getValue();
if (dataQuery == null)
dataQuery = "";
}
else if (sn.getType().equals(JDBCConstants.aclQueryNode))
{
aclQuery = sn.getValue();
if (aclQuery == null)
aclQuery = "";
}
else if (sn.getType().equals(JDBCConstants.attributeQueryNode))
{
String attributeName = sn.getAttributeValue(JDBCConstants.attributeName);
String attributeQuery = sn.getValue();
attributeQueryMap.put(attributeName, attributeQuery);
}
}
// Sort the attribute query list
final String[] attributeNames = attributeQueryMap.keySet().toArray(new String[0]);
java.util.Arrays.sort(attributeNames);
out.print(
"<table class=\"displaytable\">\n"+
" <tr>\n"+
" <td class=\"description\"><nobr>" + Messages.getBodyString(locale,"JDBCConnector.SeedingQuery") + "</nobr></td>\n"+
" <td class=\"value\">"+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(idQuery)+"</td>\n"+
" </tr>\n"+
" <tr>\n"+
" <td class=\"description\"><nobr>" + Messages.getBodyString(locale,"JDBCConnector.VersionCheckQuery") + "</nobr></td>\n"+
" <td class=\"value\">"+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(versionQuery)+"</td>\n"+
" </tr>\n"+
" <tr>\n"+
" <td class=\"description\"><nobr>" + Messages.getBodyString(locale,"JDBCConnector.AccessTokenQuery") + "</nobr></td>\n"+
" <td class=\"value\">"+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(aclQuery)+"</td>\n"+
" </tr>\n"+
" <tr>\n"+
" <td class=\"description\"><nobr>" + Messages.getBodyString(locale,"JDBCConnector.DataQuery") + "</nobr></td>\n"+
" <td class=\"value\">"+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(dataQuery)+"</td>\n"+
" </tr>\n"+
"\n"+
" <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n"+
" <tr>"+
" <td class=\"description\"><nobr>" + Messages.getBodyString(locale,"JDBCConnector.AttributeQueries") + "</nobr></td>\n"+
" <td class=\"boxcell\">\n"+
" <table class=\"formtable\">\n"+
" <tr class=\"formheaderrow\">\n"+
" <td class=\"formcolumnheader\"><nobr>" + Messages.getBodyString(locale,"JDBCConnector.AttributeName") + "</nobr></td>\n"+
" <td class=\"formcolumnheader\"><nobr>" + Messages.getBodyString(locale,"JDBCConnector.AttributeQuery") + "</nobr></td>\n"+
" </tr>\n"
);
int attributeIndex = 0;
for (final String attributeName : attributeNames) {
final String attributeQuery = attributeQueryMap.get(attributeName);
if (attributeIndex % 2 == 0)
{
out.print(
" <tr class=\"evenformrow\">\n"
);
}
else
{
out.print(
" <tr class=\"oddformrow\">\n"
);
}
// Attribute name
out.print(
" <td class=\"formcolumncell\">\n"+
" "+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(attributeName)+"\n"+
" </td>\n"
);
// Query
out.print(
" <td class=\"formcolumncell\">\n"+
" "+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(attributeQuery)+"\n"+
" </td>\n"
);
out.print(
" </tr>\n"
);
attributeIndex++;
}
if (attributeIndex == 0)
{
out.print(
" <tr><td class=\"formmessage\" colspan=\"2\">"+Messages.getBodyString(locale,"JDBCConnector.NoAttributeQueries")+"</td></tr>\n"
);
}
out.print(
" </table>\n"+
" </td>\n"+
" <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n"
);
// Find whether security is on or off
i = 0;
boolean securityOn = true;
while (i < ds.getChildCount())
{
SpecificationNode sn = ds.getChild(i++);
if (sn.getType().equals("security"))
{
String securityValue = sn.getAttributeValue("value");
if (securityValue.equals("off"))
securityOn = false;
else if (securityValue.equals("on"))
securityOn = true;
}
}
out.print(
" <tr>\n"+
" <td class=\"description\">"+Messages.getBodyString(locale,"JDBCConnector.SecurityColon")+"</td>\n"+
" <td class=\"value\">"+(securityOn?Messages.getBodyString(locale,"JDBCConnector.Enabled"):Messages.getBodyString(locale,"JDBCConnector.Disabled"))+"</td>\n"+
" </tr>\n"+
"\n"+
" <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n"
);
// Go through looking for access tokens
boolean seenAny = false;
i = 0;
while (i < ds.getChildCount())
{
SpecificationNode sn = ds.getChild(i++);
if (sn.getType().equals("access"))
{
if (seenAny == false)
{
out.print(
" <tr><td class=\"description\"><nobr>" + Messages.getBodyString(locale,"JDBCConnector.AccessTokens") + "</nobr></td>\n"+
" <td class=\"value\">\n"
);
seenAny = true;
}
String token = sn.getAttributeValue("token");
out.print(
" "+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(token)+"<br/>\n"
);
}
}
if (seenAny)
{
out.print(
" </td>\n"+
" </tr>\n"
);
}
else
{
out.print(
" <tr><td class=\"message\" colspan=\"2\"><nobr>" + Messages.getBodyString(locale,"JDBCConnector.NoAccessTokensSpecified") + "</nobr></td></tr>\n"
);
}
out.print(
"</table>\n"
);
}
/** Special column names, as far as document queries are concerned */
protected static HashMap documentKnownColumns;
static
{
documentKnownColumns = new HashMap();
documentKnownColumns.put(JDBCConstants.idReturnColumnName,"");
documentKnownColumns.put(JDBCConstants.urlReturnColumnName,"");
documentKnownColumns.put(JDBCConstants.dataReturnColumnName,"");
documentKnownColumns.put(JDBCConstants.contentTypeReturnColumnName,"");
}
/** Apply metadata to a repository document.
*@param rd is the repository document to apply the metadata to.
*@param row is the resultset row to use to get the metadata. All non-special columns from this row will be considered to be metadata.
*/
protected void applyMetadata(RepositoryDocument rd, IResultRow row)
throws ManifoldCFException
{
// Cycle through the row's columns
Iterator iter = row.getColumns();
while (iter.hasNext())
{
String columnName = (String)iter.next();
if (documentKnownColumns.get(columnName) == null)
{
// Consider this column to contain metadata.
// We can only accept non-binary metadata at this time.
Object metadata = row.getValue(columnName);
rd.addField(columnName,JDBCConnection.readAsString(metadata));
}
}
}
/** Apply multi-valued attribute values to a repository document.
*/
protected void applyMultiAttributeValues(final RepositoryDocument rd, final Map<String, Set<String>> values)
throws ManifoldCFException
{
if (values == null)
{
return;
}
for (final String attributeName : values.keySet())
{
final Set<String> attributes = values.get(attributeName);
final String[] attributeValues = new String[values.size()];
int i = 0;
for (final String attributeValue : attributes)
{
attributeValues[i++] = attributeValue;
}
rd.addField(attributeName, attributeValues);
}
}
/** Apply access tokens to a repository document.
*@param rd is the repository document to apply the access tokens to.
*@param version is the version string.
*@param spec is the document specification.
*/
protected void applyAccessTokens(RepositoryDocument rd, Set<String> accessTokens)
throws ManifoldCFException
{
if (accessTokens == null)
return;
String[] accessAcls = new String[accessTokens.size()];
int i = 0;
for (String accessToken : accessTokens)
{
accessAcls[i++] = accessToken;
}
java.util.Arrays.sort(accessAcls);
String[] denyAcls = new String[]{defaultAuthorityDenyToken};
rd.setSecurity(RepositoryDocument.SECURITY_TYPE_DOCUMENT,accessAcls,denyAcls);
}
/** Get the maximum number of documents to amalgamate together into one batch, for this connector.
*@return the maximum number. 0 indicates "unlimited".
*/
@Override
public int getMaxDocumentRequest()
{
// This is a number that is comfortably processed by the query processor as part of an IN clause.
return 100;
}
// These are protected helper methods
/** Add starttime and endtime query variables
*/
protected static void addVariable(VariableMap map, String varName, long variable)
{
ArrayList params = new ArrayList();
params.add(new Long(variable));
map.addVariable(varName,"?",params);
}
/** Add string query variables
*/
protected static void addVariable(VariableMap map, String varName, String variable)
{
ArrayList params = new ArrayList();
params.add(variable);
map.addVariable(varName,"?",params);
}
/** Add string query constants
*/
protected static void addConstant(VariableMap map, String varName, String value)
{
map.addVariable(varName,value,null);
}
/** Build an idlist variable, and add it to the specified variable map.
*/
protected static boolean addIDList(VariableMap map, String varName, String[] documentIdentifiers, Set<String> fetchDocuments)
{
ArrayList params = new ArrayList();
StringBuilder sb = new StringBuilder(" (");
int k = 0;
for (String documentIdentifier : documentIdentifiers)
{
if (fetchDocuments == null || fetchDocuments.contains(documentIdentifier))
{
if (k > 0)
sb.append(",");
sb.append("?");
params.add(documentIdentifier);
k++;
}
}
sb.append(") ");
map.addVariable(varName,sb.toString(),params);
return (k > 0);
}
/** Given a query, and a parameter map, substitute it.
* Each variable substitutes the string, and it also substitutes zero or more query parameters.
*/
protected static void substituteQuery(String inputString, VariableMap inputMap, StringBuilder outputQuery, ArrayList outputParams)
throws ManifoldCFException
{
// We are looking for strings that look like this: $(something)
// Right at the moment we don't care even about quotes, so we just want to look for $(.
int startIndex = 0;
while (true)
{
int nextIndex = inputString.indexOf("$(",startIndex);
if (nextIndex == -1)
{
outputQuery.append(inputString.substring(startIndex));
break;
}
int endIndex = inputString.indexOf(")",nextIndex);
if (endIndex == -1)
{
outputQuery.append(inputString.substring(startIndex));
break;
}
String variableName = inputString.substring(nextIndex+2,endIndex);
VariableMapItem item = inputMap.getVariable(variableName);
if (item == null)
throw new ManifoldCFException("No such substitution variable: $("+variableName+")");
outputQuery.append(inputString.substring(startIndex,nextIndex));
outputQuery.append(item.getValue());
ArrayList inputParams = item.getParameters();
if (inputParams != null)
{
int i = 0;
while (i < inputParams.size())
{
Object x = inputParams.get(i++);
outputParams.add(x);
}
}
startIndex = endIndex+1;
}
}
/** Create an entity identifier from a querystring and a parameter list.
*/
protected static String createQueryString(String queryText, ArrayList paramList)
{
StringBuilder sb = new StringBuilder(queryText);
sb.append("; arguments = (");
int i = 0;
while (i < paramList.size())
{
if (i > 0)
sb.append(",");
Object parameter = paramList.get(i++);
if (parameter instanceof String)
sb.append(quoteSQLString((String)parameter));
else
sb.append(parameter.toString());
}
sb.append(")");
return sb.toString();
}
/** Quote a sql string.
*/
protected static String quoteSQLString(String input)
{
StringBuilder sb = new StringBuilder("\'");
int i = 0;
while (i < input.length())
{
char x = input.charAt(i++);
if (x == '\'')
sb.append('\'').append(x);
else if (x >= 0 && x < ' ')
sb.append(' ');
else
sb.append(x);
}
sb.append("\'");
return sb.toString();
}
/** Variable map entry.
*/
protected static class VariableMapItem
{
protected String value;
protected ArrayList params;
/** Constructor.
*/
public VariableMapItem(String value, ArrayList params)
{
this.value = value;
this.params = params;
}
/** Get value.
*/
public String getValue()
{
return value;
}
/** Get parameters.
*/
public ArrayList getParameters()
{
return params;
}
}
/** Variable map.
*/
protected static class VariableMap
{
protected Map variableMap = new HashMap();
/** Constructor
*/
public VariableMap()
{
}
/** Add a variable map entry */
public void addVariable(String variableName, String value, ArrayList parameters)
{
VariableMapItem e = new VariableMapItem(value,parameters);
variableMap.put(variableName,e);
}
/** Get a variable map entry */
public VariableMapItem getVariable(String variableName)
{
return (VariableMapItem)variableMap.get(variableName);
}
}
/** This class represents data gleaned from a document specification, in a more usable form.
*/
protected static class TableSpec
{
public final String idQuery;
public final String versionQuery;
public final String dataQuery;
public final String aclQuery;
public final boolean securityOn;
public final Set<String> aclMap = new HashSet<String>();
public TableSpec(Specification ds)
{
String idQuery = null;
String versionQuery = null;
String dataQuery = null;
String aclQuery = null;
boolean securityOn = false;
for (int i = 0; i < ds.getChildCount(); i++)
{
SpecificationNode sn = ds.getChild(i);
if (sn.getType().equals(JDBCConstants.idQueryNode))
{
idQuery = sn.getValue();
if (idQuery == null)
idQuery = "";
}
else if (sn.getType().equals(JDBCConstants.versionQueryNode))
{
versionQuery = sn.getValue();
if (versionQuery == null)
versionQuery = "";
}
else if (sn.getType().equals(JDBCConstants.dataQueryNode))
{
dataQuery = sn.getValue();
if (dataQuery == null)
dataQuery = "";
}
else if (sn.getType().equals(JDBCConstants.aclQueryNode))
{
aclQuery = sn.getValue();
if (aclQuery == null)
aclQuery = "";
}
else if (sn.getType().equals("access"))
{
String token = sn.getAttributeValue("token");
aclMap.add(token);
}
else if (sn.getType().equals("security"))
{
String value = sn.getAttributeValue("value");
securityOn = value.equals("on");
}
}
this.idQuery = idQuery;
this.versionQuery = versionQuery;
this.dataQuery = dataQuery;
this.aclQuery = aclQuery;
this.securityOn = securityOn;
}
public Set<String> getAcls()
{
return aclMap;
}
public boolean isSecurityOn()
{
return securityOn;
}
}
}