| /* $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.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); |
| } |
| } |
| } |
| |
| // 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); |
| |
| 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 (> V6.5)</option>\n"+ |
| " <option value=\"jtds:sybase:\" "+(jdbcProvider.equals("jtds:sybase:")?"selected=\"selected\"":"")+">Sybase (>= V10)</option>\n"+ |
| " <option value=\"mysql:\" "+(jdbcProvider.equals("mysql:")?"selected=\"selected\"":"")+">MySQL (>= 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)+"=<"+Integer.toString(kmanager.getContents().length)+" certificate(s)></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+"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)"; |
| |
| 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 = ""; |
| } |
| } |
| |
| // 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"+ |
| "</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" |
| ); |
| } |
| |
| // 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> \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> \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 = 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 = ""; |
| |
| 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 = ""; |
| } |
| } |
| |
| 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" |
| ); |
| // 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 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; |
| } |
| |
| } |
| |
| } |
| |
| |