| /* $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.*; |
| |
| /** This interface describes an instance of a connection between a repository and ManifoldCF's |
| * standard "pull" ingestion agent. |
| * |
| * Each instance of this interface is used in only one thread at a time. Connection Pooling |
| * on these kinds of objects is performed by the factory which instantiates repository connectors |
| * from symbolic names and config parameters, and is pooled by these parameters. That is, a pooled connector |
| * handle is used only if all the connection parameters for the handle match. |
| * |
| * Implementers of this interface should provide a default constructor which has this signature: |
| * |
| * xxx(); |
| * |
| * Connectors are either configured or not. If configured, they will persist in a pool, and be |
| * reused multiple times. Certain methods of a connector may be called before the connector is |
| * configured. This includes basically all methods that permit inspection of the connector's |
| * capabilities. The complete list is: |
| * |
| * |
| * The purpose of the repository connector is to allow documents to be fetched from the repository. |
| * |
| * Each repository connector describes a set of documents that are known only to that connector. |
| * It therefore establishes a space of document identifiers. Each connector will only ever be |
| * asked to deal with identifiers that have in some way originated from the connector. |
| * |
| * Documents are fetched in three stages. First, the getDocuments() method is called in the connector |
| * implementation. This returns a set of document identifiers. The document identifiers are used to |
| * obtain the current document version strings in the second stage, using the getDocumentVersions() method. |
| * The last stage is processDocuments(), which queues up any additional documents needed, and also ingests. |
| * This method will not be called if the document version seems to indicate that no document change took |
| * place. |
| */ |
| 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"; |
| |
| // Activities list |
| protected static final String[] activitiesList = new String[]{ACTIVITY_EXTERNAL_QUERY}; |
| |
| /** 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 times passed to this method may be interpreted for greatest efficiency. The time ranges |
| * any given job uses with this connector will not overlap, but will proceed starting at 0 and going |
| * to the "current time", each time the job is run. 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 start time to 0. The |
| * seeding start time may also be set to 0 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. |
| *@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 startTime is the beginning of the time range to consider, inclusive. |
| *@param endTime is the end of the time range to consider, exclusive. |
| *@param jobMode is an integer describing how the job is being run, whether continuous or once-only. |
| */ |
| @Override |
| public void addSeedDocuments(ISeedingActivity activities, DocumentSpecification spec, |
| long startTime, long endTime, int jobMode) |
| throws ManifoldCFException, ServiceInterruption |
| { |
| 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,endTime); |
| |
| // 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)\"."); |
| String idValue = JDBCConnection.readAsString(o); |
| activities.addSeedDocument(idValue); |
| } |
| finally |
| { |
| row.close(); |
| } |
| } |
| } |
| finally |
| { |
| idSet.close(); |
| } |
| } |
| |
| /** Get document versions given an array of document identifiers. |
| * This method is called for EVERY document that is considered. It is |
| * therefore important to perform as little work as possible here. |
| *@param documentIdentifiers is the array of local document identifiers, as understood by this connector. |
| *@param oldVersions is the corresponding array of version strings that have been saved for the document identifiers. |
| * A null value indicates that this is a first-time fetch, while an empty string indicates that the previous document |
| * had an empty version string. |
| *@param activities is the interface this method should use to perform whatever framework actions are desired. |
| *@param spec is the current document specification for the current job. If there is a dependency on this |
| * specification, then the version string should include the pertinent data, so that reingestion will occur |
| * when the specification changes. This is primarily useful for metadata. |
| *@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. |
| *@return the corresponding version strings, with null in the places where the document no longer exists. |
| * Empty version strings indicate that there is no versioning ability for the corresponding document, and the document |
| * will always be processed. |
| */ |
| @Override |
| public String[] getDocumentVersions(String[] documentIdentifiers, String[] oldVersions, IVersionActivity activities, |
| DocumentSpecification spec, int jobMode, boolean usesDefaultAuthority) |
| throws ManifoldCFException, ServiceInterruption |
| { |
| getSession(); |
| TableSpec ts = new TableSpec(spec); |
| String[] acls = getAcls(spec); |
| // Sort these, |
| java.util.Arrays.sort(acls); |
| |
| 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. |
| |
| if (ts.versionQuery == null || ts.versionQuery.length() == 0) |
| { |
| int i = 0; |
| while (i < versionsReturned.length) |
| { |
| versionsReturned[i++] = ""; |
| } |
| |
| return versionsReturned; |
| } |
| |
| // 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)) |
| return new String[0]; |
| |
| // 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. |
| Map map = new HashMap(); |
| int j = 0; |
| while (j < documentIdentifiers.length) |
| { |
| map.put(documentIdentifiers[j],new Integer(j)); |
| versionsReturned[j] = ""; |
| j++; |
| } |
| |
| // Fire off the query! |
| 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); |
| // 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)\"."); |
| String idValue = JDBCConnection.readAsString(o); |
| o = row.getValue(JDBCConstants.versionReturnColumnName); |
| String versionValue; |
| // Null version is OK; make it a "" |
| if (o == null) |
| versionValue = ""; |
| else |
| { |
| // A real version string! Any acls must be added to the front, if they are present... |
| sb = new StringBuilder(); |
| packList(sb,acls,'+'); |
| if (acls.length > 0) |
| { |
| sb.append('+'); |
| pack(sb,defaultAuthorityDenyToken,'+'); |
| } |
| else |
| sb.append('-'); |
| |
| sb.append(JDBCConnection.readAsString(o)).append("=").append(ts.dataQuery); |
| versionValue = sb.toString(); |
| } |
| // Versions that are "", when processed, will have their acls fetched at that time... |
| versionsReturned[((Integer)map.get(idValue)).intValue()] = versionValue; |
| } |
| finally |
| { |
| row.close(); |
| } |
| } |
| } |
| finally |
| { |
| result.close(); |
| } |
| |
| return versionsReturned; |
| } |
| |
| /** 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. |
| *@param documentIdentifiers is the set of document identifiers to process. |
| *@param versions is the corresponding document versions to process, as returned by getDocumentVersions() above. |
| * The implementation may choose to ignore this parameter and always process the current version. |
| *@param activities is the interface this method should use to queue up new document references |
| * and ingest documents. |
| *@param spec is the document specification. |
| *@param scanOnly is an array corresponding to the document identifiers. It is set to true to indicate when the processing |
| * should only find other references, and should not actually call the ingestion methods. |
| */ |
| @Override |
| public void processDocuments(String[] documentIdentifiers, String[] versions, IProcessActivity activities, DocumentSpecification spec, boolean[] scanOnly) |
| throws ManifoldCFException, ServiceInterruption |
| { |
| getSession(); |
| TableSpec ts = new TableSpec(spec); |
| |
| // 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,scanOnly)) |
| return; |
| |
| // Do the substitution |
| ArrayList paramList = new ArrayList(); |
| StringBuilder sb = new StringBuilder(); |
| substituteQuery(ts.dataQuery,vm,sb,paramList); |
| |
| int i; |
| |
| // Build a map of versions we are allowed to ingest |
| Map map = new HashMap(); |
| i = 0; |
| while (i < documentIdentifiers.length) |
| { |
| if (!scanOnly[i]) |
| { |
| // Version strings at this point should never be null; the CF interprets nulls as |
| // meaning that delete must occur. Empty strings are possible though. |
| map.put(documentIdentifiers[i],versions[i]); |
| } |
| i++; |
| } |
| |
| // Execute the query |
| 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)\"."); |
| String id = JDBCConnection.readAsString(o); |
| String version = (String)map.get(id); |
| if (version != null) |
| { |
| // 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) |
| { |
| // 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) |
| { |
| // Process the document itself |
| Object contents = row.getValue(JDBCConstants.dataReturnColumnName); |
| // Null data is allowed; we just ignore these |
| if (contents != null) |
| { |
| // 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 |
| contentType = null; |
| |
| if (contentType == null || activities.checkMimeTypeIndexable(contentType)) |
| { |
| if (contents instanceof BinaryInput) |
| { |
| // An ingestion will take place for this document. |
| RepositoryDocument rd = new RepositoryDocument(); |
| |
| // Default content type is application/octet-stream for binary data |
| if (contentType == null) |
| rd.setMimeType("application/octet-stream"); |
| else |
| rd.setMimeType(contentType); |
| |
| applyAccessTokens(rd,version,spec); |
| applyMetadata(rd,row); |
| |
| BinaryInput bi = (BinaryInput)contents; |
| try |
| { |
| // Read the stream |
| InputStream is = bi.getStream(); |
| try |
| { |
| rd.setBinary(is,bi.getLength()); |
| activities.ingestDocumentWithException(id, version, url, rd); |
| } |
| finally |
| { |
| is.close(); |
| } |
| } |
| catch (java.net.SocketTimeoutException e) |
| { |
| throw new ManifoldCFException("Socket timeout reading database data: "+e.getMessage(),e); |
| } |
| catch (InterruptedIOException e) |
| { |
| throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED); |
| } |
| catch (IOException e) |
| { |
| throw new ManifoldCFException("Error reading database data: "+e.getMessage(),e); |
| } |
| } |
| else if (contents instanceof CharacterInput) |
| { |
| // An ingestion will take place for this document. |
| RepositoryDocument rd = new RepositoryDocument(); |
| |
| // Default content type is application/octet-stream for binary data |
| if (contentType == null) |
| rd.setMimeType("text/plain; charset=utf-8"); |
| else |
| rd.setMimeType(contentType); |
| |
| applyAccessTokens(rd,version,spec); |
| applyMetadata(rd,row); |
| |
| CharacterInput ci = (CharacterInput)contents; |
| try |
| { |
| // Read the stream |
| InputStream is = ci.getUtf8Stream(); |
| try |
| { |
| rd.setBinary(is,ci.getUtf8StreamLength()); |
| activities.ingestDocumentWithException(id, version, url, rd); |
| } |
| finally |
| { |
| is.close(); |
| } |
| } |
| catch (java.net.SocketTimeoutException e) |
| { |
| throw new ManifoldCFException("Socket timeout reading database data: "+e.getMessage(),e); |
| } |
| catch (InterruptedIOException e) |
| { |
| throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED); |
| } |
| catch (IOException e) |
| { |
| throw new ManifoldCFException("Error reading database data: "+e.getMessage(),e); |
| } |
| } |
| else |
| { |
| // Turn it into a string, and then into a stream |
| String value = contents.toString(); |
| try |
| { |
| byte[] bytes = value.getBytes(StandardCharsets.UTF_8); |
| RepositoryDocument rd = new RepositoryDocument(); |
| |
| // Default content type is text/plain for character data |
| if (contentType == null) |
| rd.setMimeType("text/plain"); |
| else |
| rd.setMimeType(contentType); |
| |
| applyAccessTokens(rd,version,spec); |
| applyMetadata(rd,row); |
| |
| InputStream is = new ByteArrayInputStream(bytes); |
| try |
| { |
| rd.setBinary(is,bytes.length); |
| activities.ingestDocumentWithException(id, version, url, rd); |
| } |
| finally |
| { |
| is.close(); |
| } |
| } |
| catch (InterruptedIOException e) |
| { |
| throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED); |
| } |
| catch (IOException e) |
| { |
| throw new ManifoldCFException("Error reading database data: "+e.getMessage(),e); |
| } |
| } |
| } |
| else |
| Logging.connectors.warn("JDBC: Document '"+id+"' excluded because of mime type - skipping"); |
| } |
| else |
| Logging.connectors.warn("JDBC: Document '"+id+"' seems to have null data - skipping"); |
| } |
| else |
| Logging.connectors.warn("JDBC: Document '"+id+"' has an illegal url: '"+url+"' - skipping"); |
| } |
| else |
| Logging.connectors.warn("JDBC: Document '"+id+"' has a null url - skipping"); |
| } |
| } |
| finally |
| { |
| row.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. |
| i = 0; |
| while (i < documentIdentifiers.length) |
| { |
| if (!scanOnly[i]) |
| { |
| String documentIdentifier = documentIdentifiers[i]; |
| if (map.get(documentIdentifier) != null) |
| { |
| // This means we did not see it (or data for it) in the result set. Delete it! |
| activities.deleteDocument(documentIdentifier); |
| } |
| } |
| i++; |
| } |
| |
| } |
| finally |
| { |
| result.close(); |
| } |
| } |
| |
| // 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. |
| *@param out is the output to which any HTML should be sent. |
| *@param ds is the current document specification for this 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, DocumentSpecification ds, List<String> tabsArray) |
| throws ManifoldCFException, IOException |
| { |
| tabsArray.add(Messages.getString(locale,"JDBCConnector.Queries")); |
| tabsArray.add(Messages.getString(locale,"JDBCConnector.Security")); |
| |
| out.print( |
| "<script type=\"text/javascript\">\n"+ |
| "<!--\n"+ |
| "\n"+ |
| "function SpecOp(n, opValue, anchorvalue)\n"+ |
| "{\n"+ |
| " eval(\"editjob.\"+n+\".value = \\\"\"+opValue+\"\\\"\");\n"+ |
| " postFormSetAnchor(anchorvalue);\n"+ |
| "}\n"+ |
| "\n"+ |
| "function SpecAddToken(anchorvalue)\n"+ |
| "{\n"+ |
| " if (editjob.spectoken.value == \"\")\n"+ |
| " {\n"+ |
| " alert(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.TypeInAnAccessToken") + "\");\n"+ |
| " editjob.spectoken.focus();\n"+ |
| " return;\n"+ |
| " }\n"+ |
| " SpecOp(\"accessop\",\"Add\",anchorvalue);\n"+ |
| "}\n"+ |
| "\n"+ |
| "function checkSpecification()\n"+ |
| "{\n"+ |
| " if (editjob.idquery.value == \"\")\n"+ |
| " {\n"+ |
| " alert(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.EnterASeedingQuery") + "\");\n"+ |
| " editjob.idquery.focus();\n"+ |
| " return false;\n"+ |
| " }\n"+ |
| " if (editjob.idquery.value.indexOf(\"$(IDCOLUMN)\") == -1)\n"+ |
| " {\n"+ |
| " alert(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.MustReturnIDCOLUMNInTheResult") + "\");\n"+ |
| " editjob.idquery.focus();\n"+ |
| " return false;\n"+ |
| " }\n"+ |
| " if (editjob.versionquery.value != \"\")\n"+ |
| " {\n"+ |
| " if (editjob.versionquery.value.indexOf(\"$(IDCOLUMN)\") == -1)\n"+ |
| " {\n"+ |
| " alert(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.MustReturnIDCOLUMNInTheResult") + "\");\n"+ |
| " editjob.versionquery.focus();\n"+ |
| " return false;\n"+ |
| " }\n"+ |
| " if (editjob.versionquery.value.indexOf(\"$(VERSIONCOLUMN)\") == -1)\n"+ |
| " {\n"+ |
| " alert(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.MustReturnVERSIONCOLUMNInTheResult") + "\");\n"+ |
| " editjob.versionquery.focus();\n"+ |
| " return false;\n"+ |
| " }\n"+ |
| " if (editjob.versionquery.value.indexOf(\"$(IDLIST)\") == -1)\n"+ |
| " {\n"+ |
| " alert(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.MustUseIDLISTInWHEREClause") + "\");\n"+ |
| " editjob.versionquery.focus();\n"+ |
| " return false;\n"+ |
| " }\n"+ |
| " }\n"+ |
| " if (editjob.dataquery.value == \"\")\n"+ |
| " {\n"+ |
| " alert(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.EnterADataQuery") + "\");\n"+ |
| " editjob.dataquery.focus();\n"+ |
| " return false;\n"+ |
| " }\n"+ |
| " if (editjob.dataquery.value.indexOf(\"$(IDCOLUMN)\") == -1)\n"+ |
| " {\n"+ |
| " alert(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.MustReturnIDCOLUMNInTheResult2") + "\");\n"+ |
| " editjob.dataquery.focus();\n"+ |
| " return false;\n"+ |
| " }\n"+ |
| " if (editjob.dataquery.value.indexOf(\"$(URLCOLUMN)\") == -1)\n"+ |
| " {\n"+ |
| " alert(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.MustReturnURLCOLUMNInTheResult") + "\");\n"+ |
| " editjob.dataquery.focus();\n"+ |
| " return false;\n"+ |
| " }\n"+ |
| " if (editjob.dataquery.value.indexOf(\"$(DATACOLUMN)\") == -1)\n"+ |
| " {\n"+ |
| " alert(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.MustReturnDATACOLUMNInTheResult") + "\");\n"+ |
| " editjob.dataquery.focus();\n"+ |
| " return false;\n"+ |
| " }\n"+ |
| " if (editjob.dataquery.value.indexOf(\"$(IDLIST)\") == -1)\n"+ |
| " {\n"+ |
| " alert(\"" + Messages.getBodyJavascriptString(locale,"JDBCConnector.MustUseIDLISTInWHEREClause") + "\");\n"+ |
| " editjob.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 "editjob". |
| *@param out is the output to which any HTML should be sent. |
| *@param ds is the current document specification for this job. |
| *@param tabName is the current tab name. |
| */ |
| @Override |
| public void outputSpecificationBody(IHTTPOutput out, Locale locale, DocumentSpecification ds, String tabName) |
| throws ManifoldCFException, IOException |
| { |
| 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)"; |
| |
| 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 = ""; |
| } |
| } |
| |
| // The Queries tab |
| |
| if (tabName.equals(Messages.getString(locale,"JDBCConnector.Queries"))) |
| { |
| 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=\"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=\"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.DataQuery") + "</nobr><br/><nobr>" + Messages.getBodyString(locale,"JDBCConnector.returnIdsUrlsAndDataForASetOfDocuments") + "</nobr></td>\n"+ |
| " <td class=\"value\"><textarea name=\"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=\"idquery\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(idQuery)+"\"/>\n"+ |
| "<input type=\"hidden\" name=\"versionquery\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(versionQuery)+"\"/>\n"+ |
| "<input type=\"hidden\" name=\"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; |
| |
| if (tabName.equals(Messages.getString(locale,"JDBCConnector.Security"))) |
| { |
| out.print( |
| "<table class=\"displaytable\">\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 = "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=\""+"spectoken"+accessDescription+"\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(token)+"\"/>\n"+ |
| " <a name=\""+"token_"+Integer.toString(k)+"\">\n"+ |
| " <input type=\"button\" value=\"Delete\" onClick='Javascript:SpecOp(\""+accessOpName+"\",\"Delete\",\"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=\"tokencount\" value=\""+Integer.toString(k)+"\"/>\n"+ |
| " <input type=\"hidden\" name=\"accessop\" value=\"\"/>\n"+ |
| " <a name=\""+"token_"+Integer.toString(k)+"\">\n"+ |
| " <input type=\"button\" value=\"Add\" onClick='Javascript:SpecAddToken(\"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=\"spectoken\" value=\"\"/>\n"+ |
| " </td>\n"+ |
| " </tr>\n"+ |
| "</table>\n" |
| ); |
| } |
| else |
| { |
| // 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=\""+"spectoken"+accessDescription+"\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(token)+"\"/>\n" |
| ); |
| k++; |
| } |
| } |
| out.print( |
| "<input type=\"hidden\" name=\"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 "editjob". |
| *@param variableContext contains the post data, including binary file-upload information. |
| *@param ds is the current document specification for this 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, DocumentSpecification ds) |
| throws ManifoldCFException |
| { |
| String idQuery = variableContext.getParameter("idquery"); |
| String versionQuery = variableContext.getParameter("versionquery"); |
| String dataQuery = variableContext.getParameter("dataquery"); |
| |
| 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 (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("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 = "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("spectoken"+accessDescription); |
| SpecificationNode node = new SpecificationNode("access"); |
| node.setAttribute("token",accessSpec); |
| ds.addChild(ds.getChildCount(),node); |
| i++; |
| } |
| |
| String op = variableContext.getParameter("accessop"); |
| if (op != null && op.equals("Add")) |
| { |
| String accessspec = variableContext.getParameter("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. |
| *@param out is the output to which any HTML should be sent. |
| *@param ds is the current document specification for this job. |
| */ |
| @Override |
| public void viewSpecification(IHTTPOutput out, Locale locale, DocumentSpecification ds) |
| throws ManifoldCFException, IOException |
| { |
| String idQuery = ""; |
| String versionQuery = ""; |
| String dataQuery = ""; |
| |
| 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 = ""; |
| } |
| } |
| |
| 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.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" |
| ); |
| // 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, String version, DocumentSpecification spec) |
| throws ManifoldCFException |
| { |
| // Set up any acls |
| String[] accessAcls = null; |
| String[] denyAcls = null; |
| |
| if (version.length() == 0) |
| { |
| // Version is empty string, therefore acl information must be gathered from spec |
| String[] specAcls = getAcls(spec); |
| accessAcls = specAcls; |
| if (specAcls.length != 0) |
| denyAcls = new String[]{defaultAuthorityDenyToken}; |
| else |
| denyAcls = new String[0]; |
| } |
| else |
| { |
| // Unpack access tokens and the deny token too |
| ArrayList acls = new ArrayList(); |
| StringBuilder denyAclBuffer = new StringBuilder(); |
| int startPos = unpackList(acls,version,0,'+'); |
| if (startPos < version.length() && version.charAt(startPos++) == '+') |
| { |
| startPos = unpack(denyAclBuffer,version,startPos,'+'); |
| } |
| // Turn into acls and add into description |
| accessAcls = new String[acls.size()]; |
| int j = 0; |
| while (j < accessAcls.length) |
| { |
| accessAcls[j] = (String)acls.get(j); |
| j++; |
| } |
| // Deny acl too |
| if (denyAclBuffer.length() > 0) |
| { |
| denyAcls = new String[]{denyAclBuffer.toString()}; |
| } |
| } |
| |
| 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, boolean[] scanOnly) |
| { |
| ArrayList params = new ArrayList(); |
| StringBuilder sb = new StringBuilder(" ("); |
| int i = 0; |
| int k = 0; |
| while (i < documentIdentifiers.length) |
| { |
| if (scanOnly == null || !scanOnly[i]) |
| { |
| if (k > 0) |
| sb.append(","); |
| String documentIdentifier = documentIdentifiers[i]; |
| sb.append("?"); |
| params.add(documentIdentifier); |
| k++; |
| } |
| i++; |
| } |
| 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; |
| } |
| } |
| |
| /** Grab forced acl out of document specification. |
| *@param spec is the document specification. |
| *@return the acls. |
| */ |
| protected static String[] getAcls(DocumentSpecification spec) |
| { |
| HashMap map = new HashMap(); |
| int i = 0; |
| while (i < spec.getChildCount()) |
| { |
| SpecificationNode sn = spec.getChild(i++); |
| if (sn.getType().equals("access")) |
| { |
| String token = sn.getAttributeValue("token"); |
| map.put(token,token); |
| } |
| } |
| |
| String[] rval = new String[map.size()]; |
| Iterator iter = map.keySet().iterator(); |
| i = 0; |
| while (iter.hasNext()) |
| { |
| rval[i++] = (String)iter.next(); |
| } |
| return rval; |
| } |
| |
| /** 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 String idQuery; |
| public String versionQuery; |
| public String dataQuery; |
| |
| public TableSpec(DocumentSpecification ds) |
| { |
| 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 = ""; |
| } |
| } |
| |
| } |
| |
| } |
| |
| } |
| |
| |