| /* $Id: LivelinkConnector.java 996524 2010-09-13 13:38:01Z 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.livelink; |
| |
| 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.crawler.system.ManifoldCF; |
| import org.apache.manifoldcf.core.common.XThreadInputStream; |
| import org.apache.manifoldcf.core.common.XThreadOutputStream; |
| import org.apache.manifoldcf.core.common.InterruptibleSocketFactory; |
| |
| import java.io.*; |
| import java.util.*; |
| import java.net.*; |
| import java.util.concurrent.TimeUnit; |
| |
| import com.opentext.api.*; |
| |
| import org.apache.http.conn.HttpClientConnectionManager; |
| import org.apache.http.client.HttpClient; |
| import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; |
| import org.apache.http.impl.client.HttpClientBuilder; |
| import org.apache.http.protocol.HttpRequestExecutor; |
| import org.apache.http.impl.client.HttpClients; |
| import org.apache.http.client.config.RequestConfig; |
| import org.apache.http.client.CredentialsProvider; |
| import org.apache.http.impl.client.BasicCredentialsProvider; |
| import org.apache.http.config.SocketConfig; |
| import org.apache.http.client.HttpClient; |
| import org.apache.http.client.methods.HttpGet; |
| import org.apache.http.client.methods.HttpPost; |
| import org.apache.http.NameValuePair; |
| import org.apache.http.conn.ssl.SSLConnectionSocketFactory; |
| import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier; |
| import org.apache.http.HttpEntity; |
| import org.apache.http.HttpResponse; |
| import org.apache.http.auth.AuthScope; |
| import org.apache.http.auth.NTCredentials; |
| import org.apache.http.client.methods.HttpGet; |
| import org.apache.http.client.methods.HttpRequestBase; |
| import org.apache.http.impl.client.DefaultRedirectStrategy; |
| import org.apache.http.util.EntityUtils; |
| import org.apache.http.HttpStatus; |
| import org.apache.http.HttpHost; |
| import org.apache.http.Header; |
| import org.apache.http.message.BasicHeader; |
| import org.apache.http.message.BasicNameValuePair; |
| import org.apache.http.protocol.HTTP; |
| import org.apache.http.client.entity.UrlEncodedFormEntity; |
| import org.apache.http.client.HttpRequestRetryHandler; |
| import org.apache.http.protocol.HttpContext; |
| |
| import org.apache.http.conn.ConnectTimeoutException; |
| import org.apache.http.client.RedirectException; |
| import org.apache.http.client.CircularRedirectException; |
| import org.apache.http.NoHttpResponseException; |
| import org.apache.http.HttpException; |
| |
| |
| /** This is the Livelink implementation of the IRepositoryConnectr interface. |
| * The original Volant code forced there to be one livelink session per JVM, with |
| * lots of buggy synchronization present to try to enforce this. This implementation |
| * is multi-session. However, since it is possible that the Volant restriction was |
| * indeed needed, I have attempted to structure things to allow me to turn on |
| * single-session if needed. |
| * |
| * For livelink, the document identifiers are the object identifiers. |
| * |
| */ |
| public class LivelinkConnector extends org.apache.manifoldcf.crawler.connectors.BaseRepositoryConnector |
| { |
| public static final String _rcsid = "@(#)$Id: LivelinkConnector.java 996524 2010-09-13 13:38:01Z kwright $"; |
| |
| // Activities we will report on |
| private final static String ACTIVITY_SEED = "find documents"; |
| private final static String ACTIVITY_FETCH = "fetch document"; |
| |
| /** Deny access token for default authority */ |
| private final static String defaultAuthorityDenyToken = GLOBAL_DENY_TOKEN; |
| |
| // Livelink does not have "deny" permissions, and there is no such thing as a document with no tokens, so it is safe to not have a local "deny" token. |
| // However, people feel that a suspenders-and-belt approach is called for, so this restriction has been added. |
| // Livelink tokens are numbers, "SYSTEM", or "GUEST", so they can't collide with the standard form. |
| private static final String denyToken = "DEAD_AUTHORITY"; |
| |
| // A couple of very important points. |
| // First, the canonical document identifier has the following form: |
| // <D|F>[<volume_id>:]<object_id> |
| // Second, the only LEGAL objects for a document identifier to describe |
| // are folders, documents, and volume objects. Project objects are NOT |
| // allowed; they must be mapped to the appropriate volume object before |
| // being returned to the crawler. |
| |
| // Metadata names for general metadata fields |
| protected final static String GENERAL_NAME_FIELD = "general_name"; |
| protected final static String GENERAL_DESCRIPTION_FIELD = "general_description"; |
| protected final static String GENERAL_CREATIONDATE_FIELD = "general_creationdate"; |
| protected final static String GENERAL_MODIFYDATE_FIELD = "general_modifydate"; |
| protected final static String GENERAL_OWNER = "general_owner"; |
| protected final static String GENERAL_CREATOR = "general_creator"; |
| protected final static String GENERAL_MODIFIER = "general_modifier"; |
| protected final static String GENERAL_PARENTID = "general_parentid"; |
| |
| // Signal that we have set up connection parameters properly |
| private boolean hasSessionParameters = false; |
| // Signal that we have set up a connection properly |
| private boolean hasConnected = false; |
| // Session expiration time |
| private long expirationTime = -1L; |
| // Idle session expiration interval |
| private final static long expirationInterval = 300000L; |
| |
| // Data required for maintaining livelink connection |
| private LAPI_DOCUMENTS LLDocs = null; |
| private LAPI_ATTRIBUTES LLAttributes = null; |
| private LAPI_USERS LLUsers = null; |
| |
| private LLSERVER llServer = null; |
| private int LLENTWK_VOL; |
| private int LLENTWK_ID; |
| private int LLCATWK_VOL; |
| private int LLCATWK_ID; |
| |
| // Parameter values we need |
| private String serverProtocol = null; |
| private String serverName = null; |
| private int serverPort = -1; |
| private String serverUsername = null; |
| private String serverPassword = null; |
| private String serverHTTPCgi = null; |
| private String serverHTTPNTLMDomain = null; |
| private String serverHTTPNTLMUsername = null; |
| private String serverHTTPNTLMPassword = null; |
| private IKeystoreManager serverHTTPSKeystore = null; |
| |
| private String ingestProtocol = null; |
| private String ingestPort = null; |
| private String ingestCgiPath = null; |
| |
| private String viewProtocol = null; |
| private String viewServerName = null; |
| private String viewPort = null; |
| private String viewCgiPath = null; |
| |
| private String ingestNtlmDomain = null; |
| private String ingestNtlmUsername = null; |
| private String ingestNtlmPassword = null; |
| |
| // SSL support for ingestion |
| private IKeystoreManager ingestKeystoreManager = null; |
| |
| // Connection management |
| private HttpClientConnectionManager connectionManager = null; |
| private HttpClient httpClient = null; |
| |
| // Base path for viewing |
| private String viewBasePath = null; |
| |
| // Ingestion port number |
| private int ingestPortNumber = -1; |
| |
| // Activities list |
| private static final String[] activitiesList = new String[]{ACTIVITY_SEED,ACTIVITY_FETCH}; |
| |
| // Retry count. This is so we can try to install some measure of sanity into situations where LAPI gets confused communicating to the server. |
| // So, for some kinds of errors, we just retry for a while hoping it will go away. |
| private static final int FAILURE_RETRY_COUNT = 10; |
| |
| // Current host name |
| private static String currentHost = null; |
| private static java.net.InetAddress currentAddr = null; |
| static |
| { |
| // Find the current host name |
| try |
| { |
| currentAddr = java.net.InetAddress.getLocalHost(); |
| |
| // Get hostname |
| currentHost = currentAddr.getHostName(); |
| } |
| catch (UnknownHostException e) |
| { |
| } |
| } |
| |
| |
| /** Constructor. |
| */ |
| public LivelinkConnector() |
| { |
| } |
| |
| /** Tell the world what model this connector uses for getDocumentIdentifiers(). |
| * This must return a model value as specified above. |
| *@return the model type value. |
| */ |
| @Override |
| public int getConnectorModel() |
| { |
| // Livelink is a chained hierarchy model |
| return MODEL_CHAINED_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); |
| |
| // This is required by getBins() |
| serverName = params.getParameter(LiveLinkParameters.serverName); |
| } |
| |
| protected class GetSessionThread extends Thread |
| { |
| protected Throwable exception = null; |
| |
| public GetSessionThread() |
| { |
| super(); |
| setDaemon(true); |
| } |
| |
| public void run() |
| { |
| try |
| { |
| // Create the session |
| llServer = new LLSERVER(!serverProtocol.equals("internal"),serverProtocol.equals("https"), |
| serverName,serverPort,serverUsername,serverPassword, |
| serverHTTPCgi,serverHTTPNTLMDomain,serverHTTPNTLMUsername,serverHTTPNTLMPassword, |
| serverHTTPSKeystore); |
| |
| LLDocs = new LAPI_DOCUMENTS(llServer.getLLSession()); |
| LLAttributes = new LAPI_ATTRIBUTES(llServer.getLLSession()); |
| LLUsers = new LAPI_USERS(llServer.getLLSession()); |
| |
| if (Logging.connectors.isDebugEnabled()) |
| { |
| String passwordExists = (serverPassword!=null&&serverPassword.length()>0)?"password exists":""; |
| Logging.connectors.debug("Livelink: Livelink Session: Server='"+serverName+"'; port='"+serverPort+"'; user name='"+serverUsername+"'; "+passwordExists); |
| } |
| LLValue entinfo = new LLValue().setAssoc(); |
| |
| int status; |
| status = LLDocs.AccessEnterpriseWS(entinfo); |
| if (status == 0) |
| { |
| LLENTWK_ID = entinfo.toInteger("ID"); |
| LLENTWK_VOL = entinfo.toInteger("VolumeID"); |
| } |
| else |
| throw new ManifoldCFException("Error accessing enterprise workspace: "+status); |
| |
| entinfo = new LLValue().setAssoc(); |
| status = LLDocs.AccessCategoryWS(entinfo); |
| if (status == 0) |
| { |
| LLCATWK_ID = entinfo.toInteger("ID"); |
| LLCATWK_VOL = entinfo.toInteger("VolumeID"); |
| } |
| else |
| throw new ManifoldCFException("Error accessing category workspace: "+status); |
| } |
| catch (Throwable e) |
| { |
| this.exception = e; |
| } |
| } |
| |
| public void finishUp() |
| throws ManifoldCFException, InterruptedException |
| { |
| join(); |
| Throwable thr = exception; |
| if (thr != null) |
| { |
| if (thr instanceof RuntimeException) |
| throw (RuntimeException)thr; |
| else if (thr instanceof ManifoldCFException) |
| throw (ManifoldCFException)thr; |
| else if (thr instanceof Error) |
| throw (Error)thr; |
| else |
| throw new RuntimeException("Unrecognized exception type: "+thr.getClass().getName()+": "+thr.getMessage(),thr); |
| } |
| } |
| |
| } |
| |
| /** 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) |
| { |
| // This should return server name |
| return new String[]{serverName}; |
| } |
| |
| protected HttpHost getHost() |
| { |
| return new HttpHost(llServer.getHost(),ingestPortNumber,ingestProtocol); |
| } |
| |
| protected void getSessionParameters() |
| throws ManifoldCFException |
| { |
| if (hasSessionParameters == false) |
| { |
| // Do the initial setup part (what used to be part of connect() itself) |
| |
| // Get the parameters |
| ingestProtocol = params.getParameter(LiveLinkParameters.ingestProtocol); |
| ingestPort = params.getParameter(LiveLinkParameters.ingestPort); |
| ingestCgiPath = params.getParameter(LiveLinkParameters.ingestCgiPath); |
| |
| viewProtocol = params.getParameter(LiveLinkParameters.viewProtocol); |
| viewServerName = params.getParameter(LiveLinkParameters.viewServerName); |
| viewPort = params.getParameter(LiveLinkParameters.viewPort); |
| viewCgiPath = params.getParameter(LiveLinkParameters.viewCgiPath); |
| |
| ingestNtlmDomain = params.getParameter(LiveLinkParameters.ingestNtlmDomain); |
| ingestNtlmUsername = params.getParameter(LiveLinkParameters.ingestNtlmUsername); |
| ingestNtlmPassword = params.getObfuscatedParameter(LiveLinkParameters.ingestNtlmPassword); |
| |
| serverProtocol = params.getParameter(LiveLinkParameters.serverProtocol); |
| String serverPortString = params.getParameter(LiveLinkParameters.serverPort); |
| serverUsername = params.getParameter(LiveLinkParameters.serverUsername); |
| serverPassword = params.getObfuscatedParameter(LiveLinkParameters.serverPassword); |
| serverHTTPCgi = params.getParameter(LiveLinkParameters.serverHTTPCgiPath); |
| serverHTTPNTLMDomain = params.getParameter(LiveLinkParameters.serverHTTPNTLMDomain); |
| serverHTTPNTLMUsername = params.getParameter(LiveLinkParameters.serverHTTPNTLMUsername); |
| serverHTTPNTLMPassword = params.getObfuscatedParameter(LiveLinkParameters.serverHTTPNTLMPassword); |
| |
| if (ingestProtocol == null || ingestProtocol.length() == 0) |
| ingestProtocol = null; |
| if (viewProtocol == null || viewProtocol.length() == 0) |
| { |
| if (ingestProtocol == null) |
| viewProtocol = "http"; |
| else |
| viewProtocol = ingestProtocol; |
| } |
| |
| if (ingestPort == null || ingestPort.length() == 0) |
| { |
| if (ingestProtocol != null) |
| { |
| if (!ingestProtocol.equals("https")) |
| ingestPort = "80"; |
| else |
| ingestPort = "443"; |
| } |
| else |
| ingestPort = null; |
| } |
| |
| if (viewPort == null || viewPort.length() == 0) |
| { |
| if (ingestProtocol == null || !viewProtocol.equals(ingestProtocol)) |
| { |
| if (!viewProtocol.equals("https")) |
| viewPort = "80"; |
| else |
| viewPort = "443"; |
| } |
| else |
| viewPort = ingestPort; |
| } |
| |
| if (ingestPort != null) |
| { |
| try |
| { |
| ingestPortNumber = Integer.parseInt(ingestPort); |
| } |
| catch (NumberFormatException e) |
| { |
| throw new ManifoldCFException("Bad ingest port: "+e.getMessage(),e); |
| } |
| } |
| |
| String viewPortString; |
| try |
| { |
| int portNumber = Integer.parseInt(viewPort); |
| viewPortString = ":" + Integer.toString(portNumber); |
| if (!viewProtocol.equals("https")) |
| { |
| if (portNumber == 80) |
| viewPortString = ""; |
| } |
| else |
| { |
| if (portNumber == 443) |
| viewPortString = ""; |
| } |
| } |
| catch (NumberFormatException e) |
| { |
| throw new ManifoldCFException("Bad view port: "+e.getMessage(),e); |
| } |
| |
| if (viewCgiPath == null || viewCgiPath.length() == 0) |
| viewCgiPath = ingestCgiPath; |
| |
| if (ingestNtlmDomain != null && ingestNtlmDomain.length() == 0) |
| ingestNtlmDomain = null; |
| if (ingestNtlmDomain == null) |
| { |
| ingestNtlmUsername = null; |
| ingestNtlmPassword = null; |
| } |
| else |
| { |
| if (ingestNtlmUsername == null || ingestNtlmUsername.length() == 0) |
| { |
| ingestNtlmUsername = serverUsername; |
| if (ingestNtlmPassword == null || ingestNtlmPassword.length() == 0) |
| ingestNtlmPassword = serverPassword; |
| } |
| else |
| { |
| if (ingestNtlmPassword == null) |
| ingestNtlmPassword = ""; |
| } |
| } |
| |
| // Set up ingest ssl if indicated |
| String ingestKeystoreData = params.getParameter(LiveLinkParameters.ingestKeystore); |
| if (ingestKeystoreData != null) |
| ingestKeystoreManager = KeystoreManagerFactory.make("",ingestKeystoreData); |
| |
| |
| // Server parameter processing |
| |
| if (serverProtocol == null || serverProtocol.length() == 0) |
| serverProtocol = "internal"; |
| |
| if (serverPortString == null) |
| serverPort = 2099; |
| else |
| serverPort = new Integer(serverPortString).intValue(); |
| |
| if (serverHTTPNTLMDomain != null && serverHTTPNTLMDomain.length() == 0) |
| serverHTTPNTLMDomain = null; |
| if (serverHTTPNTLMUsername == null || serverHTTPNTLMUsername.length() == 0) |
| { |
| serverHTTPNTLMUsername = null; |
| serverHTTPNTLMPassword = null; |
| } |
| |
| // Set up server ssl if indicated |
| String serverHTTPSKeystoreData = params.getParameter(LiveLinkParameters.serverHTTPSKeystore); |
| if (serverHTTPSKeystoreData != null) |
| serverHTTPSKeystore = KeystoreManagerFactory.make("",serverHTTPSKeystoreData); |
| |
| // View parameters |
| if (viewServerName == null || viewServerName.length() == 0) |
| viewServerName = serverName; |
| |
| viewBasePath = viewProtocol+"://"+viewServerName+viewPortString+viewCgiPath; |
| |
| hasSessionParameters = true; |
| } |
| } |
| |
| protected void getSession() |
| throws ManifoldCFException, ServiceInterruption |
| { |
| getSessionParameters(); |
| if (hasConnected == false) |
| { |
| int socketTimeout = 900000; |
| int connectionTimeout = 300000; |
| |
| // Set up connection manager |
| connectionManager = new PoolingHttpClientConnectionManager(); |
| |
| CredentialsProvider credentialsProvider = new BasicCredentialsProvider(); |
| |
| // Set up ingest ssl if indicated |
| SSLConnectionSocketFactory myFactory = null; |
| if (ingestKeystoreManager != null) |
| { |
| myFactory = new SSLConnectionSocketFactory(new InterruptibleSocketFactory(ingestKeystoreManager.getSecureSocketFactory(), connectionTimeout), |
| new BrowserCompatHostnameVerifier()); |
| } |
| |
| // Set up authentication to use |
| if (ingestNtlmDomain != null) |
| { |
| credentialsProvider.setCredentials(AuthScope.ANY, |
| new NTCredentials(ingestNtlmUsername,ingestNtlmPassword,currentHost,ingestNtlmDomain)); |
| } |
| |
| HttpClientBuilder builder = HttpClients.custom() |
| .setConnectionManager(connectionManager) |
| .setMaxConnTotal(1) |
| .disableAutomaticRetries() |
| .setDefaultRequestConfig(RequestConfig.custom() |
| .setCircularRedirectsAllowed(true) |
| .setSocketTimeout(socketTimeout) |
| .setStaleConnectionCheckEnabled(true) |
| .setExpectContinueEnabled(true) |
| .setConnectTimeout(connectionTimeout) |
| .setConnectionRequestTimeout(socketTimeout) |
| .build()) |
| .setDefaultSocketConfig(SocketConfig.custom() |
| .setTcpNoDelay(true) |
| .setSoTimeout(socketTimeout) |
| .build()) |
| .setDefaultCredentialsProvider(credentialsProvider) |
| .setRequestExecutor(new HttpRequestExecutor(socketTimeout)) |
| .setRedirectStrategy(new DefaultRedirectStrategy()); |
| |
| if (myFactory != null) |
| builder.setSSLSocketFactory(myFactory); |
| |
| httpClient = builder.build(); |
| |
| // System.out.println("Connection server object = "+llServer.toString()); |
| |
| // Establish the actual connection |
| int sanityRetryCount = FAILURE_RETRY_COUNT; |
| while (true) |
| { |
| GetSessionThread t = new GetSessionThread(); |
| try |
| { |
| t.start(); |
| t.finishUp(); |
| hasConnected = true; |
| break; |
| } |
| catch (InterruptedException e) |
| { |
| t.interrupt(); |
| throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED); |
| } |
| catch (RuntimeException e2) |
| { |
| sanityRetryCount = handleLivelinkRuntimeException(e2,sanityRetryCount,true); |
| } |
| } |
| } |
| expirationTime = System.currentTimeMillis() + expirationInterval; |
| } |
| |
| // All methods below this line will ONLY be called if a connect() call succeeded |
| // on this instance! |
| |
| |
| protected static int executeMethodViaThread(HttpClient client, HttpRequestBase executeMethod) |
| throws InterruptedException, HttpException, IOException |
| { |
| ExecuteMethodThread t = new ExecuteMethodThread(client,executeMethod); |
| t.start(); |
| try |
| { |
| return t.getResponseCode(); |
| } |
| catch (InterruptedException e) |
| { |
| t.interrupt(); |
| throw e; |
| } |
| finally |
| { |
| t.abort(); |
| t.finishUp(); |
| } |
| } |
| |
| /** Check status of connection. |
| */ |
| @Override |
| public String check() |
| throws ManifoldCFException |
| { |
| try |
| { |
| // Destroy saved session setup and repeat it |
| hasConnected = false; |
| getSession(); |
| |
| // Now, set up trial of ingestion connection |
| if (ingestProtocol != null) |
| { |
| String contextMsg = "for document access"; |
| String ingestHttpAddress = ingestCgiPath; |
| |
| HttpClient client = getInitializedClient(contextMsg); |
| HttpGet method = new HttpGet(getHost().toURI() + ingestHttpAddress); |
| method.setHeader(new BasicHeader("Accept","*/*")); |
| try |
| { |
| int statusCode = executeMethodViaThread(client,method); |
| switch (statusCode) |
| { |
| case 502: |
| return "Fetch test had transient 502 error response"; |
| |
| case HttpStatus.SC_UNAUTHORIZED: |
| return "Fetch test returned UNAUTHORIZED (401) response; check the security credentials and configuration"; |
| |
| case HttpStatus.SC_OK: |
| return super.check(); |
| |
| default: |
| return "Fetch test returned an unexpected response code of "+Integer.toString(statusCode); |
| } |
| } |
| catch (InterruptedException e) |
| { |
| throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED); |
| } |
| catch (java.net.SocketTimeoutException e) |
| { |
| return "Fetch test timed out reading from the Livelink HTTP Server: "+e.getMessage(); |
| } |
| catch (java.net.SocketException e) |
| { |
| return "Fetch test received a socket error reading from Livelink HTTP Server: "+e.getMessage(); |
| } |
| catch (javax.net.ssl.SSLHandshakeException e) |
| { |
| return "Fetch test was unable to set up a SSL connection to Livelink HTTP Server: "+e.getMessage(); |
| } |
| catch (ConnectTimeoutException e) |
| { |
| return "Fetch test connection timed out reading from Livelink HTTP Server: "+e.getMessage(); |
| } |
| catch (InterruptedIOException e) |
| { |
| throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED); |
| } |
| catch (HttpException e) |
| { |
| return "Fetch test had an HTTP exception: "+e.getMessage(); |
| } |
| catch (IOException e) |
| { |
| return "Fetch test had an IO failure: "+e.getMessage(); |
| } |
| } |
| else |
| return super.check(); |
| } |
| catch (ServiceInterruption e) |
| { |
| return "Transient error: "+e.getMessage(); |
| } |
| catch (ManifoldCFException e) |
| { |
| if (e.getErrorCode() == ManifoldCFException.INTERRUPTED) |
| throw e; |
| return "Error: "+e.getMessage(); |
| } |
| } |
| |
| /** This method is periodically called for all connectors that are connected but not |
| * in active use. |
| */ |
| @Override |
| public void poll() |
| throws ManifoldCFException |
| { |
| if (!hasConnected) |
| return; |
| |
| long currentTime = System.currentTimeMillis(); |
| if (currentTime >= expirationTime) |
| { |
| hasConnected = false; |
| expirationTime = -1L; |
| |
| // Shutdown livelink connection |
| if (llServer != null) |
| { |
| llServer.disconnect(); |
| llServer = null; |
| } |
| |
| // Shutdown pool |
| if (connectionManager != null) |
| { |
| connectionManager.shutdown(); |
| connectionManager = null; |
| } |
| } |
| } |
| |
| /** This method is called to assess whether to count this connector instance should |
| * actually be counted as being connected. |
| *@return true if the connector instance is actually connected. |
| */ |
| @Override |
| public boolean isConnected() |
| { |
| return hasConnected; |
| } |
| |
| /** Close the connection. Call this before discarding the repository connector. |
| */ |
| @Override |
| public void disconnect() |
| throws ManifoldCFException |
| { |
| hasSessionParameters = false; |
| hasConnected = false; |
| expirationTime = -1L; |
| if (llServer != null) |
| { |
| llServer.disconnect(); |
| llServer = null; |
| } |
| LLDocs = null; |
| LLAttributes = null; |
| ingestKeystoreManager = null; |
| ingestPortNumber = -1; |
| |
| serverProtocol = null; |
| serverName = null; |
| serverPort = -1; |
| serverUsername = null; |
| serverPassword = null; |
| serverHTTPCgi = null; |
| serverHTTPNTLMDomain = null; |
| serverHTTPNTLMUsername = null; |
| serverHTTPNTLMPassword = null; |
| serverHTTPSKeystore = null; |
| |
| ingestPort = null; |
| ingestProtocol = null; |
| ingestCgiPath = null; |
| |
| viewPort = null; |
| viewServerName = null; |
| viewProtocol = null; |
| viewCgiPath = null; |
| |
| viewBasePath = null; |
| |
| ingestNtlmDomain = null; |
| ingestNtlmUsername = null; |
| ingestNtlmPassword = null; |
| |
| if (connectionManager != null) |
| { |
| connectionManager.shutdown(); |
| connectionManager = null; |
| } |
| |
| super.disconnect(); |
| } |
| |
| /** List the activities we might report on. |
| */ |
| @Override |
| public String[] getActivitiesList() |
| { |
| return activitiesList; |
| } |
| |
| /** Convert a document identifier to a relative URI to read data from. This is not the search URI; that's constructed |
| * by a different method. |
| *@param documentIdentifier is the document identifier. |
| *@return the relative document uri. |
| */ |
| protected String convertToIngestURI(String documentIdentifier) |
| throws ManifoldCFException |
| { |
| // The document identifier is the string form of the object ID for this connector. |
| if (!documentIdentifier.startsWith("D")) |
| return null; |
| int colonPosition = documentIdentifier.indexOf(":",1); |
| if (colonPosition == -1) |
| return ingestCgiPath+"?func=ll&objID="+documentIdentifier.substring(1)+"&objAction=download"; |
| else |
| return ingestCgiPath+"?func=ll&objID="+documentIdentifier.substring(colonPosition+1)+"&objAction=download"; |
| } |
| |
| /** Convert a document identifier to a URI to view. The URI is the URI that will be the unique key from |
| * the search index, and will be presented to the user as part of the search results. It must therefore |
| * be a unique way of describing the document. |
| *@param documentIdentifier is the document identifier. |
| *@return the document uri. |
| */ |
| protected String convertToViewURI(String documentIdentifier) |
| throws ManifoldCFException |
| { |
| // The document identifier is the string form of the object ID for this connector. |
| if (!documentIdentifier.startsWith("D")) |
| return null; |
| int colonPosition = documentIdentifier.indexOf(":",1); |
| if (colonPosition == -1) |
| return viewBasePath+"?func=ll&objID="+documentIdentifier.substring(1)+"&objAction=download"; |
| else |
| return viewBasePath+"?func=ll&objID="+documentIdentifier.substring(colonPosition+1)+"&objAction=download"; |
| } |
| |
| /** Request arbitrary connector information. |
| * This method is called directly from the API in order to allow API users to perform any one of several connector-specific |
| * queries. |
| *@param output is the response object, to be filled in by this method. |
| *@param command is the command, which is taken directly from the API request. |
| *@return true if the resource is found, false if not. In either case, output may be filled in. |
| */ |
| @Override |
| public boolean requestInfo(Configuration output, String command) |
| throws ManifoldCFException |
| { |
| if (command.equals("workspaces")) |
| { |
| try |
| { |
| String[] workspaces = getWorkspaceNames(); |
| int i = 0; |
| while (i < workspaces.length) |
| { |
| String workspace = workspaces[i++]; |
| ConfigurationNode node = new ConfigurationNode("workspace"); |
| node.setValue(workspace); |
| output.addChild(output.getChildCount(),node); |
| } |
| } |
| catch (ServiceInterruption e) |
| { |
| ManifoldCF.createServiceInterruptionNode(output,e); |
| } |
| catch (ManifoldCFException e) |
| { |
| ManifoldCF.createErrorNode(output,e); |
| } |
| } |
| else if (command.startsWith("folders/")) |
| { |
| String path = command.substring("folders/".length()); |
| |
| try |
| { |
| String[] folders = getChildFolderNames(path); |
| int i = 0; |
| while (i < folders.length) |
| { |
| String folder = folders[i++]; |
| ConfigurationNode node = new ConfigurationNode("folder"); |
| node.setValue(folder); |
| output.addChild(output.getChildCount(),node); |
| } |
| } |
| catch (ServiceInterruption e) |
| { |
| ManifoldCF.createServiceInterruptionNode(output,e); |
| } |
| catch (ManifoldCFException e) |
| { |
| ManifoldCF.createErrorNode(output,e); |
| } |
| } |
| else if (command.startsWith("categories/")) |
| { |
| String path = command.substring("categories/".length()); |
| |
| try |
| { |
| String[] categories = getChildCategoryNames(path); |
| int i = 0; |
| while (i < categories.length) |
| { |
| String category = categories[i++]; |
| ConfigurationNode node = new ConfigurationNode("category"); |
| node.setValue(category); |
| output.addChild(output.getChildCount(),node); |
| } |
| } |
| catch (ServiceInterruption e) |
| { |
| ManifoldCF.createServiceInterruptionNode(output,e); |
| } |
| catch (ManifoldCFException e) |
| { |
| ManifoldCF.createErrorNode(output,e); |
| } |
| |
| } |
| else if (command.startsWith("categoryattributes/")) |
| { |
| String path = command.substring("categoryattributes/".length()); |
| |
| try |
| { |
| String[] attributes = getCategoryAttributes(path); |
| int i = 0; |
| while (i < attributes.length) |
| { |
| String attribute = attributes[i++]; |
| ConfigurationNode node = new ConfigurationNode("attribute"); |
| node.setValue(attribute); |
| output.addChild(output.getChildCount(),node); |
| } |
| } |
| catch (ServiceInterruption e) |
| { |
| ManifoldCF.createServiceInterruptionNode(output,e); |
| } |
| catch (ManifoldCFException e) |
| { |
| ManifoldCF.createErrorNode(output,e); |
| } |
| } |
| else |
| return super.requestInfo(output,command); |
| return true; |
| } |
| |
| /** 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. |
| *@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. |
| */ |
| @Override |
| public void addSeedDocuments(ISeedingActivity activities, DocumentSpecification spec, |
| long startTime, long endTime) |
| throws ManifoldCFException, ServiceInterruption |
| { |
| getSession(); |
| LivelinkContext llc = new LivelinkContext(); |
| |
| // First, grab the root LLValue |
| ObjectInformation rootValue = llc.getObjectInformation(LLENTWK_VOL,LLENTWK_ID); |
| if (!rootValue.exists()) |
| { |
| // If we get here, it HAS to be a bad network/transient problem. |
| Logging.connectors.warn("Livelink: Could not look up root workspace object during seeding! Retrying -"); |
| throw new ServiceInterruption("Service interruption during seeding",new ManifoldCFException("Could not looking root workspace object during seeding"),System.currentTimeMillis()+60000L, |
| System.currentTimeMillis()+600000L,-1,true); |
| } |
| |
| // Walk the specification for the "startpoint" types. Amalgamate these into a list of strings. |
| // Presume that all roots are startpoint nodes |
| boolean doUserWorkspaces = false; |
| for (int i = 0; i < spec.getChildCount(); i++) |
| { |
| SpecificationNode n = spec.getChild(i); |
| if (n.getType().equals("startpoint")) |
| { |
| // The id returned is simply the node path, which can't be messed up |
| long beginTime = System.currentTimeMillis(); |
| String path = n.getAttributeValue("path"); |
| VolumeAndId vaf = rootValue.getPathId(path); |
| if (vaf != null) |
| { |
| activities.recordActivity(new Long(beginTime),ACTIVITY_SEED,null, |
| path,"OK",null,null); |
| |
| String newID = "F" + new Integer(vaf.getVolumeID()).toString()+":"+ new Integer(vaf.getPathId()).toString(); |
| activities.addSeedDocument(newID); |
| if (Logging.connectors.isDebugEnabled()) |
| Logging.connectors.debug("Livelink: Seed = '"+newID+"'"); |
| } |
| else |
| { |
| activities.recordActivity(new Long(beginTime),ACTIVITY_SEED,null, |
| path,"NOT FOUND",null,null); |
| } |
| } |
| else if (n.getType().equals("userworkspace")) |
| { |
| String value = n.getAttributeValue("value"); |
| if (value != null && value.equals("true")) |
| doUserWorkspaces = true; |
| else if (value != null && value.equals("false")) |
| doUserWorkspaces = false; |
| } |
| |
| if (doUserWorkspaces) |
| { |
| // Do ListUsers and enumerate the values. |
| int sanityRetryCount = FAILURE_RETRY_COUNT; |
| while (true) |
| { |
| ListUsersThread t = new ListUsersThread(); |
| try |
| { |
| t.start(); |
| LLValue childrenDocs; |
| try |
| { |
| childrenDocs = t.finishUp(); |
| } |
| catch (ManifoldCFException e) |
| { |
| sanityRetryCount = assessRetry(sanityRetryCount,e); |
| continue; |
| } |
| |
| int size = 0; |
| |
| if (childrenDocs.isRecord()) |
| size = 1; |
| if (childrenDocs.isTable()) |
| size = childrenDocs.size(); |
| |
| // Do the scan |
| for (int j = 0; j < size; j++) |
| { |
| int childID = childrenDocs.toInteger(j, "ID"); |
| |
| // Skip admin user |
| if (childID == 1000 || childID == 1001) |
| continue; |
| |
| if (Logging.connectors.isDebugEnabled()) |
| Logging.connectors.debug("Livelink: Found a user: ID="+Integer.toString(childID)); |
| |
| activities.addSeedDocument("F0:"+Integer.toString(childID)); |
| } |
| break; |
| } |
| catch (InterruptedException e) |
| { |
| t.interrupt(); |
| throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED); |
| } |
| catch (RuntimeException e) |
| { |
| sanityRetryCount = handleLivelinkRuntimeException(e,sanityRetryCount,true); |
| continue; |
| } |
| } |
| } |
| |
| } |
| |
| } |
| |
| |
| /** 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(); |
| |
| // Initialize a "livelink context", to minimize the number of objects we have to fetch |
| LivelinkContext llc = new LivelinkContext(); |
| |
| // First, process the spec to get the string we tack on |
| |
| // Read the forced acls. A null return indicates that security is disabled!!! |
| // A zero-length return indicates that the native acls should be used. |
| // All of this is germane to how we ingest the document, so we need to note it in |
| // the version string completely. |
| String[] acls = getAcls(spec); |
| // Sort it, in case it is needed. |
| if (acls != null) |
| java.util.Arrays.sort(acls); |
| |
| // Look at the metadata attributes. |
| // So that the version strings are comparable, we will put them in an array first, and sort them. |
| String pathAttributeName = null; |
| String pathSeparator = null; |
| Set<String> holder = new HashSet<String>(); |
| MatchMap matchMap = new MatchMap(); |
| boolean includeAllMetadata = false; |
| int i = 0; |
| while (i < spec.getChildCount()) |
| { |
| SpecificationNode n = spec.getChild(i++); |
| if (n.getType().equals("allmetadata")) |
| { |
| String isAll = n.getAttributeValue("all"); |
| if (isAll != null && isAll.equals("true")) |
| includeAllMetadata = true; |
| } |
| else if (n.getType().equals("metadata")) |
| { |
| String category = n.getAttributeValue("category"); |
| String attributeName = n.getAttributeValue("attribute"); |
| String isAll = n.getAttributeValue("all"); |
| if (isAll != null && isAll.equals("true")) |
| { |
| // Locate all metadata items for the specified category path, |
| // and enter them into the array |
| String[] attrs = getCategoryAttributes(llc,category); |
| if (attrs != null) |
| { |
| int j = 0; |
| while (j < attrs.length) |
| { |
| attributeName = attrs[j++]; |
| String metadataName = packCategoryAttribute(category,attributeName); |
| holder.add(metadataName); |
| } |
| } |
| } |
| else |
| { |
| String metadataName = packCategoryAttribute(category,attributeName); |
| holder.add(metadataName); |
| } |
| } |
| else if (n.getType().equals("pathnameattribute")) |
| { |
| pathAttributeName = n.getAttributeValue("value"); |
| pathSeparator = n.getAttributeValue("separator"); |
| if (pathSeparator == null) |
| pathSeparator = "/"; |
| } |
| else if (n.getType().equals("pathmap")) |
| { |
| // Path mapping info also needs to be looked at, because it affects what is |
| // ingested. |
| String pathMatch = n.getAttributeValue("match"); |
| String pathReplace = n.getAttributeValue("replace"); |
| matchMap.appendMatchPair(pathMatch,pathReplace); |
| } |
| |
| } |
| |
| // Prepare the specified metadata |
| StringBuilder metadataString = null; |
| CategoryPathAccumulator catAccum = null; |
| if (!includeAllMetadata) |
| { |
| metadataString = new StringBuilder(); |
| // Put into an array |
| String[] sortArray = new String[holder.size()]; |
| i = 0; |
| for (String attrName : holder) |
| { |
| sortArray[i++] = attrName; |
| } |
| |
| // Sort! |
| java.util.Arrays.sort(sortArray); |
| // Build the metadata string piece now |
| packList(metadataString,sortArray,'+'); |
| } |
| else |
| catAccum = new CategoryPathAccumulator(llc); |
| |
| // Calculate the part of the version string that comes from path name and mapping. |
| // This starts with = since ; is used by another optional component (the forced acls) |
| StringBuilder pathNameAttributeVersion = new StringBuilder(); |
| if (pathAttributeName != null) |
| pathNameAttributeVersion.append("=").append(pathAttributeName).append(":").append(pathSeparator).append(":").append(matchMap); |
| |
| // The version string includes the following: |
| // 1) The modify date for the document |
| // 2) The rights for the document, ordered (which can change without changing the ModifyDate field) |
| // 3) The requested metadata fields (category and attribute, ordered) for the document |
| // |
| // The document identifiers are object id's. |
| |
| String[] rval = new String[documentIdentifiers.length]; |
| i = 0; |
| while (i < documentIdentifiers.length) |
| { |
| // Since each livelink access is time-consuming, be sure that we abort if the job has gone inactive |
| activities.checkJobStillActive(); |
| |
| // Read the document or folder metadata, which includes the ModifyDate |
| String docID = documentIdentifiers[i]; |
| |
| boolean isFolder = docID.startsWith("F"); |
| |
| int colonPos = docID.indexOf(":",1); |
| |
| int objID; |
| int vol; |
| |
| if (colonPos == -1) |
| { |
| objID = new Integer(docID.substring(1)).intValue(); |
| vol = LLENTWK_VOL; |
| } |
| else |
| { |
| objID = new Integer(docID.substring(colonPos+1)).intValue(); |
| vol = new Integer(docID.substring(1,colonPos)).intValue(); |
| } |
| |
| rval[i] = null; |
| ObjectInformation value = llc.getObjectInformation(vol,objID); |
| if (value.exists()) |
| { |
| // Make sure we have permission to see the object's contents |
| int permissions = value.getPermissions().intValue(); |
| if ((permissions & LAPI_DOCUMENTS.PERM_SEECONTENTS) != 0) |
| { |
| Date dt = value.getModifyDate(); |
| // The rights don't change when the object changes, so we have to include those too. |
| int[] rights = getObjectRights(vol,objID); |
| if (rights != null) |
| { |
| // We were able to get rights, so object still exists. |
| |
| // I rearranged this on 11/7/2006 so that it would be more parseable, since |
| // we want to pull the transient information out of the version string where |
| // possible (so there is no mismatch between version checking and ingestion). |
| |
| StringBuilder sb = new StringBuilder(); |
| |
| // On 1/17/2008 I changed the version generation code to NOT include metadata, view info, etc. for folders, since |
| // folders make absolutely no use of this info. |
| if (!isFolder) |
| { |
| if (includeAllMetadata) |
| { |
| // Find all the metadata associated with this object, and then |
| // find the set of category pathnames that correspond to it. |
| int[] catIDs = getObjectCategoryIDs(vol,objID); |
| String[] categoryPaths = catAccum.getCategoryPathsAttributeNames(catIDs); |
| // Sort! |
| java.util.Arrays.sort(categoryPaths); |
| // Build the metadata string piece now |
| packList(sb,categoryPaths,'+'); |
| } |
| else |
| sb.append(metadataString); |
| } |
| |
| String[] actualAcls; |
| String denyAcl; |
| if (acls != null && acls.length == 0) |
| { |
| // No forced acls. Read the actual acls from livelink, as a set of rights. |
| // We need also to add in support for the special rights objects. These are: |
| // -1: RIGHT_WORLD |
| // -2: RIGHT_SYSTEM |
| // -3: RIGHT_OWNER |
| // -4: RIGHT_GROUP |
| // |
| // RIGHT_WORLD means guest access. |
| // RIGHT_SYSTEM is "Public Access". |
| // RIGHT_OWNER is access by the owner of the object. |
| // RIGHT_GROUP is access by a member of the base group containing the owner |
| // |
| // These objects are returned by the GetObjectRights() call made above, and NOT |
| // returned by LLUser.ListObjects(). We have to figure out how to map these to |
| // things that are |
| // the equivalent of acls. |
| |
| actualAcls = lookupTokens(rights, value); |
| java.util.Arrays.sort(actualAcls); |
| // If security is on, no deny acl is needed for the local authority, since the repository does not support "deny". But this was added |
| // to be really really really sure. |
| denyAcl = denyToken; |
| |
| } |
| else if (acls != null && acls.length > 0) |
| { |
| // Forced acls |
| actualAcls = acls; |
| denyAcl = defaultAuthorityDenyToken; |
| } |
| else |
| { |
| // Security is OFF |
| actualAcls = acls; |
| denyAcl = null; |
| } |
| |
| // Now encode the acls. If null, we write a special value. |
| if (actualAcls == null) |
| sb.append('-'); |
| else |
| { |
| sb.append('+'); |
| packList(sb,actualAcls,'+'); |
| // This was added on 4/21/2008 to support forced acls working with the global default authority. |
| pack(sb,denyAcl,'+'); |
| } |
| |
| // The date does not need to be parseable |
| sb.append(dt.toString()); |
| |
| if (!isFolder) |
| { |
| // PathNameAttributeVersion comes completely from the spec, so we don't |
| // have to worry about it changing. No need, therefore, to parse it during |
| // processDocuments. |
| sb.append("=").append(pathNameAttributeVersion); |
| |
| // Tack on ingestCgiPath, to insulate us against changes to the repository connection setup. Added 9/7/07. |
| sb.append("_").append(viewBasePath); |
| } |
| |
| rval[i] = sb.toString(); |
| if (Logging.connectors.isDebugEnabled()) |
| Logging.connectors.debug("Livelink: Successfully calculated version string for object "+Integer.toString(vol)+":"+Integer.toString(objID)+" : '"+rval[i]+"'"); |
| } |
| else |
| { |
| if (Logging.connectors.isDebugEnabled()) |
| Logging.connectors.debug("Livelink: Could not get rights for object "+Integer.toString(vol)+":"+Integer.toString(objID)+" - deleting"); |
| } |
| } |
| else |
| { |
| if (Logging.connectors.isDebugEnabled()) |
| Logging.connectors.debug("Livelink: Crawl user cannot see contents of object "+Integer.toString(vol)+":"+Integer.toString(objID)+" - deleting"); |
| } |
| } |
| else |
| { |
| if (Logging.connectors.isDebugEnabled()) |
| Logging.connectors.debug("Livelink: Object "+Integer.toString(vol)+":"+Integer.toString(objID)+" has no information - deleting"); |
| } |
| i++; |
| |
| } |
| return rval; |
| } |
| |
| protected class ListObjectsThread extends Thread |
| { |
| protected final int vol; |
| protected final int objID; |
| protected final String filterString; |
| protected Throwable exception = null; |
| protected LLValue rval = null; |
| |
| public ListObjectsThread(int vol, int objID, String filterString) |
| { |
| super(); |
| setDaemon(true); |
| this.vol = vol; |
| this.objID = objID; |
| this.filterString = filterString; |
| } |
| |
| public void run() |
| { |
| try |
| { |
| LLValue childrenDocs = new LLValue(); |
| int status = LLDocs.ListObjects(vol, objID, null, filterString, LAPI_DOCUMENTS.PERM_SEECONTENTS, childrenDocs); |
| if (status != 0) |
| { |
| throw new ManifoldCFException("Error retrieving contents of folder "+Integer.toString(vol)+":"+Integer.toString(objID)+" : Status="+Integer.toString(status)+" ("+llServer.getErrors()+")"); |
| } |
| rval = childrenDocs; |
| } |
| catch (Throwable e) |
| { |
| this.exception = e; |
| } |
| } |
| |
| public LLValue finishUp() |
| throws ManifoldCFException, InterruptedException |
| { |
| join(); |
| Throwable thr = exception; |
| if (thr != null) |
| { |
| if (thr instanceof RuntimeException) |
| throw (RuntimeException)thr; |
| else if (thr instanceof ManifoldCFException) |
| throw (ManifoldCFException)thr; |
| else if (thr instanceof Error) |
| throw (Error)thr; |
| else |
| throw new RuntimeException("Unrecognized exception type: "+thr.getClass().getName()+": "+thr.getMessage(),thr); |
| } |
| return rval; |
| } |
| } |
| |
| /** 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 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(); |
| |
| // Initialize livelink context |
| LivelinkContext llc = new LivelinkContext(); |
| |
| // First, initialize the table of catid's. |
| // Keeping this around will allow us to benefit from batching of documents. |
| MetadataDescription desc = new MetadataDescription(llc); |
| |
| // Build the node/path cache |
| SystemMetadataDescription sDesc = new SystemMetadataDescription(llc,spec); |
| |
| int i = 0; |
| while (i < documentIdentifiers.length) |
| { |
| // Since each livelink access is time-consuming, be sure that we abort if the job has gone inactive |
| activities.checkJobStillActive(); |
| String documentIdentifier = documentIdentifiers[i]; |
| boolean doScanOnly = scanOnly[i]; |
| |
| boolean isFolder = documentIdentifier.startsWith("F"); |
| int colonPosition = documentIdentifier.indexOf(":",1); |
| int vol; |
| int objID; |
| if (colonPosition == -1) |
| { |
| vol = LLENTWK_VOL; |
| objID = new Integer(documentIdentifier.substring(1)).intValue(); |
| } |
| else |
| { |
| vol = new Integer(documentIdentifier.substring(1,colonPosition)).intValue(); |
| objID = new Integer(documentIdentifier.substring(colonPosition+1)).intValue(); |
| } |
| |
| if (isFolder) |
| { |
| if (Logging.connectors.isDebugEnabled()) |
| Logging.connectors.debug("Livelink: Processing folder "+Integer.toString(vol)+":"+Integer.toString(objID)); |
| |
| // Since the identifier indicates it is a directory, then queue up all the current children which pass the filter. |
| String filterString = buildFilterString(spec); |
| |
| int sanityRetryCount = FAILURE_RETRY_COUNT; |
| while (true) |
| { |
| ListObjectsThread t = new ListObjectsThread(vol,objID,filterString); |
| try |
| { |
| t.start(); |
| LLValue childrenDocs; |
| try |
| { |
| childrenDocs = t.finishUp(); |
| } |
| catch (ManifoldCFException e) |
| { |
| sanityRetryCount = assessRetry(sanityRetryCount,e); |
| continue; |
| } |
| |
| int size = 0; |
| |
| if (childrenDocs.isRecord()) |
| size = 1; |
| if (childrenDocs.isTable()) |
| size = childrenDocs.size(); |
| |
| // System.out.println("Total child count = "+Integer.toString(size)); |
| |
| // Do the scan |
| int j = 0; |
| while (j < size) |
| { |
| int childID = childrenDocs.toInteger(j, "ID"); |
| |
| if (Logging.connectors.isDebugEnabled()) |
| Logging.connectors.debug("Livelink: Found a child of folder "+Integer.toString(vol)+":"+Integer.toString(objID)+" : ID="+Integer.toString(childID)); |
| |
| int subtype = childrenDocs.toInteger(j, "SubType"); |
| boolean childIsFolder = (subtype == LAPI_DOCUMENTS.FOLDERSUBTYPE || subtype == LAPI_DOCUMENTS.PROJECTSUBTYPE || |
| subtype == LAPI_DOCUMENTS.COMPOUNDDOCUMENTSUBTYPE); |
| |
| // If it's a folder, we just let it through for now |
| if (!childIsFolder && checkInclude(childrenDocs.toString(j,"Name") + "." + childrenDocs.toString(j,"FileType"), spec) == false) |
| { |
| if (Logging.connectors.isDebugEnabled()) |
| Logging.connectors.debug("Livelink: Child identifier "+Integer.toString(childID)+" was excluded by inclusion criteria"); |
| j++; |
| continue; |
| } |
| |
| if (childIsFolder) |
| { |
| if (Logging.connectors.isDebugEnabled()) |
| Logging.connectors.debug("Livelink: Child identifier "+Integer.toString(childID)+" is a folder, project, or compound document; adding a reference"); |
| if (subtype == LAPI_DOCUMENTS.PROJECTSUBTYPE) |
| { |
| // If we pick up a project object, we need to describe the volume object (which |
| // will be the root of all documents beneath) |
| activities.addDocumentReference("F"+new Integer(childID).toString()+":"+new Integer(-childID).toString()); |
| } |
| else |
| activities.addDocumentReference("F"+new Integer(vol).toString()+":"+new Integer(childID).toString()); |
| } |
| else |
| { |
| if (Logging.connectors.isDebugEnabled()) |
| Logging.connectors.debug("Livelink: Child identifier "+Integer.toString(childID)+" is a simple document; adding a reference"); |
| |
| activities.addDocumentReference("D"+new Integer(vol).toString()+":"+new Integer(childID).toString()); |
| } |
| |
| j++; |
| } |
| break; |
| } |
| catch (InterruptedException e) |
| { |
| t.interrupt(); |
| throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED); |
| } |
| catch (RuntimeException e) |
| { |
| sanityRetryCount = handleLivelinkRuntimeException(e,sanityRetryCount,true); |
| continue; |
| } |
| } |
| if (Logging.connectors.isDebugEnabled()) |
| Logging.connectors.debug("Livelink: Done processing folder "+Integer.toString(vol)+":"+Integer.toString(objID)); |
| } |
| else |
| { |
| // It's a known file, and we've already checked whether it's allowed or not (except any |
| // checks based on the file data) |
| |
| if (doScanOnly == false) |
| { |
| if (Logging.connectors.isDebugEnabled()) |
| Logging.connectors.debug("Livelink: Processing document "+Integer.toString(vol)+":"+Integer.toString(objID)); |
| if (checkIngest(llc,objID,spec)) |
| { |
| if (Logging.connectors.isDebugEnabled()) |
| Logging.connectors.debug("Livelink: Decided to ingest document "+Integer.toString(vol)+":"+Integer.toString(objID)); |
| |
| // Grab the access tokens for this file from the version string, inside ingest method. |
| ingestFromLiveLink(llc,documentIdentifiers[i],versions[i],activities,desc,sDesc); |
| } |
| else |
| { |
| if (Logging.connectors.isDebugEnabled()) |
| Logging.connectors.debug("Livelink: Decided not to ingest document "+Integer.toString(vol)+":"+Integer.toString(objID)+" - Did not match ingestion criteria"); |
| |
| } |
| if (Logging.connectors.isDebugEnabled()) |
| Logging.connectors.debug("Livelink: Done processing document "+Integer.toString(vol)+":"+Integer.toString(objID)); |
| } |
| } |
| i++; |
| } |
| } |
| |
| /** 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() |
| { |
| // Intrinsically, Livelink doesn't batch well. Multiple chunks have no advantage over one-at-a-time requests, |
| // since apparently the Livelink API does not support multiples. HOWEVER - when metadata is considered, |
| // it becomes worthwhile, because we will be able to do what is needed to look up the correct CATID node |
| // only once per n requests! So it's a tradeoff between the advantage gained by threading, and the |
| // savings gained by CATID lookup. |
| // Note that at Shell, the fact that the network hiccups a lot makes it better to choose a smaller value. |
| return 6; |
| } |
| |
| // 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,"LivelinkConnector.Server")); |
| tabsArray.add(Messages.getString(locale,"LivelinkConnector.DocumentAccess")); |
| tabsArray.add(Messages.getString(locale,"LivelinkConnector.DocumentView")); |
| out.print( |
| "<script type=\"text/javascript\">\n"+ |
| "<!--\n"+ |
| "function ServerDeleteCertificate(aliasName)\n"+ |
| "{\n"+ |
| " editconnection.serverkeystorealias.value = aliasName;\n"+ |
| " editconnection.serverconfigop.value = \"Delete\";\n"+ |
| " postForm();\n"+ |
| "}\n"+ |
| "\n"+ |
| "function ServerAddCertificate()\n"+ |
| "{\n"+ |
| " if (editconnection.servercertificate.value == \"\")\n"+ |
| " {\n"+ |
| " alert(\""+Messages.getBodyJavascriptString(locale,"LivelinkConnector.ChooseACertificateFile")+"\");\n"+ |
| " editconnection.servercertificate.focus();\n"+ |
| " }\n"+ |
| " else\n"+ |
| " {\n"+ |
| " editconnection.serverconfigop.value = \"Add\";\n"+ |
| " postForm();\n"+ |
| " }\n"+ |
| "}\n"+ |
| "\n"+ |
| "function IngestDeleteCertificate(aliasName)\n"+ |
| "{\n"+ |
| " editconnection.ingestkeystorealias.value = aliasName;\n"+ |
| " editconnection.ingestconfigop.value = \"Delete\";\n"+ |
| " postForm();\n"+ |
| "}\n"+ |
| "\n"+ |
| "function IngestAddCertificate()\n"+ |
| "{\n"+ |
| " if (editconnection.ingestcertificate.value == \"\")\n"+ |
| " {\n"+ |
| " alert(\""+Messages.getBodyJavascriptString(locale,"LivelinkConnector.ChooseACertificateFile")+"\");\n"+ |
| " editconnection.ingestcertificate.focus();\n"+ |
| " }\n"+ |
| " else\n"+ |
| " {\n"+ |
| " editconnection.ingestconfigop.value = \"Add\";\n"+ |
| " postForm();\n"+ |
| " }\n"+ |
| "}\n"+ |
| "\n"+ |
| "function checkConfig()\n"+ |
| "{\n"+ |
| " if (editconnection.serverport.value != \"\" && !isInteger(editconnection.serverport.value))\n"+ |
| " {\n"+ |
| " alert(\""+Messages.getBodyJavascriptString(locale,"LivelinkConnector.AValidNumberIsRequired")+"\");\n"+ |
| " editconnection.serverport.focus();\n"+ |
| " return false;\n"+ |
| " }\n"+ |
| " if (editconnection.ingestport.value != \"\" && !isInteger(editconnection.ingestport.value))\n"+ |
| " {\n"+ |
| " alert(\""+Messages.getBodyJavascriptString(locale,"LivelinkConnector.AValidNumberOrBlankIsRequired")+"\");\n"+ |
| " editconnection.ingestport.focus();\n"+ |
| " return false;\n"+ |
| " }\n"+ |
| " if (editconnection.viewport.value != \"\" && !isInteger(editconnection.viewport.value))\n"+ |
| " {\n"+ |
| " alert(\""+Messages.getBodyJavascriptString(locale,"LivelinkConnector.AValidNumberOrBlankIsRequired")+"\");\n"+ |
| " editconnection.viewport.focus();\n"+ |
| " return false;\n"+ |
| " }\n"+ |
| " return true;\n"+ |
| "}\n"+ |
| "\n"+ |
| "function checkConfigForSave()\n"+ |
| "{\n"+ |
| " if (editconnection.servername.value == \"\")\n"+ |
| " {\n"+ |
| " alert(\""+Messages.getBodyJavascriptString(locale,"LivelinkConnector.EnterALivelinkServerName")+"\");\n"+ |
| " SelectTab(\"" + Messages.getBodyJavascriptString(locale,"LivelinkConnector.Server") + "\");\n"+ |
| " editconnection.servername.focus();\n"+ |
| " return false;\n"+ |
| " }\n"+ |
| " if (editconnection.serverport.value == \"\")\n"+ |
| " {\n"+ |
| " alert(\""+Messages.getBodyJavascriptString(locale,"LivelinkConnector.AServerPortNumberIsRequired")+"\");\n"+ |
| " SelectTab(\"" + Messages.getBodyJavascriptString(locale,"LivelinkConnector.Server") + "\");\n"+ |
| " editconnection.serverport.focus();\n"+ |
| " return false;\n"+ |
| " }\n"+ |
| " if (editconnection.serverhttpcgipath.value == \"\")\n"+ |
| " {\n"+ |
| " alert(\""+Messages.getBodyJavascriptString(locale,"LivelinkConnector.EnterTheServerCgiPathToLivelink")+"\");\n"+ |
| " SelectTab(\"" + Messages.getBodyJavascriptString(locale,"LivelinkConnector.Server") + "\");\n"+ |
| " editconnection.serverhttpcgipath.focus();\n"+ |
| " return false;\n"+ |
| " }\n"+ |
| " if (editconnection.serverhttpcgipath.value.substring(0,1) != \"/\")\n"+ |
| " {\n"+ |
| " alert(\""+Messages.getBodyJavascriptString(locale,"LivelinkConnector.TheServerCgiPathMustBeginWithACharacter")+"\");\n"+ |
| " SelectTab(\"" + Messages.getBodyJavascriptString(locale,"LivelinkConnector.Server") + "\");\n"+ |
| " editconnection.serverhttpcgipath.focus();\n"+ |
| " return false;\n"+ |
| " }\n"+ |
| " if (editconnection.viewprotocol.value == \"\" && editconnection.ingestprotocol.value == \"\")\n"+ |
| " {\n"+ |
| " alert(\""+Messages.getBodyJavascriptString(locale,"LivelinkConnector.SelectAViewProtocol")+"\");\n"+ |
| " SelectTab(\"" + Messages.getBodyJavascriptString(locale,"LivelinkConnector.DocumentView") + "\");\n"+ |
| " editconnection.viewprotocol.focus();\n"+ |
| " return false;\n"+ |
| " }\n"+ |
| " if (editconnection.viewcgipath.value == \"\" && editconnection.ingestcgipath.value == \"\")\n"+ |
| " {\n"+ |
| " alert(\""+Messages.getBodyJavascriptString(locale,"LivelinkConnector.EnterTheViewCgiPathToLivelink")+"\");\n"+ |
| " SelectTab(\"" + Messages.getBodyJavascriptString(locale,"LivelinkConnector.DocumentView") + "\");\n"+ |
| " editconnection.viewcgipath.focus();\n"+ |
| " return false;\n"+ |
| " }\n"+ |
| " if (editconnection.ingestcgipath.value != \"\" && editconnection.ingestcgipath.value.substring(0,1) != \"/\")\n"+ |
| " {\n"+ |
| " alert(\""+Messages.getBodyJavascriptString(locale,"LivelinkConnector.TheIngestCgiPathMustBeBlankOrBeginWithACharacter")+"\");\n"+ |
| " SelectTab(\"" + Messages.getBodyJavascriptString(locale,"LivelinkConnector.DocumentAccess") + "\");\n"+ |
| " editconnection.ingestcgipath.focus();\n"+ |
| " return false;\n"+ |
| " }\n"+ |
| " if (editconnection.viewcgipath.value != \"\" && editconnection.viewcgipath.value.substring(0,1) != \"/\")\n"+ |
| " {\n"+ |
| " alert(\""+Messages.getBodyJavascriptString(locale,"LivelinkConnector.TheViewCgiPathMustBeBlankOrBeginWithACharacter")+"\");\n"+ |
| " SelectTab(\"" + Messages.getBodyJavascriptString(locale,"LivelinkConnector.DocumentView") + "\");\n"+ |
| " editconnection.viewcgipath.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 |
| { |
| |
| // LAPI parameters |
| String serverProtocol = parameters.getParameter(LiveLinkParameters.serverProtocol); |
| if (serverProtocol == null) |
| serverProtocol = "internal"; |
| String serverName = parameters.getParameter(LiveLinkParameters.serverName); |
| if (serverName == null) |
| serverName = "localhost"; |
| String serverPort = parameters.getParameter(LiveLinkParameters.serverPort); |
| if (serverPort == null) |
| serverPort = "2099"; |
| String serverUserName = parameters.getParameter(LiveLinkParameters.serverUsername); |
| if (serverUserName == null) |
| serverUserName = ""; |
| String serverPassword = parameters.getObfuscatedParameter(LiveLinkParameters.serverPassword); |
| if (serverPassword == null) |
| serverPassword = ""; |
| else |
| serverPassword = out.mapPasswordToKey(serverPassword); |
| String serverHTTPCgiPath = parameters.getParameter(LiveLinkParameters.serverHTTPCgiPath); |
| if (serverHTTPCgiPath == null) |
| serverHTTPCgiPath = "/livelink/livelink.exe"; |
| String serverHTTPNTLMDomain = parameters.getParameter(LiveLinkParameters.serverHTTPNTLMDomain); |
| if (serverHTTPNTLMDomain == null) |
| serverHTTPNTLMDomain = ""; |
| String serverHTTPNTLMUserName = parameters.getParameter(LiveLinkParameters.serverHTTPNTLMUsername); |
| if (serverHTTPNTLMUserName == null) |
| serverHTTPNTLMUserName = ""; |
| String serverHTTPNTLMPassword = parameters.getObfuscatedParameter(LiveLinkParameters.serverHTTPNTLMPassword); |
| if (serverHTTPNTLMPassword == null) |
| serverHTTPNTLMPassword = ""; |
| else |
| serverHTTPNTLMPassword = out.mapPasswordToKey(serverHTTPNTLMPassword); |
| String serverHTTPSKeystore = parameters.getParameter(LiveLinkParameters.serverHTTPSKeystore); |
| IKeystoreManager localServerHTTPSKeystore; |
| if (serverHTTPSKeystore == null) |
| localServerHTTPSKeystore = KeystoreManagerFactory.make(""); |
| else |
| localServerHTTPSKeystore = KeystoreManagerFactory.make("",serverHTTPSKeystore); |
| |
| // Document access parameters |
| String ingestProtocol = parameters.getParameter(LiveLinkParameters.ingestProtocol); |
| if (ingestProtocol == null) |
| ingestProtocol = ""; |
| String ingestPort = parameters.getParameter(LiveLinkParameters.ingestPort); |
| if (ingestPort == null) |
| ingestPort = ""; |
| String ingestCgiPath = parameters.getParameter(LiveLinkParameters.ingestCgiPath); |
| if (ingestCgiPath == null) |
| ingestCgiPath = ""; |
| String ingestNtlmUsername = parameters.getParameter(LiveLinkParameters.ingestNtlmUsername); |
| if (ingestNtlmUsername == null) |
| ingestNtlmUsername = ""; |
| String ingestNtlmPassword = parameters.getObfuscatedParameter(LiveLinkParameters.ingestNtlmPassword); |
| if (ingestNtlmPassword == null) |
| ingestNtlmPassword = ""; |
| else |
| ingestNtlmPassword = out.mapPasswordToKey(ingestNtlmPassword); |
| String ingestNtlmDomain = parameters.getParameter(LiveLinkParameters.ingestNtlmDomain); |
| if (ingestNtlmDomain == null) |
| ingestNtlmDomain = ""; |
| String ingestKeystore = parameters.getParameter(LiveLinkParameters.ingestKeystore); |
| IKeystoreManager localIngestKeystore; |
| if (ingestKeystore == null) |
| localIngestKeystore = KeystoreManagerFactory.make(""); |
| else |
| localIngestKeystore = KeystoreManagerFactory.make("",ingestKeystore); |
| |
| // Document view parameters |
| String viewProtocol = parameters.getParameter(LiveLinkParameters.viewProtocol); |
| if (viewProtocol == null) |
| viewProtocol = "http"; |
| String viewServerName = parameters.getParameter(LiveLinkParameters.viewServerName); |
| if (viewServerName == null) |
| viewServerName = ""; |
| String viewPort = parameters.getParameter(LiveLinkParameters.viewPort); |
| if (viewPort == null) |
| viewPort = ""; |
| String viewCgiPath = parameters.getParameter(LiveLinkParameters.viewCgiPath); |
| if (viewCgiPath == null) |
| viewCgiPath = "/livelink/livelink.exe"; |
| |
| // The "Server" tab |
| // Always pass the whole keystore as a hidden. |
| out.print( |
| "<input name=\"serverconfigop\" type=\"hidden\" value=\"Continue\"/>\n" |
| ); |
| if (serverHTTPSKeystore != null) |
| { |
| out.print( |
| "<input type=\"hidden\" name=\"serverhttpskeystoredata\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(serverHTTPSKeystore)+"\"/>\n" |
| ); |
| } |
| if (tabName.equals(Messages.getString(locale,"LivelinkConnector.Server"))) |
| { |
| out.print( |
| "<table class=\"displaytable\">\n"+ |
| " <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n"+ |
| " <tr>\n"+ |
| " <td class=\"description\">"+Messages.getBodyString(locale,"LivelinkConnector.ServerProtocol")+"</td>\n"+ |
| " <td class=\"value\">\n"+ |
| " <select name=\"serverprotocol\" size=\"2\">\n"+ |
| " <option value=\"internal\" "+((serverProtocol.equals("internal"))?"selected=\"selected\"":"")+">"+Messages.getBodyString(locale,"LivelinkConnector.internal")+"</option>\n"+ |
| " <option value=\"http\" "+((serverProtocol.equals("http"))?"selected=\"selected\"":"")+">http</option>\n"+ |
| " <option value=\"https\" "+((serverProtocol.equals("https"))?"selected=\"selected\"":"")+">https</option>\n"+ |
| " </select>\n"+ |
| " </td>\n"+ |
| " </tr>\n"+ |
| " <tr>\n"+ |
| " <td class=\"description\"><nobr>"+Messages.getBodyString(locale,"LivelinkConnector.ServerName")+"</nobr></td>\n"+ |
| " <td class=\"value\"><input type=\"text\" size=\"64\" name=\"servername\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(serverName)+"\"/></td>\n"+ |
| " </tr>\n"+ |
| " <tr>\n"+ |
| " <td class=\"description\"><nobr>"+Messages.getBodyString(locale,"LivelinkConnector.ServerPort")+"</nobr></td>\n"+ |
| " <td class=\"value\"><input type=\"text\" size=\"5\" name=\"serverport\" value=\""+serverPort+"\"/></td>\n"+ |
| " </tr>\n"+ |
| " <tr>\n"+ |
| " <td class=\"description\"><nobr>"+Messages.getBodyString(locale,"LivelinkConnector.ServerUserName")+"</nobr></td>\n"+ |
| " <td class=\"value\"><input type=\"text\" size=\"32\" name=\"serverusername\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(serverUserName)+"\"/></td>\n"+ |
| " </tr>\n"+ |
| " <tr>\n"+ |
| " <td class=\"description\"><nobr>"+Messages.getBodyString(locale,"LivelinkConnector.ServerPassword")+"</nobr></td>\n"+ |
| " <td class=\"value\"><input type=\"password\" size=\"32\" name=\"serverpassword\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(serverPassword)+"\"/></td>\n"+ |
| " </tr>\n"+ |
| " <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n"+ |
| " <tr>\n"+ |
| " <td class=\"description\"><nobr>"+Messages.getBodyString(locale,"LivelinkConnector.ServerHTTPCGIPath")+"</nobr></td>\n"+ |
| " <td class=\"value\"><input type=\"text\" size=\"32\" name=\"serverhttpcgipath\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(serverHTTPCgiPath)+"\"/></td>\n"+ |
| " </tr>\n"+ |
| " <tr>\n"+ |
| " <td class=\"description\"><nobr>"+Messages.getBodyString(locale,"LivelinkConnector.ServerHTTPNTLMDomain")+"</nobr><br/><nobr>"+Messages.getBodyString(locale,"LivelinkConnector.SetIfNTLMAuthDesired")+"</nobr></td>\n"+ |
| " <td class=\"value\">\n"+ |
| " <input type=\"text\" size=\"32\" name=\"serverhttpntlmdomain\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(serverHTTPNTLMDomain)+"\"/>\n"+ |
| " </td>\n"+ |
| " </tr>\n"+ |
| " <tr>\n"+ |
| " <td class=\"description\"><nobr>"+Messages.getBodyString(locale,"LivelinkConnector.ServerHTTPNTLMUserName")+"</nobr></td>\n"+ |
| " <td class=\"value\">\n"+ |
| " <input type=\"text\" size=\"32\" name=\"serverhttpntlmusername\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(serverHTTPNTLMUserName)+"\"/>\n"+ |
| " </td>\n"+ |
| " </tr>\n"+ |
| " <tr>\n"+ |
| " <td class=\"description\"><nobr>"+Messages.getBodyString(locale,"LivelinkConnector.ServerHTTPNTLMPassword")+"</nobr></td>\n"+ |
| " <td class=\"value\">\n"+ |
| " <input type=\"password\" size=\"32\" name=\"serverhttpntlmpassword\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(serverHTTPNTLMPassword)+"\"/>\n"+ |
| " </td>\n"+ |
| " </tr>\n"+ |
| " <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n" |
| ); |
| out.print( |
| " <tr>\n"+ |
| " <td class=\"description\"><nobr>"+Messages.getBodyString(locale,"LivelinkConnector.ServerSSLCertificateList")+"</nobr></td>\n"+ |
| " <td class=\"value\">\n"+ |
| " <input type=\"hidden\" name=\"serverkeystorealias\" value=\"\"/>\n"+ |
| " <table class=\"displaytable\">\n" |
| ); |
| // List the individual certificates in the store, with a delete button for each |
| String[] contents = localServerHTTPSKeystore.getContents(); |
| if (contents.length == 0) |
| { |
| out.print( |
| " <tr><td class=\"message\" colspan=\"2\"><nobr>"+Messages.getBodyString(locale,"LivelinkConnector.NoCertificatesPresent")+"</nobr></td></tr>\n" |
| ); |
| } |
| else |
| { |
| int i = 0; |
| while (i < contents.length) |
| { |
| String alias = contents[i]; |
| String description = localServerHTTPSKeystore.getDescription(alias); |
| if (description.length() > 128) |
| description = description.substring(0,125) + "..."; |
| out.print( |
| " <tr>\n"+ |
| " <td class=\"value\"><input type=\"button\" onclick='Javascript:ServerDeleteCertificate(\""+org.apache.manifoldcf.ui.util.Encoder.attributeJavascriptEscape(alias)+"\")' alt=\""+Messages.getAttributeString(locale,"LivelinkConnector.DeleteCert")+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(alias)+"\" value=\""+Messages.getAttributeString(locale,"LivelinkConnector.Delete")+"\"/></td>\n"+ |
| " <td>"+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(description)+"</td>\n"+ |
| " </tr>\n" |
| ); |
| i++; |
| } |
| } |
| out.print( |
| " </table>\n"+ |
| " <input type=\"button\" onclick='Javascript:ServerAddCertificate()' alt=\""+Messages.getAttributeString(locale,"LivelinkConnector.AddCert")+"\" value=\""+Messages.getAttributeString(locale,"LivelinkConnector.Add")+"\"/> \n"+ |
| " "+Messages.getBodyString(locale,"LivelinkConnector.Certificate")+"<input name=\"servercertificate\" size=\"50\" type=\"file\"/>\n"+ |
| " </td>\n"+ |
| " </tr>\n" |
| ); |
| out.print( |
| "</table>\n" |
| ); |
| } |
| else |
| { |
| // Hiddens for Server tab |
| out.print( |
| "<input type=\"hidden\" name=\"serverprotocol\" value=\""+serverProtocol+"\"/>\n"+ |
| "<input type=\"hidden\" name=\"servername\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(serverName)+"\"/>\n"+ |
| "<input type=\"hidden\" name=\"serverport\" value=\""+serverPort+"\"/>\n"+ |
| "<input type=\"hidden\" name=\"serverusername\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(serverUserName)+"\"/>\n"+ |
| "<input type=\"hidden\" name=\"serverpassword\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(serverPassword)+"\"/>\n"+ |
| "<input type=\"hidden\" name=\"serverhttpcgipath\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(serverHTTPCgiPath)+"\"/>\n"+ |
| "<input type=\"hidden\" name=\"serverhttpntlmdomain\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(serverHTTPNTLMDomain)+"\"/>\n"+ |
| "<input type=\"hidden\" name=\"serverhttpntlmusername\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(serverHTTPNTLMUserName)+"\"/>\n"+ |
| "<input type=\"hidden\" name=\"serverhttpntlmpassword\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(serverHTTPNTLMPassword)+"\"/>\n" |
| ); |
| } |
| |
| // The "Document Access" tab |
| // Always pass the whole keystore as a hidden. |
| out.print( |
| "<input name=\"ingestconfigop\" type=\"hidden\" value=\"Continue\"/>\n" |
| ); |
| if (ingestKeystore != null) |
| { |
| out.print( |
| "<input type=\"hidden\" name=\"ingestkeystoredata\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(ingestKeystore)+"\"/>\n" |
| ); |
| } |
| if (tabName.equals(Messages.getString(locale,"LivelinkConnector.DocumentAccess"))) |
| { |
| out.print( |
| "<table class=\"displaytable\">\n"+ |
| " <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n"+ |
| " <tr>\n"+ |
| " <td class=\"description\">"+Messages.getBodyString(locale,"LivelinkConnector.DocumentFetchProtocol")+"</td>\n"+ |
| " <td class=\"value\">\n"+ |
| " <select name=\"ingestprotocol\" size=\"3\">\n"+ |
| " <option value=\"\" "+((ingestProtocol.equals(""))?"selected=\"selected\"":"")+">"+Messages.getBodyString(locale,"LivelinkConnector.UseLAPI")+"</option>\n"+ |
| " <option value=\"http\" "+((ingestProtocol.equals("http"))?"selected=\"selected\"":"")+">http</option>\n"+ |
| " <option value=\"https\" "+((ingestProtocol.equals("https"))?"selected=\"selected\"":"")+">https</option>\n"+ |
| " </select>\n"+ |
| " </td>\n"+ |
| " </tr>\n"+ |
| " <tr>\n"+ |
| " <td class=\"description\"><nobr>"+Messages.getBodyString(locale,"LivelinkConnector.DocumentFetchPort")+"</nobr></td>\n"+ |
| " <td class=\"value\"><input type=\"text\" size=\"5\" name=\"ingestport\" value=\""+ingestPort+"\"/></td>\n"+ |
| " </tr>\n"+ |
| " <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n"+ |
| " <tr>\n"+ |
| " <td class=\"description\"><nobr>"+Messages.getBodyString(locale,"LivelinkConnector.DocumentFetchCGIPath")+"</nobr></td>\n"+ |
| " <td class=\"value\"><input type=\"text\" size=\"32\" name=\"ingestcgipath\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(ingestCgiPath)+"\"/></td>\n"+ |
| " </tr>\n"+ |
| " <tr>\n"+ |
| " <td class=\"description\"><nobr>"+Messages.getBodyString(locale,"LivelinkConnector.DocumentFetchNTLMDomain")+"</nobr><br/><nobr>"+Messages.getBodyString(locale,"LivelinkConnector.SetIfNTLMAuthDesired")+"</nobr></td>\n"+ |
| " <td class=\"value\">\n"+ |
| " <input type=\"text\" size=\"32\" name=\"ingestntlmdomain\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(ingestNtlmDomain)+"\"/>\n"+ |
| " </td>\n"+ |
| " </tr>\n"+ |
| " <tr>\n"+ |
| " <td class=\"description\"><nobr>"+Messages.getBodyString(locale,"LivelinkConnector.DocumentFetchNTLMUserName")+"</nobr><br/><nobr>"+Messages.getBodyString(locale,"LivelinkConnector.SetIfDifferentFromServerUserName")+"</nobr></td>\n"+ |
| " <td class=\"value\">\n"+ |
| " <input type=\"text\" size=\"32\" name=\"ingestntlmusername\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(ingestNtlmUsername)+"\"/>\n"+ |
| " </td>\n"+ |
| " </tr>\n"+ |
| " <tr>\n"+ |
| " <td class=\"description\"><nobr>"+Messages.getBodyString(locale,"LivelinkConnector.DocumentFetchNTLMPassword")+"</nobr><br/><nobr>"+Messages.getBodyString(locale,"LivelinkConnector.SetIfDifferentFromServerPassword")+"</nobr></td>\n"+ |
| " <td class=\"value\">\n"+ |
| " <input type=\"password\" size=\"32\" name=\"ingestntlmpassword\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(ingestNtlmPassword)+"\"/>\n"+ |
| " </td>\n"+ |
| " </tr>\n"+ |
| " <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n" |
| ); |
| out.print( |
| " <tr>\n"+ |
| " <td class=\"description\"><nobr>"+Messages.getBodyString(locale,"LivelinkConnector.DocumentFetchSSLCertificateList")+"</nobr></td>\n"+ |
| " <td class=\"value\">\n"+ |
| " <input type=\"hidden\" name=\"ingestkeystorealias\" value=\"\"/>\n"+ |
| " <table class=\"displaytable\">\n" |
| ); |
| // List the individual certificates in the store, with a delete button for each |
| String[] contents = localIngestKeystore.getContents(); |
| if (contents.length == 0) |
| { |
| out.print( |
| " <tr><td class=\"message\" colspan=\"2\"><nobr>"+Messages.getBodyString(locale,"LivelinkConnector.NoCertificatesPresent")+"</nobr></td></tr>\n" |
| ); |
| } |
| else |
| { |
| int i = 0; |
| while (i < contents.length) |
| { |
| String alias = contents[i]; |
| String description = localIngestKeystore.getDescription(alias); |
| if (description.length() > 128) |
| description = description.substring(0,125) + "..."; |
| out.print( |
| " <tr>\n"+ |
| " <td class=\"value\"><input type=\"button\" onclick='Javascript:IngestDeleteCertificate(\""+org.apache.manifoldcf.ui.util.Encoder.attributeJavascriptEscape(alias)+"\")' alt=\""+Messages.getAttributeString(locale,"LivelinkConnector.DeleteCert")+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(alias)+"\" value=\""+Messages.getAttributeString(locale,"LivelinkConnector.Delete")+"\"/></td>\n"+ |
| " <td>"+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(description)+"</td>\n"+ |
| " </tr>\n" |
| ); |
| i++; |
| } |
| } |
| out.print( |
| " </table>\n"+ |
| " <input type=\"button\" onclick='Javascript:IngestAddCertificate()' alt=\""+Messages.getAttributeString(locale,"LivelinkConnector.AddCert")+"\" value=\""+Messages.getAttributeString(locale,"LivelinkConnector.Add")+"\"/> \n"+ |
| " "+Messages.getBodyString(locale,"LivelinkConnector.Certificate")+"<input name=\"ingestcertificate\" size=\"50\" type=\"file\"/>\n"+ |
| " </td>\n"+ |
| " </tr>\n" |
| ); |
| out.print( |
| "</table>\n" |
| ); |
| } |
| else |
| { |
| // Hiddens for Document Access tab |
| out.print( |
| "<input type=\"hidden\" name=\"ingestprotocol\" value=\""+ingestProtocol+"\"/>\n"+ |
| "<input type=\"hidden\" name=\"ingestport\" value=\""+ingestPort+"\"/>\n"+ |
| "<input type=\"hidden\" name=\"ingestcgipath\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(ingestCgiPath)+"\"/>\n"+ |
| "<input type=\"hidden\" name=\"ingestntlmusername\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(ingestNtlmUsername)+"\"/>\n"+ |
| "<input type=\"hidden\" name=\"ingestntlmpassword\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(ingestNtlmPassword)+"\"/>\n"+ |
| "<input type=\"hidden\" name=\"ingestntlmdomain\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(ingestNtlmDomain)+"\"/>\n" |
| ); |
| } |
| |
| // Document View tab |
| if (tabName.equals(Messages.getString(locale,"LivelinkConnector.DocumentView"))) |
| { |
| out.print( |
| "<table class=\"displaytable\">\n"+ |
| " <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n"+ |
| " <tr>\n"+ |
| " <td class=\"description\">"+Messages.getBodyString(locale,"LivelinkConnector.DocumentViewProtocol")+"</td>\n"+ |
| " <td class=\"value\">\n"+ |
| " <select name=\"viewprotocol\" size=\"3\">\n"+ |
| " <option value=\"\" "+((viewProtocol.equals(""))?"selected=\"selected\"":"")+">"+Messages.getBodyString(locale,"LivelinkConnector.SameAsFetchProtocol")+"</option>\n"+ |
| " <option value=\"http\" "+((viewProtocol.equals("http"))?"selected=\"selected\"":"")+">http</option>\n"+ |
| " <option value=\"https\" "+((viewProtocol.equals("https"))?"selected=\"selected\"":"")+">https</option>\n"+ |
| " </select>\n"+ |
| " </td>\n"+ |
| " </tr>\n"+ |
| " <tr>\n"+ |
| " <td class=\"description\"><nobr>"+Messages.getBodyString(locale,"LivelinkConnector.DocumentViewServerName")+"</nobr><br/><nobr>"+Messages.getBodyString(locale,"LivelinkConnector.BlankSameAsFetchServer")+"</nobr></td>\n"+ |
| " <td class=\"value\"><input type=\"text\" size=\"64\" name=\"viewservername\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(viewServerName)+"\"/></td>\n"+ |
| " </tr>\n"+ |
| " <tr>\n"+ |
| " <td class=\"description\"><nobr>"+Messages.getBodyString(locale,"LivelinkConnector.DocumentViewPort")+"</nobr><br/><nobr>(blank = same as fetch port)</nobr></td>\n"+ |
| " <td class=\"value\"><input type=\"text\" size=\"5\" name=\"viewport\" value=\""+viewPort+"\"/></td>\n"+ |
| " </tr>\n"+ |
| " <tr>\n"+ |
| " <td class=\"description\"><nobr>"+Messages.getBodyString(locale,"LivelinkConnector.DocumentViewCGIPath")+"</nobr><br/><nobr>"+Messages.getBodyString(locale,"LivelinkConnector.BlankSameAsFetchServer")+"</nobr></td>\n"+ |
| " <td class=\"value\"><input type=\"text\" size=\"32\" name=\"viewcgipath\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(viewCgiPath)+"\"/></td>\n"+ |
| " </tr>\n"+ |
| "</table>\n" |
| ); |
| } |
| else |
| { |
| // Hiddens for Document View tab |
| out.print( |
| "<input type=\"hidden\" name=\"viewprotocol\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(viewProtocol)+"\"/>\n"+ |
| "<input type=\"hidden\" name=\"viewservername\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(viewServerName)+"\"/>\n"+ |
| "<input type=\"hidden\" name=\"viewport\" value=\""+viewPort+"\"/>\n"+ |
| "<input type=\"hidden\" name=\"viewcgipath\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(viewCgiPath)+"\"/>\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 |
| { |
| // View parameters |
| String viewProtocol = variableContext.getParameter("viewprotocol"); |
| if (viewProtocol != null) |
| parameters.setParameter(LiveLinkParameters.viewProtocol,viewProtocol); |
| String viewServerName = variableContext.getParameter("viewservername"); |
| if (viewServerName != null) |
| parameters.setParameter(LiveLinkParameters.viewServerName,viewServerName); |
| String viewPort = variableContext.getParameter("viewport"); |
| if (viewPort != null) |
| parameters.setParameter(LiveLinkParameters.viewPort,viewPort); |
| String viewCgiPath = variableContext.getParameter("viewcgipath"); |
| if (viewCgiPath != null) |
| parameters.setParameter(LiveLinkParameters.viewCgiPath,viewCgiPath); |
| |
| // Server parameters |
| String serverProtocol = variableContext.getParameter("serverprotocol"); |
| if (serverProtocol != null) |
| parameters.setParameter(LiveLinkParameters.serverProtocol,serverProtocol); |
| String serverName = variableContext.getParameter("servername"); |
| if (serverName != null) |
| parameters.setParameter(LiveLinkParameters.serverName,serverName); |
| String serverPort = variableContext.getParameter("serverport"); |
| if (serverPort != null) |
| parameters.setParameter(LiveLinkParameters.serverPort,serverPort); |
| String serverUserName = variableContext.getParameter("serverusername"); |
| if (serverUserName != null) |
| parameters.setParameter(LiveLinkParameters.serverUsername,serverUserName); |
| String serverPassword = variableContext.getParameter("serverpassword"); |
| if (serverPassword != null) |
| parameters.setObfuscatedParameter(LiveLinkParameters.serverPassword,variableContext.mapKeyToPassword(serverPassword)); |
| String serverHTTPCgiPath = variableContext.getParameter("serverhttpcgipath"); |
| if (serverHTTPCgiPath != null) |
| parameters.setParameter(LiveLinkParameters.serverHTTPCgiPath,serverHTTPCgiPath); |
| String serverHTTPNTLMDomain = variableContext.getParameter("serverhttpntlmdomain"); |
| if (serverHTTPNTLMDomain != null) |
| parameters.setParameter(LiveLinkParameters.serverHTTPNTLMDomain,serverHTTPNTLMDomain); |
| String serverHTTPNTLMUserName = variableContext.getParameter("serverhttpntlmusername"); |
| if (serverHTTPNTLMUserName != null) |
| parameters.setParameter(LiveLinkParameters.serverHTTPNTLMUsername,serverHTTPNTLMUserName); |
| String serverHTTPNTLMPassword = variableContext.getParameter("serverhttpntlmpassword"); |
| if (serverHTTPNTLMPassword != null) |
| parameters.setObfuscatedParameter(LiveLinkParameters.serverHTTPNTLMPassword,variableContext.mapKeyToPassword(serverHTTPNTLMPassword)); |
| String serverHTTPSKeystoreValue = variableContext.getParameter("serverhttpskeystoredata"); |
| if (serverHTTPSKeystoreValue != null) |
| parameters.setParameter(LiveLinkParameters.serverHTTPSKeystore,serverHTTPSKeystoreValue); |
| |
| String serverConfigOp = variableContext.getParameter("serverconfigop"); |
| if (serverConfigOp != null) |
| { |
| if (serverConfigOp.equals("Delete")) |
| { |
| String alias = variableContext.getParameter("serverkeystorealias"); |
| serverHTTPSKeystoreValue = parameters.getParameter(LiveLinkParameters.serverHTTPSKeystore); |
| IKeystoreManager mgr; |
| if (serverHTTPSKeystoreValue != null) |
| mgr = KeystoreManagerFactory.make("",serverHTTPSKeystoreValue); |
| else |
| mgr = KeystoreManagerFactory.make(""); |
| mgr.remove(alias); |
| parameters.setParameter(LiveLinkParameters.serverHTTPSKeystore,mgr.getString()); |
| } |
| else if (serverConfigOp.equals("Add")) |
| { |
| String alias = IDFactory.make(threadContext); |
| byte[] certificateValue = variableContext.getBinaryBytes("servercertificate"); |
| serverHTTPSKeystoreValue = parameters.getParameter(LiveLinkParameters.serverHTTPSKeystore); |
| IKeystoreManager mgr; |
| if (serverHTTPSKeystoreValue != null) |
| mgr = KeystoreManagerFactory.make("",serverHTTPSKeystoreValue); |
| else |
| mgr = KeystoreManagerFactory.make(""); |
| java.io.InputStream is = new java.io.ByteArrayInputStream(certificateValue); |
| String certError = null; |
| try |
| { |
| mgr.importCertificate(alias,is); |
| } |
| catch (Throwable e) |
| { |
| certError = e.getMessage(); |
| } |
| finally |
| { |
| try |
| { |
| is.close(); |
| } |
| catch (IOException e) |
| { |
| // Eat this exception |
| } |
| } |
| |
| if (certError != null) |
| { |
| return "Illegal certificate: "+certError; |
| } |
| parameters.setParameter(LiveLinkParameters.serverHTTPSKeystore,mgr.getString()); |
| } |
| } |
| |
| // Ingest parameters |
| String ingestProtocol = variableContext.getParameter("ingestprotocol"); |
| if (ingestProtocol != null) |
| parameters.setParameter(LiveLinkParameters.ingestProtocol,ingestProtocol); |
| String ingestPort = variableContext.getParameter("ingestport"); |
| if (ingestPort != null) |
| parameters.setParameter(LiveLinkParameters.ingestPort,ingestPort); |
| String ingestCgiPath = variableContext.getParameter("ingestcgipath"); |
| if (ingestCgiPath != null) |
| parameters.setParameter(LiveLinkParameters.ingestCgiPath,ingestCgiPath); |
| String ingestNtlmDomain = variableContext.getParameter("ingestntlmdomain"); |
| if (ingestNtlmDomain != null) |
| parameters.setParameter(LiveLinkParameters.ingestNtlmDomain,ingestNtlmDomain); |
| String ingestNtlmUsername = variableContext.getParameter("ingestntlmusername"); |
| if (ingestNtlmUsername != null) |
| parameters.setParameter(LiveLinkParameters.ingestNtlmUsername,ingestNtlmUsername); |
| String ingestNtlmPassword = variableContext.getParameter("ingestntlmpassword"); |
| if (ingestNtlmPassword != null) |
| parameters.setObfuscatedParameter(LiveLinkParameters.ingestNtlmPassword,variableContext.mapKeyToPassword(ingestNtlmPassword)); |
| String ingestKeystoreValue = variableContext.getParameter("ingestkeystoredata"); |
| if (ingestKeystoreValue != null) |
| parameters.setParameter(LiveLinkParameters.ingestKeystore,ingestKeystoreValue); |
| |
| String ingestConfigOp = variableContext.getParameter("ingestconfigop"); |
| if (ingestConfigOp != null) |
| { |
| if (ingestConfigOp.equals("Delete")) |
| { |
| String alias = variableContext.getParameter("ingestkeystorealias"); |
| ingestKeystoreValue = parameters.getParameter(LiveLinkParameters.ingestKeystore); |
| IKeystoreManager mgr; |
| if (ingestKeystoreValue != null) |
| mgr = KeystoreManagerFactory.make("",ingestKeystoreValue); |
| else |
| mgr = KeystoreManagerFactory.make(""); |
| mgr.remove(alias); |
| parameters.setParameter(LiveLinkParameters.ingestKeystore,mgr.getString()); |
| } |
| else if (ingestConfigOp.equals("Add")) |
| { |
| String alias = IDFactory.make(threadContext); |
| byte[] certificateValue = variableContext.getBinaryBytes("ingestcertificate"); |
| ingestKeystoreValue = parameters.getParameter(LiveLinkParameters.ingestKeystore); |
| IKeystoreManager mgr; |
| if (ingestKeystoreValue != null) |
| mgr = KeystoreManagerFactory.make("",ingestKeystoreValue); |
| else |
| mgr = KeystoreManagerFactory.make(""); |
| java.io.InputStream is = new java.io.ByteArrayInputStream(certificateValue); |
| String certError = null; |
| try |
| { |
| mgr.importCertificate(alias,is); |
| } |
| catch (Throwable e) |
| { |
| certError = e.getMessage(); |
| } |
| finally |
| { |
| try |
| { |
| is.close(); |
| } |
| catch (IOException e) |
| { |
| // Eat this exception |
| } |
| } |
| |
| if (certError != null) |
| { |
| return "Illegal certificate: "+certError; |
| } |
| parameters.setParameter(LiveLinkParameters.ingestKeystore,mgr.getString()); |
| } |
| } |
| |
| 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,"LivelinkConnector.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") || |
| param.length() > "truststore".length() && param.substring(param.length()-"truststore".length()).equalsIgnoreCase("truststore")) |
| { |
| IKeystoreManager kmanager = KeystoreManagerFactory.make("",value); |
| out.print( |
| " <nobr>"+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(param)+"=<"+Integer.toString(kmanager.getContents().length)+Messages.getBodyString(locale,"LivelinkConnector.certificates")+"></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,"LivelinkConnector.Paths")); |
| tabsArray.add(Messages.getString(locale,"LivelinkConnector.Filters")); |
| tabsArray.add(Messages.getString(locale,"LivelinkConnector.Security")); |
| tabsArray.add(Messages.getString(locale,"LivelinkConnector.Metadata")); |
| out.print( |
| "<script type=\"text/javascript\">\n"+ |
| "<!--\n"+ |
| "\n"+ |
| "function checkSpecification()\n"+ |
| "{\n"+ |
| " // Does nothing right now.\n"+ |
| " return true;\n"+ |
| "}\n"+ |
| "\n"+ |
| "function SpecOp(n, opValue, anchorvalue)\n"+ |
| "{\n"+ |
| " eval(\"editjob.\"+n+\".value = \\\"\"+opValue+\"\\\"\");\n"+ |
| " postFormSetAnchor(anchorvalue);\n"+ |
| "}\n"+ |
| "\n"+ |
| "function SpecAddToPath(anchorvalue)\n"+ |
| "{\n"+ |
| " if (editjob.pathaddon.value == \"\")\n"+ |
| " {\n"+ |
| " alert(\""+Messages.getBodyJavascriptString(locale,"LivelinkConnector.SelectAFolderFirst")+"\");\n"+ |
| " editjob.pathaddon.focus();\n"+ |
| " return;\n"+ |
| " }\n"+ |
| "\n"+ |
| " SpecOp(\"pathop\",\"AddToPath\",anchorvalue);\n"+ |
| "}\n"+ |
| "\n"+ |
| "function SpecAddFilespec(anchorvalue)\n"+ |
| "{\n"+ |
| " if (editjob.specfile.value == \"\")\n"+ |
| " {\n"+ |
| " alert(\""+Messages.getBodyJavascriptString(locale,"LivelinkConnector.TypeInAFileSpecification")+"\");\n"+ |
| " editjob.specfile.focus();\n"+ |
| " return;\n"+ |
| " }\n"+ |
| " SpecOp(\"fileop\",\"Add\",anchorvalue);\n"+ |
| "}\n"+ |
| "\n"+ |
| "function SpecAddToken(anchorvalue)\n"+ |
| "{\n"+ |
| " if (editjob.spectoken.value == \"\")\n"+ |
| " {\n"+ |
| " alert(\""+Messages.getBodyJavascriptString(locale,"LivelinkConnector.TypeInAnAccessToken")+"\");\n"+ |
| " editjob.spectoken.focus();\n"+ |
| " return;\n"+ |
| " }\n"+ |
| " SpecOp(\"accessop\",\"Add\",anchorvalue);\n"+ |
| "}\n"+ |
| "\n"+ |
| "function SpecAddToMetadata(anchorvalue)\n"+ |
| "{\n"+ |
| " if (editjob.metadataaddon.value == \"\")\n"+ |
| " {\n"+ |
| " alert(\""+Messages.getBodyJavascriptString(locale,"LivelinkConnector.SelectAFolderFirst")+"\");\n"+ |
| " editjob.metadataaddon.focus();\n"+ |
| " return;\n"+ |
| " }\n"+ |
| " SpecOp(\"metadataop\",\"AddToPath\",anchorvalue);\n"+ |
| "}\n"+ |
| "\n"+ |
| "function SpecSetWorkspace(anchorvalue)\n"+ |
| "{\n"+ |
| " if (editjob.metadataaddon.value == \"\")\n"+ |
| " {\n"+ |
| " alert(\""+Messages.getBodyJavascriptString(locale,"LivelinkConnector.SelectAWorkspaceFirst")+"\");\n"+ |
| " editjob.metadataaddon.focus();\n"+ |
| " return;\n"+ |
| " }\n"+ |
| " SpecOp(\"metadataop\",\"SetWorkspace\",anchorvalue);\n"+ |
| "}\n"+ |
| "\n"+ |
| "function SpecAddCategory(anchorvalue)\n"+ |
| "{\n"+ |
| " if (editjob.categoryaddon.value == \"\")\n"+ |
| " {\n"+ |
| " alert(\""+Messages.getBodyJavascriptString(locale,"LivelinkConnector.SelectACategoryFirst")+"\");\n"+ |
| " editjob.categoryaddon.focus();\n"+ |
| " return;\n"+ |
| " }\n"+ |
| " SpecOp(\"metadataop\",\"AddCategory\",anchorvalue);\n"+ |
| "}\n"+ |
| "\n"+ |
| "function SpecAddMetadata(anchorvalue)\n"+ |
| "{\n"+ |
| " if (editjob.attributeselect.value == \"\" && editjob.attributeall.value == \"\")\n"+ |
| " {\n"+ |
| " alert(\""+Messages.getBodyJavascriptString(locale,"LivelinkConnector.SelectAtLeastOneAttributeFirst")+"\");\n"+ |
| " editjob.attributeselect.focus();\n"+ |
| " return;\n"+ |
| " }\n"+ |
| " SpecOp(\"metadataop\",\"Add\",anchorvalue);\n"+ |
| "}\n"+ |
| "\n"+ |
| "function SpecAddMapping(anchorvalue)\n"+ |
| "{\n"+ |
| " if (editjob.specmatch.value == \"\")\n"+ |
| " {\n"+ |
| " alert(\""+Messages.getBodyJavascriptString(locale,"LivelinkConnector.MatchStringCannotBeEmpty")+"\");\n"+ |
| " editjob.specmatch.focus();\n"+ |
| " return;\n"+ |
| " }\n"+ |
| " if (!isRegularExpression(editjob.specmatch.value))\n"+ |
| " {\n"+ |
| " alert(\""+Messages.getBodyJavascriptString(locale,"LivelinkConnector.MatchStringMustBeValidRegularExpression")+"\");\n"+ |
| " editjob.specmatch.focus();\n"+ |
| " return;\n"+ |
| " }\n"+ |
| " SpecOp(\"specmappingop\",\"Add\",anchorvalue);\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 |
| { |
| int i; |
| int k; |
| |
| // Paths tab |
| boolean userWorkspaces = false; |
| i = 0; |
| while (i < ds.getChildCount()) |
| { |
| SpecificationNode sn = ds.getChild(i++); |
| if (sn.getType().equals("userworkspace")) |
| { |
| String value = sn.getAttributeValue("value"); |
| if (value != null && value.equals("true")) |
| userWorkspaces = true; |
| } |
| } |
| if (tabName.equals(Messages.getString(locale,"LivelinkConnector.Paths"))) |
| { |
| out.print( |
| "<table class=\"displaytable\">\n"+ |
| " <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n"+ |
| " <tr>\n"+ |
| " <td class=\"description\">\n"+ |
| " <nobr>"+Messages.getBodyString(locale,"LivelinkConnector.CrawlUserWorkspaces")+"</nobr>\n"+ |
| " </td>\n"+ |
| " <td class=\"value\">\n"+ |
| " <input type=\"checkbox\" name=\"userworkspace\" value=\"true\""+(userWorkspaces?" checked=\"true\"":"")+"/>\n"+ |
| " <input type=\"hidden\" name=\"userworkspace_present\" value=\"true\"/>\n"+ |
| " </td>\n"+ |
| " </tr>\n"+ |
| " <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n" |
| ); |
| // Now, loop through paths |
| i = 0; |
| k = 0; |
| while (i < ds.getChildCount()) |
| { |
| SpecificationNode sn = ds.getChild(i++); |
| if (sn.getType().equals("startpoint")) |
| { |
| String pathDescription = "_"+Integer.toString(k); |
| String pathOpName = "pathop"+pathDescription; |
| out.print( |
| " <tr>\n"+ |
| " <td class=\"description\">\n"+ |
| " <input type=\"hidden\" name=\""+"specpath"+pathDescription+"\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(sn.getAttributeValue("path"))+"\"/>\n"+ |
| " <input type=\"hidden\" name=\""+pathOpName+"\" value=\"\"/>\n"+ |
| " <a name=\""+"path_"+Integer.toString(k)+"\">\n"+ |
| " <input type=\"button\" value=\"Delete\" onClick='Javascript:SpecOp(\""+pathOpName+"\",\"Delete\",\"path_"+Integer.toString(k)+"\")' alt=\""+Messages.getAttributeString(locale,"LivelinkConnector.DeletePath")+Integer.toString(k)+"\"/>\n"+ |
| " </a>\n"+ |
| " </td>\n"+ |
| " <td class=\"value\">\n"+ |
| " "+((sn.getAttributeValue("path").length() == 0)?"(root)":org.apache.manifoldcf.ui.util.Encoder.bodyEscape(sn.getAttributeValue("path")))+"\n"+ |
| " </td>\n"+ |
| " </tr>\n" |
| ); |
| k++; |
| } |
| } |
| if (k == 0) |
| { |
| out.print( |
| " <tr>\n"+ |
| " <td class=\"message\" colspan=\"2\">"+Messages.getBodyString(locale,"LivelinkConnector.NoStartingPointsDefined")+"</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=\"pathcount\" value=\""+Integer.toString(k)+"\"/>\n" |
| ); |
| |
| String pathSoFar = (String)currentContext.get("specpath"); |
| if (pathSoFar == null) |
| pathSoFar = ""; |
| |
| // Grab next folder/project list |
| try |
| { |
| String[] childList; |
| childList = getChildFolderNames(pathSoFar); |
| if (childList == null) |
| { |
| // Illegal path - set it back |
| pathSoFar = ""; |
| childList = getChildFolderNames(""); |
| if (childList == null) |
| throw new ManifoldCFException("Can't find any children for root folder"); |
| } |
| out.print( |
| " <input type=\"hidden\" name=\"specpath\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(pathSoFar)+"\"/>\n"+ |
| " <input type=\"hidden\" name=\"pathop\" value=\"\"/>\n"+ |
| " <a name=\""+"path_"+Integer.toString(k)+"\">\n"+ |
| " <input type=\"button\" value=\"Add\" onClick='Javascript:SpecOp(\"pathop\",\"Add\",\"path_"+Integer.toString(k+1)+"\")' alt=\""+Messages.getAttributeString(locale,"LivelinkConnector.AddPath")+"\"/>\n"+ |
| " </a> \n"+ |
| " </td>\n"+ |
| " <td class=\"value\">\n"+ |
| " "+((pathSoFar.length()==0)?"(root)":org.apache.manifoldcf.ui.util.Encoder.bodyEscape(pathSoFar))+"\n" |
| ); |
| if (pathSoFar.length() > 0) |
| { |
| out.print( |
| " <input type=\"button\" value=\"-\" onClick='Javascript:SpecOp(\"pathop\",\"Up\",\"path_"+Integer.toString(k)+"\")' alt=\"Back up path\"/>\n" |
| ); |
| } |
| if (childList.length > 0) |
| { |
| out.print( |
| " <input type=\"button\" value=\"+\" onClick='Javascript:SpecAddToPath(\"path_"+Integer.toString(k)+"\")' alt=\""+Messages.getAttributeString(locale,"LivelinkConnector.AddToPath")+"\"/> \n"+ |
| " <select multiple=\"false\" name=\"pathaddon\" size=\"2\">\n"+ |
| " <option value=\"\" selected=\"selected\">"+Messages.getBodyString(locale,"LivelinkConnector.PickAFolder")+"</option>\n" |
| ); |
| int j = 0; |
| while (j < childList.length) |
| { |
| out.print( |
| " <option value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(childList[j])+"\">"+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(childList[j])+"</option>\n" |
| ); |
| j++; |
| } |
| out.print( |
| " </select>\n" |
| ); |
| } |
| } |
| catch (ServiceInterruption e) |
| { |
| //e.printStackTrace(); |
| out.println(org.apache.manifoldcf.ui.util.Encoder.bodyEscape(e.getMessage())); |
| } |
| catch (ManifoldCFException e) |
| { |
| //e.printStackTrace(); |
| out.println(org.apache.manifoldcf.ui.util.Encoder.bodyEscape(e.getMessage())); |
| } |
| out.print( |
| " </td>\n"+ |
| " </tr>\n"+ |
| "</table>\n" |
| ); |
| } |
| else |
| { |
| // Now, loop through paths |
| i = 0; |
| k = 0; |
| while (i < ds.getChildCount()) |
| { |
| SpecificationNode sn = ds.getChild(i++); |
| if (sn.getType().equals("startpoint")) |
| { |
| String pathDescription = "_"+Integer.toString(k); |
| out.print( |
| "<input type=\"hidden\" name=\""+"specpath"+pathDescription+"\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(sn.getAttributeValue("path"))+"\"/>\n" |
| ); |
| k++; |
| } |
| } |
| out.print( |
| "<input type=\"hidden\" name=\"pathcount\" value=\""+Integer.toString(k)+"\"/>\n"+ |
| "<input type=\"hidden\" name=\"userworkspace\" value=\""+(userWorkspaces?"true":"false")+"\"/>\n"+ |
| "<input type=\"hidden\" name=\"userworkspace_present\" value=\"true\"/>\n" |
| ); |
| } |
| |
| // Filter tab |
| if (tabName.equals(Messages.getString(locale,"LivelinkConnector.Filters"))) |
| { |
| out.print( |
| "<table class=\"displaytable\">\n"+ |
| " <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n" |
| ); |
| // Next, go through include/exclude filespecs |
| i = 0; |
| k = 0; |
| while (i < ds.getChildCount()) |
| { |
| SpecificationNode sn = ds.getChild(i++); |
| if (sn.getType().equals("include") || sn.getType().equals("exclude")) |
| { |
| String fileSpecDescription = "_"+Integer.toString(k); |
| String fileOpName = "fileop"+fileSpecDescription; |
| String filespec = sn.getAttributeValue("filespec"); |
| out.print( |
| " <tr>\n"+ |
| " <td class=\"description\">\n"+ |
| " <input type=\"hidden\" name=\""+"specfiletype"+fileSpecDescription+"\" value=\""+sn.getType()+"\"/>\n"+ |
| " <input type=\"hidden\" name=\""+fileOpName+"\" value=\"\"/>\n"+ |
| " <a name=\""+"filespec_"+Integer.toString(k)+"\">\n"+ |
| " <input type=\"button\" value=\"Delete\" onClick='Javascript:SpecOp(\""+fileOpName+"\",\"Delete\",\"filespec_"+Integer.toString(k)+"\")' alt=\""+Messages.getAttributeString(locale,"LivelinkConnector.DeleteFilespec")+Integer.toString(k)+"\"/>\n"+ |
| " </a>\n"+ |
| " </td>\n"+ |
| " <td class=\"value\">\n"+ |
| " "+(sn.getType().equals("include")?"Include:":"")+"\n"+ |
| " "+(sn.getType().equals("exclude")?"Exclude:":"")+"\n"+ |
| " <input type=\"hidden\" name=\""+"specfile"+fileSpecDescription+"\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(filespec)+"\"/>\n"+ |
| " "+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(filespec)+"\n"+ |
| " </td>\n"+ |
| " </tr>\n" |
| ); |
| k++; |
| } |
| } |
| if (k == 0) |
| { |
| out.print( |
| " <tr>\n"+ |
| " <td class=\"message\" colspan=\"2\">"+Messages.getBodyString(locale,"LivelinkConnector.NoIncludeExcludeFilesDefined")+"</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=\"filecount\" value=\""+Integer.toString(k)+"\"/>\n"+ |
| " <input type=\"hidden\" name=\"fileop\" value=\"\"/>\n"+ |
| " <a name=\""+"filespec_"+Integer.toString(k)+"\">\n"+ |
| " <input type=\"button\" value=\"Add\" onClick='Javascript:SpecAddFilespec(\"filespec_"+Integer.toString(k+1)+"\")' alt=\""+Messages.getAttributeString(locale,"LivelinkConnector.AddFileSpecification")+"\"/>\n"+ |
| " </a> \n"+ |
| " </td>\n"+ |
| " <td class=\"value\">\n"+ |
| " <select name=\"specfiletype\" size=\"1\">\n"+ |
| " <option value=\"include\" selected=\"selected\">"+Messages.getBodyString(locale,"LivelinkConnector.Include")+"</option>\n"+ |
| " <option value=\"exclude\">"+Messages.getBodyString(locale,"LivelinkConnector.Exclude")+"</option>\n"+ |
| " </select> \n"+ |
| " <input type=\"text\" size=\"30\" name=\"specfile\" value=\"\"/>\n"+ |
| " </td>\n"+ |
| " </tr>\n"+ |
| "</table>\n" |
| ); |
| } |
| else |
| { |
| // Next, go through include/exclude filespecs |
| i = 0; |
| k = 0; |
| while (i < ds.getChildCount()) |
| { |
| SpecificationNode sn = ds.getChild(i++); |
| if (sn.getType().equals("include") || sn.getType().equals("exclude")) |
| { |
| String fileSpecDescription = "_"+Integer.toString(k); |
| String filespec = sn.getAttributeValue("filespec"); |
| out.print( |
| "<input type=\"hidden\" name=\""+"specfiletype"+fileSpecDescription+"\" value=\""+sn.getType()+"\"/>\n"+ |
| "<input type=\"hidden\" name=\""+"specfile"+fileSpecDescription+"\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(filespec)+"\"/>\n" |
| ); |
| k++; |
| } |
| } |
| out.print( |
| "<input type=\"hidden\" name=\"filecount\" value=\""+Integer.toString(k)+"\"/>\n" |
| ); |
| } |
| |
| |
| // Security tab |
| // 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; |
| } |
| } |
| |
| if (tabName.equals(Messages.getString(locale,"LivelinkConnector.Security"))) |
| { |
| 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,"LivelinkConnector.SecurityColon")+"</nobr></td>\n"+ |
| " <td class=\"value\">\n"+ |
| " <input type=\"radio\" name=\"specsecurity\" value=\"on\" "+(securityOn?"checked=\"true\"":"")+" />"+Messages.getBodyString(locale,"LivelinkConnector.Enabled")+"\n"+ |
| " <input type=\"radio\" name=\"specsecurity\" value=\"off\" "+((securityOn==false)?"checked=\"true\"":"")+" />"+Messages.getBodyString(locale,"LivelinkConnector.Disabled")+"\n"+ |
| " </td>\n"+ |
| " </tr>\n"+ |
| " <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n" |
| ); |
| // Go through forced ACL |
| i = 0; |
| 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,"LivelinkConnector.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,"LivelinkConnector.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,"LivelinkConnector.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 |
| { |
| out.print( |
| "<input type=\"hidden\" name=\"specsecurity\" value=\""+(securityOn?"on":"off")+"\"/>\n" |
| ); |
| // Finally, go through forced ACL |
| i = 0; |
| 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" |
| ); |
| } |
| |
| |
| // Metadata tab |
| |
| // Find the path-value metadata attribute name |
| i = 0; |
| String pathNameAttribute = ""; |
| String pathNameSeparator = "/"; |
| while (i < ds.getChildCount()) |
| { |
| SpecificationNode sn = ds.getChild(i++); |
| if (sn.getType().equals("pathnameattribute")) |
| { |
| pathNameAttribute = sn.getAttributeValue("value"); |
| if (sn.getAttributeValue("separator") != null) |
| pathNameSeparator = sn.getAttributeValue("separator"); |
| } |
| } |
| |
| // Find the path-value mapping data |
| i = 0; |
| MatchMap matchMap = new MatchMap(); |
| while (i < ds.getChildCount()) |
| { |
| SpecificationNode sn = ds.getChild(i++); |
| if (sn.getType().equals("pathmap")) |
| { |
| String pathMatch = sn.getAttributeValue("match"); |
| String pathReplace = sn.getAttributeValue("replace"); |
| matchMap.appendMatchPair(pathMatch,pathReplace); |
| } |
| } |
| |
| |
| i = 0; |
| String ingestAllMetadata = "false"; |
| while (i < ds.getChildCount()) |
| { |
| SpecificationNode sn = ds.getChild(i++); |
| if (sn.getType().equals("allmetadata")) |
| { |
| ingestAllMetadata = sn.getAttributeValue("all"); |
| if (ingestAllMetadata == null) |
| ingestAllMetadata = "false"; |
| } |
| } |
| |
| if (tabName.equals(Messages.getString(locale,"LivelinkConnector.Metadata"))) |
| { |
| out.print( |
| "<input type=\"hidden\" name=\"specmappingcount\" value=\""+Integer.toString(matchMap.getMatchCount())+"\"/>\n"+ |
| "<input type=\"hidden\" name=\"specmappingop\" value=\"\"/>\n"+ |
| "\n"+ |
| "<table class=\"displaytable\">\n"+ |
| " <tr><td class=\"separator\" colspan=\"4\"><hr/></td></tr>\n"+ |
| " <tr>\n"+ |
| " <td class=\"description\" colspan=\"1\"><nobr>"+Messages.getBodyString(locale,"LivelinkConnector.IngestALLMetadata")+"</nobr></td>\n"+ |
| " <td class=\"value\" colspan=\"3\">\n"+ |
| " <nobr><input type=\"radio\" name=\"specallmetadata\" value=\"true\" "+(ingestAllMetadata.equals("true")?"checked=\"true\"":"")+"/>"+Messages.getBodyString(locale,"LivelinkConnector.Yes")+"</nobr> \n"+ |
| " <nobr><input type=\"radio\" name=\"specallmetadata\" value=\"false\" "+(ingestAllMetadata.equals("false")?"checked=\"true\"":"")+"/>"+Messages.getBodyString(locale,"LivelinkConnector.No")+"</nobr>\n"+ |
| " </td>\n"+ |
| " </tr>\n"+ |
| " <tr><td class=\"separator\" colspan=\"4\"><hr/></td></tr>\n" |
| ); |
| // Go through the selected metadata attributes |
| i = 0; |
| k = 0; |
| while (i < ds.getChildCount()) |
| { |
| SpecificationNode sn = ds.getChild(i++); |
| if (sn.getType().equals("metadata")) |
| { |
| String accessDescription = "_"+Integer.toString(k); |
| String accessOpName = "metadataop"+accessDescription; |
| String categoryPath = sn.getAttributeValue("category"); |
| String isAll = sn.getAttributeValue("all"); |
| if (isAll == null) |
| isAll = "false"; |
| String attributeName = sn.getAttributeValue("attribute"); |
| if (attributeName == null) |
| attributeName = ""; |
| out.print( |
| " <tr>\n"+ |
| " <td class=\"description\" colspan=\"1\">\n"+ |
| " <input type=\"hidden\" name=\""+accessOpName+"\" value=\"\"/>\n"+ |
| " <input type=\"hidden\" name=\""+"speccategory"+accessDescription+"\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(categoryPath)+"\"/>\n"+ |
| " <input type=\"hidden\" name=\""+"specattributeall"+accessDescription+"\" value=\""+isAll+"\"/>\n"+ |
| " <input type=\"hidden\" name=\""+"specattribute"+accessDescription+"\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(attributeName)+"\"/>\n"+ |
| " <a name=\""+"metadata_"+Integer.toString(k)+"\">\n"+ |
| " <input type=\"button\" value=\"Delete\" onClick='Javascript:SpecOp(\""+accessOpName+"\",\"Delete\",\"metadata_"+Integer.toString(k)+"\")' alt=\""+Messages.getAttributeString(locale,"LivelinkConnector.DeleteMetadata")+Integer.toString(k)+"\"/>\n"+ |
| " </a> \n"+ |
| " </td>\n"+ |
| " <td class=\"value\" colspan=\"3\">\n"+ |
| " "+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(categoryPath)+":"+((isAll!=null&&isAll.equals("true"))?"(All metadata attributes)":org.apache.manifoldcf.ui.util.Encoder.bodyEscape(attributeName))+"\n"+ |
| " </td>\n"+ |
| " </tr>\n" |
| ); |
| k++; |
| } |
| } |
| if (k == 0) |
| { |
| out.print( |
| " <tr>\n"+ |
| " <td class=\"message\" colspan=\"4\">"+Messages.getBodyString(locale,"LivelinkConnector.NoMetadataSpecified")+"</td>\n"+ |
| " </tr>\n" |
| ); |
| } |
| out.print( |
| " <tr><td class=\"lightseparator\" colspan=\"4\"><hr/></td></tr>\n"+ |
| " <tr>\n"+ |
| " <td class=\"description\" colspan=\"1\">\n"+ |
| " <a name=\""+"metadata_"+Integer.toString(k)+"\"></a>\n"+ |
| " <input type=\"hidden\" name=\"metadatacount\" value=\""+Integer.toString(k)+"\"/>\n" |
| ); |
| String categorySoFar = (String)currentContext.get("speccategory"); |
| if (categorySoFar == null) |
| categorySoFar = ""; |
| // Grab next folder/project list, and the appropriate category list |
| try |
| { |
| String[] childList = null; |
| String[] workspaceList = null; |
| String[] categoryList = null; |
| String[] attributeList = null; |
| if (categorySoFar.length() == 0) |
| { |
| workspaceList = getWorkspaceNames(); |
| } |
| else |
| { |
| attributeList = getCategoryAttributes(categorySoFar); |
| if (attributeList == null) |
| { |
| childList = getChildFolderNames(categorySoFar); |
| if (childList == null) |
| { |
| // Illegal path - set it back |
| categorySoFar = ""; |
| childList = getChildFolderNames(""); |
| if (childList == null) |
| throw new ManifoldCFException("Can't find any children for root folder"); |
| } |
| categoryList = getChildCategoryNames(categorySoFar); |
| if (categoryList == null) |
| throw new ManifoldCFException("Can't find any categories for root folder folder"); |
| } |
| } |
| out.print( |
| " <input type=\"hidden\" name=\"speccategory\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(categorySoFar)+"\"/>\n"+ |
| " <input type=\"hidden\" name=\"metadataop\" value=\"\"/>\n" |
| ); |
| if (attributeList != null) |
| { |
| // We have a valid category! |
| out.print( |
| " <input type=\"button\" value=\"Add\" onClick='Javascript:SpecAddMetadata(\"metadata_"+Integer.toString(k+1)+"\")' alt=\""+Messages.getAttributeString(locale,"LivelinkConnector.AddMetadataItem")+"\"/> \n"+ |
| " </td>\n"+ |
| " <td class=\"value\" colspan=\"3\">\n"+ |
| " "+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(categorySoFar)+":<input type=\"button\" value=\"-\" onClick='Javascript:SpecOp(\"metadataop\",\"Up\",\"metadata_"+Integer.toString(k)+"\")' alt=\""+Messages.getAttributeString(locale,"LivelinkConnector.BackUpMetadataPath")+"\"/> \n"+ |
| " <table class=\"displaytable\">\n"+ |
| " <tr>\n"+ |
| " <td class=\"value\">\n"+ |
| " <input type=\"checkbox\" name=\"attributeall\" value=\"true\"/>"+Messages.getBodyString(locale,"LivelinkConnector.AllAttributesInThisCategory")+"<br/>\n"+ |
| " <select multiple=\"true\" name=\"attributeselect\" size=\"2\">\n"+ |
| " <option value=\"\" selected=\"selected\">"+Messages.getBodyString(locale,"LivelinkConnector.PickAttributes")+"</option>\n" |
| ); |
| int l = 0; |
| while (l < attributeList.length) |
| { |
| String attributeName = attributeList[l++]; |
| out.print( |
| " <option value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(attributeName)+"\">"+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(attributeName)+"</option>\n" |
| ); |
| } |
| out.print( |
| " </select>\n"+ |
| " </td>\n"+ |
| " </tr>\n"+ |
| " </table>\n" |
| ); |
| } |
| else if (workspaceList != null) |
| { |
| out.print( |
| " </td>\n"+ |
| " <td class=\"value\" colspan=\"3\">\n"+ |
| " <input type=\"button\" value=\"+\" onClick='Javascript:SpecSetWorkspace(\"metadata_"+Integer.toString(k)+"\")' alt=\""+Messages.getAttributeString(locale,"LivelinkConnector.AddToMetadataPath")+"\"/> \n"+ |
| " <select multiple=\"false\" name=\"metadataaddon\" size=\"2\">\n"+ |
| " <option value=\"\" selected=\"selected\">"+Messages.getBodyString(locale,"LivelinkConnector.PickWorkspace")+"</option>\n" |
| ); |
| int j = 0; |
| while (j < workspaceList.length) |
| { |
| out.print( |
| " <option value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(workspaceList[j])+"\">"+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(workspaceList[j])+"</option>\n" |
| ); |
| j++; |
| } |
| out.print( |
| " </select>\n" |
| ); |
| } |
| else |
| { |
| out.print( |
| " </td>\n"+ |
| " <td class=\"value\" colspan=\"3\">\n"+ |
| " "+((categorySoFar.length()==0)?"(root)":org.apache.manifoldcf.ui.util.Encoder.bodyEscape(categorySoFar))+" \n" |
| ); |
| if (categorySoFar.length() > 0) |
| { |
| out.print( |
| " <input type=\"button\" value=\"-\" onClick='Javascript:SpecOp(\"metadataop\",\"Up\",\"metadata_"+Integer.toString(k)+"\")' alt=\""+Messages.getAttributeString(locale,"LivelinkConnector.BackUpMetadataPath")+"\"/> \n" |
| ); |
| } |
| if (childList.length > 0) |
| { |
| out.print( |
| " <input type=\"button\" value=\"+\" onClick='Javascript:SpecAddToMetadata(\"metadata_"+Integer.toString(k)+"\")' alt=\""+Messages.getAttributeString(locale,"LivelinkConnector.AddToMetadataPath")+"\"/> \n"+ |
| " <select multiple=\"false\" name=\"metadataaddon\" size=\"2\">\n"+ |
| " <option value=\"\" selected=\"selected\">"+Messages.getBodyString(locale,"LivelinkConnector.PickAFolder")+"</option>\n" |
| ); |
| int j = 0; |
| while (j < childList.length) |
| { |
| out.print( |
| " <option value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(childList[j])+"\">"+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(childList[j])+"</option>\n" |
| ); |
| j++; |
| } |
| out.print( |
| " </select>\n" |
| ); |
| } |
| if (categoryList.length > 0) |
| { |
| out.print( |
| " <input type=\"button\" value=\"+\" onClick='Javascript:SpecAddCategory(\"metadata_"+Integer.toString(k)+"\")' alt=\""+Messages.getAttributeString(locale,"LivelinkConnector.AddCategory")+"\"/> \n"+ |
| " <select multiple=\"false\" name=\"categoryaddon\" size=\"2\">\n"+ |
| " <option value=\"\" selected=\"selected\">"+Messages.getBodyString(locale,"LivelinkConnector.PickACategory")+"</option>\n" |
| ); |
| int j = 0; |
| while (j < categoryList.length) |
| { |
| out.print( |
| " <option value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(categoryList[j])+"\">"+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(categoryList[j])+"</option>\n" |
| ); |
| j++; |
| } |
| out.print( |
| " </select>\n" |
| ); |
| } |
| } |
| } |
| catch (ServiceInterruption e) |
| { |
| //e.printStackTrace(); |
| out.println(org.apache.manifoldcf.ui.util.Encoder.bodyEscape(e.getMessage())); |
| } |
| catch (ManifoldCFException e) |
| { |
| //e.printStackTrace(); |
| out.println(org.apache.manifoldcf.ui.util.Encoder.bodyEscape(e.getMessage())); |
| } |
| out.print( |
| " </td>\n"+ |
| " </tr>\n"+ |
| " <tr><td class=\"separator\" colspan=\"4\"><hr/></td></tr>\n"+ |
| " <tr>\n"+ |
| " <td class=\"description\" colspan=\"1\"><nobr>"+Messages.getBodyString(locale,"LivelinkConnector.PathAttributeName")+"</nobr></td>\n"+ |
| " <td class=\"value\" colspan=\"1\">\n"+ |
| " <input type=\"text\" name=\"specpathnameattribute\" size=\"20\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(pathNameAttribute)+"\"/>\n"+ |
| " </td>\n"+ |
| " <td class=\"description\" colspan=\"1\"><nobr>"+Messages.getBodyString(locale,"LivelinkConnector.PathSeparatorString")+"</nobr></td>\n"+ |
| " <td class=\"value\" colspan=\"1\">\n"+ |
| " <input type=\"text\" name=\"specpathnameseparator\" size=\"20\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(pathNameSeparator)+"\"/>\n"+ |
| " </td>\n"+ |
| " </tr>\n"+ |
| " <tr><td class=\"separator\" colspan=\"4\"><hr/></td></tr>\n" |
| ); |
| i = 0; |
| while (i < matchMap.getMatchCount()) |
| { |
| String matchString = matchMap.getMatchString(i); |
| String replaceString = matchMap.getReplaceString(i); |
| out.print( |
| " <tr>\n"+ |
| " <td class=\"description\">\n"+ |
| " <input type=\"hidden\" name=\""+"specmappingop_"+Integer.toString(i)+"\" value=\"\"/>\n"+ |
| " <a name=\""+"mapping_"+Integer.toString(i)+"\">\n"+ |
| " <input type=\"button\" onClick='Javascript:SpecOp(\"specmappingop_"+Integer.toString(i)+"\",\"Delete\",\"mapping_"+Integer.toString(i)+"\")' alt=\""+Messages.getAttributeString(locale,"LivelinkConnector.DeleteMapping")+Integer.toString(i)+"\" value=\"Delete\"/>\n"+ |
| " </a>\n"+ |
| " </td>\n"+ |
| " <td class=\"value\">\n"+ |
| " <input type=\"hidden\" name=\""+"specmatch_"+Integer.toString(i)+"\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(matchString)+"\"/>"+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(matchString)+"\n"+ |
| " </td>\n"+ |
| " <td class=\"value\">==></td>\n"+ |
| " <td class=\"value\">\n"+ |
| " <input type=\"hidden\" name=\""+"specreplace_"+Integer.toString(i)+"\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(replaceString)+"\"/>"+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(replaceString)+"\n"+ |
| " </td>\n"+ |
| " </tr>\n" |
| ); |
| i++; |
| } |
| if (i == 0) |
| { |
| out.print( |
| " <tr><td colspan=\"4\" class=\"message\">"+Messages.getBodyString(locale,"LivelinkConnector.NoMappingsSpecified")+"</td></tr>\n" |
| ); |
| } |
| out.print( |
| " <tr><td class=\"lightseparator\" colspan=\"4\"><hr/></td></tr>\n"+ |
| " <tr>\n"+ |
| " <td class=\"description\">\n"+ |
| " <a name=\""+"mapping_"+Integer.toString(i)+"\">\n"+ |
| " <input type=\"button\" onClick='Javascript:SpecAddMapping(\"mapping_"+Integer.toString(i+1)+"\")' alt=\""+Messages.getAttributeString(locale,"LivelinkConnector.AddToMappings")+"\" value=\"Add\"/>\n"+ |
| " </a>\n"+ |
| " </td>\n"+ |
| " <td class=\"value\">Match regexp: <input type=\"text\" name=\"specmatch\" size=\"32\" value=\"\"/></td>\n"+ |
| " <td class=\"value\">==></td>\n"+ |
| " <td class=\"value\">Replace string: <input type=\"text\" name=\"specreplace\" size=\"32\" value=\"\"/></td>\n"+ |
| " </tr>\n"+ |
| "</table>\n" |
| ); |
| } |
| else |
| { |
| out.print( |
| "<input type=\"hidden\" name=\"specallmetadata\" value=\""+ingestAllMetadata+"\"/>\n" |
| ); |
| // Go through the selected metadata attributes |
| i = 0; |
| k = 0; |
| while (i < ds.getChildCount()) |
| { |
| SpecificationNode sn = ds.getChild(i++); |
| if (sn.getType().equals("metadata")) |
| { |
| String accessDescription = "_"+Integer.toString(k); |
| String categoryPath = sn.getAttributeValue("category"); |
| String isAll = sn.getAttributeValue("all"); |
| if (isAll == null) |
| isAll = "false"; |
| String attributeName = sn.getAttributeValue("attribute"); |
| if (attributeName == null) |
| attributeName = ""; |
| out.print( |
| "<input type=\"hidden\" name=\""+"speccategory"+accessDescription+"\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(categoryPath)+"\"/>\n"+ |
| "<input type=\"hidden\" name=\""+"specattributeall"+accessDescription+"\" value=\""+isAll+"\"/>\n"+ |
| "<input type=\"hidden\" name=\""+"specattribute"+accessDescription+"\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(attributeName)+"\"/>\n" |
| ); |
| k++; |
| } |
| } |
| out.print( |
| "<input type=\"hidden\" name=\"metadatacount\" value=\""+Integer.toString(k)+"\"/>\n"+ |
| "<input type=\"hidden\" name=\"specpathnameattribute\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(pathNameAttribute)+"\"/>\n"+ |
| "<input type=\"hidden\" name=\"specpathnameseparator\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(pathNameSeparator)+"\"/>\n"+ |
| "<input type=\"hidden\" name=\"specmappingcount\" value=\""+Integer.toString(matchMap.getMatchCount())+"\"/>\n" |
| ); |
| i = 0; |
| while (i < matchMap.getMatchCount()) |
| { |
| String matchString = matchMap.getMatchString(i); |
| String replaceString = matchMap.getReplaceString(i); |
| out.print( |
| "<input type=\"hidden\" name=\""+"specmatch_"+Integer.toString(i)+"\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(matchString)+"\"/>\n"+ |
| "<input type=\"hidden\" name=\""+"specreplace_"+Integer.toString(i)+"\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(replaceString)+"\"/>\n" |
| ); |
| i++; |
| } |
| } |
| } |
| |
| /** 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 userWorkspacesPresent = variableContext.getParameter("userworkspace_present"); |
| if (userWorkspacesPresent != null) |
| { |
| String value = variableContext.getParameter("userworkspace"); |
| int i = 0; |
| while (i < ds.getChildCount()) |
| { |
| SpecificationNode sn = ds.getChild(i); |
| if (sn.getType().equals("userworkspace")) |
| ds.removeChild(i); |
| else |
| i++; |
| } |
| SpecificationNode sn = new SpecificationNode("userworkspace"); |
| sn.setAttribute("value",value); |
| ds.addChild(ds.getChildCount(),sn); |
| } |
| |
| String xc = variableContext.getParameter("pathcount"); |
| if (xc != null) |
| { |
| // Delete all path specs first |
| int i = 0; |
| while (i < ds.getChildCount()) |
| { |
| SpecificationNode sn = ds.getChild(i); |
| if (sn.getType().equals("startpoint")) |
| ds.removeChild(i); |
| else |
| i++; |
| } |
| |
| // Find out how many children were sent |
| int pathCount = Integer.parseInt(xc); |
| // Gather up these |
| i = 0; |
| while (i < pathCount) |
| { |
| String pathDescription = "_"+Integer.toString(i); |
| String pathOpName = "pathop"+pathDescription; |
| xc = variableContext.getParameter(pathOpName); |
| if (xc != null && xc.equals("Delete")) |
| { |
| // Skip to the next |
| i++; |
| continue; |
| } |
| // Path inserts won't happen until the very end |
| String path = variableContext.getParameter("specpath"+pathDescription); |
| SpecificationNode node = new SpecificationNode("startpoint"); |
| node.setAttribute("path",path); |
| ds.addChild(ds.getChildCount(),node); |
| i++; |
| } |
| |
| // See if there's a global add operation |
| String op = variableContext.getParameter("pathop"); |
| if (op != null && op.equals("Add")) |
| { |
| String path = variableContext.getParameter("specpath"); |
| SpecificationNode node = new SpecificationNode("startpoint"); |
| node.setAttribute("path",path); |
| ds.addChild(ds.getChildCount(),node); |
| } |
| else if (op != null && op.equals("Up")) |
| { |
| // Strip off end |
| String path = variableContext.getParameter("specpath"); |
| int lastSlash = -1; |
| int k = 0; |
| while (k < path.length()) |
| { |
| char x = path.charAt(k++); |
| if (x == '/') |
| { |
| lastSlash = k-1; |
| continue; |
| } |
| if (x == '\\') |
| k++; |
| } |
| if (lastSlash == -1) |
| path = ""; |
| else |
| path = path.substring(0,lastSlash); |
| currentContext.save("specpath",path); |
| } |
| else if (op != null && op.equals("AddToPath")) |
| { |
| String path = variableContext.getParameter("specpath"); |
| String addon = variableContext.getParameter("pathaddon"); |
| if (addon != null && addon.length() > 0) |
| { |
| StringBuilder sb = new StringBuilder(); |
| int k = 0; |
| while (k < addon.length()) |
| { |
| char x = addon.charAt(k++); |
| if (x == '/' || x == '\\' || x == ':') |
| sb.append('\\'); |
| sb.append(x); |
| } |
| if (path.length() == 0) |
| path = sb.toString(); |
| else |
| path += "/" + sb.toString(); |
| } |
| currentContext.save("specpath",path); |
| } |
| } |
| |
| xc = variableContext.getParameter("filecount"); |
| if (xc != null) |
| { |
| // Delete all file specs first |
| int i = 0; |
| while (i < ds.getChildCount()) |
| { |
| SpecificationNode sn = ds.getChild(i); |
| if (sn.getType().equals("include") || sn.getType().equals("exclude")) |
| ds.removeChild(i); |
| else |
| i++; |
| } |
| |
| int fileCount = Integer.parseInt(xc); |
| i = 0; |
| while (i < fileCount) |
| { |
| String fileSpecDescription = "_"+Integer.toString(i); |
| String fileOpName = "fileop"+fileSpecDescription; |
| xc = variableContext.getParameter(fileOpName); |
| if (xc != null && xc.equals("Delete")) |
| { |
| // Next row |
| i++; |
| continue; |
| } |
| // Get the stuff we need |
| String filespecType = variableContext.getParameter("specfiletype"+fileSpecDescription); |
| String filespec = variableContext.getParameter("specfile"+fileSpecDescription); |
| SpecificationNode node = new SpecificationNode(filespecType); |
| node.setAttribute("filespec",filespec); |
| ds.addChild(ds.getChildCount(),node); |
| i++; |
| } |
| |
| String op = variableContext.getParameter("fileop"); |
| if (op != null && op.equals("Add")) |
| { |
| String filespec = variableContext.getParameter("specfile"); |
| String filespectype = variableContext.getParameter("specfiletype"); |
| SpecificationNode node = new SpecificationNode(filespectype); |
| node.setAttribute("filespec",filespec); |
| ds.addChild(ds.getChildCount(),node); |
| } |
| } |
| |
| xc = variableContext.getParameter("specsecurity"); |
| if (xc != null) |
| { |
| // Delete all security entries first |
| int i = 0; |
| while (i < ds.getChildCount()) |
| { |
| SpecificationNode 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("tokencount"); |
| if (xc != null) |
| { |
| // Delete all file specs first |
| int i = 0; |
| while (i < ds.getChildCount()) |
| { |
| SpecificationNode 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); |
| } |
| } |
| |
| xc = variableContext.getParameter("specallmetadata"); |
| if (xc != null) |
| { |
| // Look for the 'all metadata' checkbox |
| int i = 0; |
| while (i < ds.getChildCount()) |
| { |
| SpecificationNode sn = ds.getChild(i); |
| if (sn.getType().equals("allmetadata")) |
| ds.removeChild(i); |
| else |
| i++; |
| } |
| |
| if (xc.equals("true")) |
| { |
| SpecificationNode newNode = new SpecificationNode("allmetadata"); |
| newNode.setAttribute("all",xc); |
| ds.addChild(ds.getChildCount(),newNode); |
| } |
| } |
| |
| xc = variableContext.getParameter("metadatacount"); |
| if (xc != null) |
| { |
| // Delete all metadata specs first |
| int i = 0; |
| while (i < ds.getChildCount()) |
| { |
| SpecificationNode sn = ds.getChild(i); |
| if (sn.getType().equals("metadata")) |
| ds.removeChild(i); |
| else |
| i++; |
| } |
| |
| // Find out how many children were sent |
| int metadataCount = Integer.parseInt(xc); |
| // Gather up these |
| i = 0; |
| while (i < metadataCount) |
| { |
| String pathDescription = "_"+Integer.toString(i); |
| String pathOpName = "metadataop"+pathDescription; |
| xc = variableContext.getParameter(pathOpName); |
| if (xc != null && xc.equals("Delete")) |
| { |
| // Skip to the next |
| i++; |
| continue; |
| } |
| // Metadata inserts won't happen until the very end |
| String category = variableContext.getParameter("speccategory"+pathDescription); |
| String attributeName = variableContext.getParameter("specattribute"+pathDescription); |
| String isAll = variableContext.getParameter("specattributeall"+pathDescription); |
| SpecificationNode node = new SpecificationNode("metadata"); |
| node.setAttribute("category",category); |
| if (isAll != null && isAll.equals("true")) |
| node.setAttribute("all","true"); |
| else |
| node.setAttribute("attribute",attributeName); |
| ds.addChild(ds.getChildCount(),node); |
| i++; |
| } |
| |
| // See if there's a global add operation |
| String op = variableContext.getParameter("metadataop"); |
| if (op != null && op.equals("Add")) |
| { |
| String category = variableContext.getParameter("speccategory"); |
| String isAll = variableContext.getParameter("attributeall"); |
| if (isAll != null && isAll.equals("true")) |
| { |
| SpecificationNode node = new SpecificationNode("metadata"); |
| node.setAttribute("category",category); |
| node.setAttribute("all","true"); |
| ds.addChild(ds.getChildCount(),node); |
| } |
| else |
| { |
| String[] attributes = variableContext.getParameterValues("attributeselect"); |
| if (attributes != null && attributes.length > 0) |
| { |
| int k = 0; |
| while (k < attributes.length) |
| { |
| String attribute = attributes[k++]; |
| SpecificationNode node = new SpecificationNode("metadata"); |
| node.setAttribute("category",category); |
| node.setAttribute("attribute",attribute); |
| ds.addChild(ds.getChildCount(),node); |
| } |
| } |
| } |
| } |
| else if (op != null && op.equals("Up")) |
| { |
| // Strip off end |
| String category = variableContext.getParameter("speccategory"); |
| int lastSlash = -1; |
| int firstColon = -1; |
| int k = 0; |
| while (k < category.length()) |
| { |
| char x = category.charAt(k++); |
| if (x == '/') |
| { |
| lastSlash = k-1; |
| continue; |
| } |
| if (x == ':') |
| { |
| firstColon = k; |
| continue; |
| } |
| if (x == '\\') |
| k++; |
| } |
| |
| if (lastSlash == -1) |
| { |
| if (firstColon == -1 || firstColon == category.length()) |
| category = ""; |
| else |
| category = category.substring(0,firstColon); |
| } |
| else |
| category = category.substring(0,lastSlash); |
| currentContext.save("speccategory",category); |
| } |
| else if (op != null && op.equals("AddToPath")) |
| { |
| String category = variableContext.getParameter("speccategory"); |
| String addon = variableContext.getParameter("metadataaddon"); |
| if (addon != null && addon.length() > 0) |
| { |
| StringBuilder sb = new StringBuilder(); |
| int k = 0; |
| while (k < addon.length()) |
| { |
| char x = addon.charAt(k++); |
| if (x == '/' || x == '\\' || x == ':') |
| sb.append('\\'); |
| sb.append(x); |
| } |
| if (category.length() == 0 || category.endsWith(":")) |
| category += sb.toString(); |
| else |
| category += "/" + sb.toString(); |
| } |
| currentContext.save("speccategory",category); |
| } |
| else if (op != null && op.equals("SetWorkspace")) |
| { |
| String addon = variableContext.getParameter("metadataaddon"); |
| if (addon != null && addon.length() > 0) |
| { |
| StringBuilder sb = new StringBuilder(); |
| int k = 0; |
| while (k < addon.length()) |
| { |
| char x = addon.charAt(k++); |
| if (x == '/' || x == '\\' || x == ':') |
| sb.append('\\'); |
| sb.append(x); |
| } |
| |
| String category = sb.toString() + ":"; |
| currentContext.save("speccategory",category); |
| } |
| } |
| else if (op != null && op.equals("AddCategory")) |
| { |
| String category = variableContext.getParameter("speccategory"); |
| String addon = variableContext.getParameter("categoryaddon"); |
| if (addon != null && addon.length() > 0) |
| { |
| StringBuilder sb = new StringBuilder(); |
| int k = 0; |
| while (k < addon.length()) |
| { |
| char x = addon.charAt(k++); |
| if (x == '/' || x == '\\' || x == ':') |
| sb.append('\\'); |
| sb.append(x); |
| } |
| if (category.length() == 0 || category.endsWith(":")) |
| category += sb.toString(); |
| else |
| category += "/" + sb.toString(); |
| } |
| currentContext.save("speccategory",category); |
| } |
| } |
| |
| xc = variableContext.getParameter("specpathnameattribute"); |
| if (xc != null) |
| { |
| String separator = variableContext.getParameter("specpathnameseparator"); |
| if (separator == null) |
| separator = "/"; |
| // Delete old one |
| int i = 0; |
| while (i < ds.getChildCount()) |
| { |
| SpecificationNode sn = ds.getChild(i); |
| if (sn.getType().equals("pathnameattribute")) |
| ds.removeChild(i); |
| else |
| i++; |
| } |
| if (xc.length() > 0) |
| { |
| SpecificationNode node = new SpecificationNode("pathnameattribute"); |
| node.setAttribute("value",xc); |
| node.setAttribute("separator",separator); |
| ds.addChild(ds.getChildCount(),node); |
| } |
| } |
| |
| xc = variableContext.getParameter("specmappingcount"); |
| if (xc != null) |
| { |
| // Delete old spec |
| int i = 0; |
| while (i < ds.getChildCount()) |
| { |
| SpecificationNode sn = ds.getChild(i); |
| if (sn.getType().equals("pathmap")) |
| ds.removeChild(i); |
| else |
| i++; |
| } |
| |
| // Now, go through the data and assemble a new list. |
| int mappingCount = Integer.parseInt(xc); |
| |
| // Gather up these |
| i = 0; |
| while (i < mappingCount) |
| { |
| String pathDescription = "_"+Integer.toString(i); |
| String pathOpName = "specmappingop"+pathDescription; |
| xc = variableContext.getParameter(pathOpName); |
| if (xc != null && xc.equals("Delete")) |
| { |
| // Skip to the next |
| i++; |
| continue; |
| } |
| // Inserts won't happen until the very end |
| String match = variableContext.getParameter("specmatch"+pathDescription); |
| String replace = variableContext.getParameter("specreplace"+pathDescription); |
| SpecificationNode node = new SpecificationNode("pathmap"); |
| node.setAttribute("match",match); |
| node.setAttribute("replace",replace); |
| ds.addChild(ds.getChildCount(),node); |
| i++; |
| } |
| |
| // Check for add |
| xc = variableContext.getParameter("specmappingop"); |
| if (xc != null && xc.equals("Add")) |
| { |
| String match = variableContext.getParameter("specmatch"); |
| String replace = variableContext.getParameter("specreplace"); |
| SpecificationNode node = new SpecificationNode("pathmap"); |
| node.setAttribute("match",match); |
| node.setAttribute("replace",replace); |
| 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 |
| { |
| out.print( |
| "<table class=\"displaytable\">\n"+ |
| " <tr>\n" |
| ); |
| int i = 0; |
| boolean userWorkspaces = false; |
| while (i < ds.getChildCount()) |
| { |
| SpecificationNode sn = ds.getChild(i++); |
| if (sn.getType().equals("userworkspace")) |
| { |
| String value = sn.getAttributeValue("value"); |
| if (value != null && value.equals("true")) |
| userWorkspaces = true; |
| } |
| } |
| |
| out.print( |
| " <td class=\"description\"/>\n"+ |
| " <nobr>"+Messages.getBodyString(locale,"LivelinkConnector.CrawlUserWorkspaces")+"</nobr>\n"+ |
| " </td>\n"+ |
| " <td class=\"value\"/>\n"+ |
| " "+(userWorkspaces?Messages.getBodyString(locale,"LivelinkConnector.Yes"):Messages.getBodyString(locale,"LivelinkConnector.No"))+"\n"+ |
| " </td>\n"+ |
| " </tr>" |
| ); |
| out.print( |
| " <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n" |
| ); |
| out.print( |
| " <tr>" |
| ); |
| |
| i = 0; |
| boolean seenAny = false; |
| while (i < ds.getChildCount()) |
| { |
| SpecificationNode sn = ds.getChild(i++); |
| if (sn.getType().equals("startpoint")) |
| { |
| if (seenAny == false) |
| { |
| out.print( |
| " <td class=\"description\">"+Messages.getBodyString(locale,"LivelinkConnector.Roots")+"</td>\n"+ |
| " <td class=\"value\">\n" |
| ); |
| seenAny = true; |
| } |
| out.print( |
| " "+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(sn.getAttributeValue("path"))+"<br/>\n" |
| ); |
| } |
| } |
| |
| if (seenAny) |
| { |
| out.print( |
| " </td>\n"+ |
| " </tr>\n" |
| ); |
| } |
| else |
| { |
| out.print( |
| " <tr><td class=\"message\" colspan=\"2\">"+Messages.getBodyString(locale,"LivelinkConnector.NoStartPointsSpecified")+"</td></tr>\n" |
| ); |
| } |
| out.print( |
| " <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n" |
| ); |
| |
| seenAny = false; |
| // Go through looking for include or exclude file specs |
| i = 0; |
| while (i < ds.getChildCount()) |
| { |
| SpecificationNode sn = ds.getChild(i++); |
| if (sn.getType().equals("include") || sn.getType().equals("exclude")) |
| { |
| if (seenAny == false) |
| { |
| out.print( |
| " <tr><td class=\"description\">"+Messages.getBodyString(locale,"LivelinkConnector.FileSpecs")+"</td>\n"+ |
| " <td class=\"value\">\n" |
| ); |
| seenAny = true; |
| } |
| String filespec = sn.getAttributeValue("filespec"); |
| out.print( |
| " "+(sn.getType().equals("include")?"Include file:":"")+"\n"+ |
| " "+(sn.getType().equals("exclude")?"Exclude file:":"")+"\n"+ |
| " "+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(filespec)+"<br/>\n" |
| ); |
| } |
| } |
| |
| if (seenAny) |
| { |
| out.print( |
| " </td>\n"+ |
| " </tr>\n" |
| ); |
| } |
| else |
| { |
| out.print( |
| " <tr><td class=\"message\" colspan=\"2\">"+Messages.getBodyString(locale,"LivelinkConnector.NoFileSpecsSpecified")+"</td></tr>\n" |
| ); |
| } |
| out.print( |
| " <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,"LivelinkConnector.SecurityColon")+"</td>\n"+ |
| " <td class=\"value\">"+(securityOn?Messages.getBodyString(locale,"LivelinkConnector.Enabled2"):Messages.getBodyString(locale,"LivelinkConnector.Disabled"))+"</td>\n"+ |
| " </tr>\n"+ |
| "\n"+ |
| " <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n" |
| ); |
| // Go through looking for access tokens |
| 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\">"+Messages.getBodyString(locale,"LivelinkConnector.AccessTokens")+"</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\">" + Messages.getBodyString(locale,"LivelinkConnector.NoAccessTokensSpecified") + "</td></tr>\n" |
| ); |
| } |
| out.print( |
| " <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n" |
| ); |
| i = 0; |
| String allMetadata = Messages.getBodyString(locale,"LivelinkConnector.OnlySpecifiedMetadataWillBeIngested"); |
| while (i < ds.getChildCount()) |
| { |
| SpecificationNode sn = ds.getChild(i++); |
| if (sn.getType().equals("allmetadata")) |
| { |
| String value = sn.getAttributeValue("all"); |
| if (value != null && value.equals("true")) |
| { |
| allMetadata=Messages.getBodyString(locale,"LivelinkConnector.AllDocumentMetadataWillBeIngested"); |
| } |
| } |
| } |
| out.print( |
| " <tr>\n"+ |
| " <td class=\"description\"><nobr>"+Messages.getBodyString(locale,"LivelinkConnector.MetadataSpecification")+"</nobr></td>\n"+ |
| " <td class=\"value\"><nobr>"+allMetadata+"</nobr></td>\n"+ |
| " </tr>\n"+ |
| " <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n" |
| ); |
| // Go through looking for metadata spec |
| seenAny = false; |
| i = 0; |
| while (i < ds.getChildCount()) |
| { |
| SpecificationNode sn = ds.getChild(i++); |
| if (sn.getType().equals("metadata")) |
| { |
| if (seenAny == false) |
| { |
| out.print( |
| " <tr><td class=\"description\"><nobr>"+Messages.getBodyString(locale,"LivelinkConnector.SpecificMetadata")+"</nobr></td>\n"+ |
| " <td class=\"value\">\n" |
| ); |
| seenAny = true; |
| } |
| String category = sn.getAttributeValue("category"); |
| String attribute = sn.getAttributeValue("attribute"); |
| String isAll = sn.getAttributeValue("all"); |
| out.print( |
| " "+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(category)+":"+((isAll!=null&&isAll.equals("true"))?"(All metadata attributes)":org.apache.manifoldcf.ui.util.Encoder.bodyEscape(attribute))+"<br/>\n" |
| ); |
| } |
| } |
| |
| if (seenAny) |
| { |
| out.print( |
| " </td>\n"+ |
| " </tr>\n" |
| ); |
| } |
| else |
| { |
| out.print( |
| " <tr><td class=\"message\" colspan=\"2\">"+Messages.getBodyString(locale,"LivelinkConnector.NoMetadataSpecified")+"</td></tr>\n" |
| ); |
| } |
| out.print( |
| " <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n" |
| ); |
| // Find the path-name metadata attribute name |
| i = 0; |
| String pathNameAttribute = ""; |
| String pathSeparator = "/"; |
| while (i < ds.getChildCount()) |
| { |
| SpecificationNode sn = ds.getChild(i++); |
| if (sn.getType().equals("pathnameattribute")) |
| { |
| pathNameAttribute = sn.getAttributeValue("value"); |
| if (sn.getAttributeValue("separator") != null) |
| pathSeparator = sn.getAttributeValue("separator"); |
| } |
| } |
| if (pathNameAttribute.length() > 0) |
| { |
| out.print( |
| " <tr>\n"+ |
| " <td class=\"description\">"+Messages.getBodyString(locale,"LivelinkConnector.PathNameMetadataAttribute")+"</td>\n"+ |
| " <td class=\"value\">"+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(pathNameAttribute)+"</td>\n"+ |
| " </tr>\n"+ |
| " <tr>\n"+ |
| " <td class=\"description\">"+Messages.getBodyString(locale,"LivelinkConnector.PathSeparatorString")+"</td>\n"+ |
| " <td class=\"value\">"+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(pathSeparator)+"</td>\n"+ |
| " </tr>\n" |
| ); |
| } |
| else |
| { |
| out.print( |
| " <tr>\n"+ |
| " <td class=\"message\" colspan=\"2\">"+Messages.getBodyString(locale,"LivelinkConnector.NoPathNameMetadataAttributeSpecified")+"</td>\n"+ |
| " </tr>\n" |
| ); |
| } |
| out.print( |
| "\n"+ |
| " <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n"+ |
| "\n"+ |
| " <tr>\n" |
| ); |
| // Find the path-value mapping data |
| i = 0; |
| MatchMap matchMap = new MatchMap(); |
| while (i < ds.getChildCount()) |
| { |
| SpecificationNode sn = ds.getChild(i++); |
| if (sn.getType().equals("pathmap")) |
| { |
| String pathMatch = sn.getAttributeValue("match"); |
| String pathReplace = sn.getAttributeValue("replace"); |
| matchMap.appendMatchPair(pathMatch,pathReplace); |
| } |
| } |
| if (matchMap.getMatchCount() > 0) |
| { |
| out.print( |
| " <td class=\"description\">"+Messages.getBodyString(locale,"LivelinkConnector.PathValueMapping")+"</td>\n"+ |
| " <td class=\"value\">\n"+ |
| " <table class=\"displaytable\">\n" |
| ); |
| i = 0; |
| while (i < matchMap.getMatchCount()) |
| { |
| String matchString = matchMap.getMatchString(i); |
| String replaceString = matchMap.getReplaceString(i); |
| out.print( |
| " <tr>\n"+ |
| " <td class=\"value\">"+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(matchString)+"</td>\n"+ |
| " <td class=\"value\">--></td>\n"+ |
| " <td class=\"value\">"+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(replaceString)+"</td>\n"+ |
| " </tr>\n" |
| ); |
| i++; |
| } |
| out.print( |
| " </table>\n"+ |
| " </td>\n" |
| ); |
| } |
| else |
| { |
| out.print( |
| " <td class=\"message\" colspan=\"2\">"+Messages.getBodyString(locale,"LivelinkConnector.NoMappingsSpecified")+"</td>\n" |
| ); |
| } |
| out.print( |
| " </tr>\n"+ |
| "</table>\n" |
| ); |
| } |
| |
| // The following public methods are NOT part of the interface. They are here so that the UI can present information |
| // that will allow users to select what they need. |
| |
| protected final static String CATEGORY_NAME = "CATEGORY"; |
| protected final static String ENTWKSPACE_NAME = "ENTERPRISE"; |
| |
| /** Get the allowed workspace names. |
| *@return a list of workspace names. |
| */ |
| public String[] getWorkspaceNames() |
| throws ManifoldCFException, ServiceInterruption |
| { |
| return new String[]{CATEGORY_NAME,ENTWKSPACE_NAME}; |
| } |
| |
| /** Given a path string, get a list of folders and projects under that node. |
| *@param pathString is the current path (folder names and project names, separated by dots (.)). |
| *@return a list of folder and project names, in sorted order, or null if the path was invalid. |
| */ |
| public String[] getChildFolderNames(String pathString) |
| throws ManifoldCFException, ServiceInterruption |
| { |
| getSession(); |
| return getChildFolders(new LivelinkContext(),pathString); |
| } |
| |
| |
| /** Given a path string, get a list of categories under that node. |
| *@param pathString is the current path (folder names and project names, separated by dots (.)). |
| *@return a list of category names, in sorted order, or null if the path was invalid. |
| */ |
| public String[] getChildCategoryNames(String pathString) |
| throws ManifoldCFException, ServiceInterruption |
| { |
| getSession(); |
| return getChildCategories(new LivelinkContext(),pathString); |
| } |
| |
| /** Given a category path, get a list of legal attribute names. |
| *@param pathString is the current path of a category (with path components separated by dots). |
| *@return a list of attribute names, in sorted order, or null of the path was invalid. |
| */ |
| public String[] getCategoryAttributes(String pathString) |
| throws ManifoldCFException, ServiceInterruption |
| { |
| getSession(); |
| return getCategoryAttributes(new LivelinkContext(), pathString); |
| } |
| |
| protected String[] getCategoryAttributes(LivelinkContext llc, String pathString) |
| throws ManifoldCFException, ServiceInterruption |
| { |
| // Start at root |
| RootValue rv = new RootValue(llc,pathString); |
| |
| // Get the object id of the category the path describes |
| int catObjectID = getCategoryId(rv); |
| if (catObjectID == -1) |
| return null; |
| |
| String[] rval = getCategoryAttributes(catObjectID); |
| if (rval == null) |
| return new String[0]; |
| return rval; |
| } |
| |
| // Protected methods and classes |
| |
| |
| /** Create the login URI. This must be a relative URI. |
| */ |
| protected String createLivelinkLoginURI() |
| throws ManifoldCFException |
| { |
| StringBuilder llURI = new StringBuilder(); |
| |
| llURI.append(ingestCgiPath); |
| llURI.append("?func=ll.login&CurrentClientTime=D%2F2005%2F3%2F9%3A13%3A16%3A30&NextURL="); |
| llURI.append(org.apache.manifoldcf.core.util.URLEncoder.encode(ingestCgiPath)); |
| llURI.append("%3FRedirect%3D1&Username="); |
| llURI.append(org.apache.manifoldcf.core.util.URLEncoder.encode(llServer.getLLUser())); |
| llURI.append("&Password="); |
| llURI.append(org.apache.manifoldcf.core.util.URLEncoder.encode(llServer.getLLPwd())); |
| |
| return llURI.toString(); |
| } |
| |
| /** |
| * Connects to the specified Livelink document using HTTP protocol |
| * @param documentIdentifier is the document identifier (as far as the crawler knows). |
| * @param activities is the process activity structure, so we can ingest |
| */ |
| protected void ingestFromLiveLink(LivelinkContext llc, |
| String documentIdentifier, String version, |
| IProcessActivity activities, |
| MetadataDescription desc, SystemMetadataDescription sDesc) |
| throws ManifoldCFException, ServiceInterruption |
| { |
| |
| String contextMsg = "for '"+documentIdentifier+"'"; |
| |
| String viewHttpAddress = convertToViewURI(documentIdentifier); |
| if (viewHttpAddress == null) |
| { |
| if (Logging.connectors.isDebugEnabled()) |
| Logging.connectors.debug("Livelink: No view URI "+contextMsg+" - not ingesting"); |
| return; |
| } |
| |
| // Fetch logging |
| long startTime = System.currentTimeMillis(); |
| String resultCode = "FAILED"; |
| String resultDescription = null; |
| Long readSize = null; |
| boolean wasInterrupted = false; |
| int objID; |
| int vol; |
| |
| int colonPos = documentIdentifier.indexOf(":",1); |
| |
| if (colonPos == -1) |
| { |
| objID = new Integer(documentIdentifier.substring(1)).intValue(); |
| vol = LLENTWK_VOL; |
| } |
| else |
| { |
| objID = new Integer(documentIdentifier.substring(colonPos+1)).intValue(); |
| vol = new Integer(documentIdentifier.substring(1,colonPos)).intValue(); |
| } |
| |
| // Try/finally for fetch logging |
| try |
| { |
| // Check URL first |
| if (activities.checkURLIndexable(viewHttpAddress)) |
| { |
| |
| // Add general metadata |
| ObjectInformation objInfo = llc.getObjectInformation(vol,objID); |
| VersionInformation versInfo = llc.getVersionInformation(vol,objID,0); |
| if (!objInfo.exists()) |
| { |
| resultCode = "OBJECTNOTFOUND"; |
| Logging.connectors.debug("Livelink: No object "+contextMsg+": not ingesting"); |
| return; |
| } |
| if (!versInfo.exists()) |
| { |
| resultCode = "VERSIONNOTFOUND"; |
| Logging.connectors.debug("Livelink: No version data "+contextMsg+": not ingesting"); |
| return; |
| } |
| |
| String mimeType = versInfo.getMimeType(); |
| if (activities.checkMimeTypeIndexable(mimeType)) |
| { |
| Long dataSize = versInfo.getDataSize(); |
| if (dataSize != null && activities.checkLengthIndexable(dataSize.longValue())) |
| { |
| String fileName = versInfo.getFileName(); |
| Date creationDate = objInfo.getCreationDate(); |
| Date modifyDate = versInfo.getModifyDate(); |
| Integer parentID = objInfo.getParentId(); |
| RepositoryDocument rd = new RepositoryDocument(); |
| |
| |
| // Add general data we need for the output connector |
| if (mimeType != null) |
| rd.setMimeType(mimeType); |
| if (fileName != null) |
| rd.setFileName(fileName); |
| if (creationDate != null) |
| rd.setCreatedDate(creationDate); |
| if (modifyDate != null) |
| rd.setModifiedDate(modifyDate); |
| |
| rd.addField(GENERAL_NAME_FIELD,objInfo.getName()); |
| rd.addField(GENERAL_DESCRIPTION_FIELD,objInfo.getComments()); |
| if (creationDate != null) |
| rd.addField(GENERAL_CREATIONDATE_FIELD,creationDate.toString()); |
| if (modifyDate != null) |
| rd.addField(GENERAL_MODIFYDATE_FIELD,modifyDate.toString()); |
| if (parentID != null) |
| rd.addField(GENERAL_PARENTID,parentID.toString()); |
| UserInformation owner = llc.getUserInformation(objInfo.getOwnerId().intValue()); |
| UserInformation creator = llc.getUserInformation(objInfo.getCreatorId().intValue()); |
| UserInformation modifier = llc.getUserInformation(versInfo.getOwnerId().intValue()); |
| if (owner != null) |
| rd.addField(GENERAL_OWNER,owner.getName()); |
| if (creator != null) |
| rd.addField(GENERAL_CREATOR,creator.getName()); |
| if (modifier != null) |
| rd.addField(GENERAL_MODIFIER,modifier.getName()); |
| |
| // Iterate over the metadata items. These are organized by category |
| // for speed of lookup. |
| |
| // Unpack version string |
| int startPos = 0; |
| |
| // Metadata items first |
| ArrayList metadataItems = new ArrayList(); |
| startPos = unpackList(metadataItems,version,startPos,'+'); |
| Iterator catIter = desc.getItems(metadataItems); |
| while (catIter.hasNext()) |
| { |
| MetadataItem item = (MetadataItem)catIter.next(); |
| MetadataPathItem pathItem = item.getPathItem(); |
| if (pathItem != null) |
| { |
| int catID = pathItem.getCatID(); |
| // grab the associated catversion |
| LLValue catVersion = getCatVersion(objID,catID); |
| if (catVersion != null) |
| { |
| // Go through attributes now |
| Iterator attrIter = item.getAttributeNames(); |
| while (attrIter.hasNext()) |
| { |
| String attrName = (String)attrIter.next(); |
| // Create a unique metadata name |
| String metadataName = pathItem.getCatName()+":"+attrName; |
| // Fetch the metadata and stuff it into the RepositoryData structure |
| String[] metadataValue = getAttributeValue(catVersion,attrName); |
| if (metadataValue != null) |
| rd.addField(metadataName,metadataValue); |
| else |
| Logging.connectors.warn("Livelink: Metadata attribute '"+metadataName+"' does not seem to exist; please correct the job"); |
| } |
| } |
| |
| } |
| } |
| |
| // Unpack acls (conditionally) |
| if (startPos < version.length()) |
| { |
| char x = version.charAt(startPos++); |
| if (x == '+') |
| { |
| ArrayList acls = new ArrayList(); |
| startPos = unpackList(acls,version,startPos,'+'); |
| // Turn into acls and add into description |
| String[] aclArray = new String[acls.size()]; |
| int j = 0; |
| while (j < aclArray.length) |
| { |
| aclArray[j] = (String)acls.get(j); |
| j++; |
| } |
| |
| StringBuilder denyBuffer = new StringBuilder(); |
| startPos = unpack(denyBuffer,version,startPos,'+'); |
| String denyAcl = denyBuffer.toString(); |
| String[] denyAclArray = new String[1]; |
| denyAclArray[0] = denyAcl; |
| |
| rd.setSecurity(RepositoryDocument.SECURITY_TYPE_DOCUMENT,aclArray,denyAclArray); |
| } |
| } |
| |
| // Add the path metadata item into the mix, if enabled |
| String pathAttributeName = sDesc.getPathAttributeName(); |
| if (pathAttributeName != null && pathAttributeName.length() > 0) |
| { |
| String pathString = sDesc.getPathAttributeValue(documentIdentifier); |
| if (pathString != null) |
| { |
| if (Logging.connectors.isDebugEnabled()) |
| Logging.connectors.debug("Livelink: Path attribute name is '"+pathAttributeName+"'"+contextMsg+", value is '"+pathString+"'"); |
| rd.addField(pathAttributeName,pathString); |
| } |
| } |
| |
| if (ingestProtocol != null) |
| { |
| // Use HTTP to fetch document! |
| String ingestHttpAddress = convertToIngestURI(documentIdentifier); |
| if (ingestHttpAddress != null) |
| { |
| |
| // Set up connection |
| HttpClient client = getInitializedClient(contextMsg); |
| |
| long currentTime; |
| |
| if (Logging.connectors.isInfoEnabled()) |
| Logging.connectors.info("Livelink: " + ingestHttpAddress); |
| |
| |
| HttpGet method = new HttpGet(getHost().toURI() + ingestHttpAddress); |
| method.setHeader(new BasicHeader("Accept","*/*")); |
| |
| ExecuteMethodThread methodThread = new ExecuteMethodThread(client,method); |
| methodThread.start(); |
| try |
| { |
| |
| int statusCode = methodThread.getResponseCode(); |
| switch (statusCode) |
| { |
| case 500: |
| case 502: |
| Logging.connectors.warn("Livelink: Service interruption during fetch "+contextMsg+" with Livelink HTTP Server, retrying..."); |
| throw new ServiceInterruption("Service interruption during fetch",new ManifoldCFException(Integer.toString(statusCode)+" error while fetching"),System.currentTimeMillis()+60000L, |
| System.currentTimeMillis()+600000L,-1,true); |
| |
| case HttpStatus.SC_UNAUTHORIZED: |
| Logging.connectors.warn("Livelink: Document fetch unauthorized for "+ingestHttpAddress+" ("+contextMsg+")"); |
| // Since we logged in, we should fail here if the ingestion user doesn't have access to the |
| // the document, but if we do, don't fail hard. |
| resultCode = "UNAUTHORIZED"; |
| activities.noDocument(documentIdentifier,version); |
| return; |
| |
| case HttpStatus.SC_OK: |
| if (Logging.connectors.isDebugEnabled()) |
| Logging.connectors.debug("Livelink: Created http document connection to Livelink "+contextMsg); |
| // A non-existent content length will cause a value of -1 to be returned. This seems to indicate that the session login did not work right. |
| if (methodThread.getResponseContentLength() >= 0) |
| { |
| try |
| { |
| InputStream is = methodThread.getSafeInputStream(); |
| try |
| { |
| rd.setBinary(is,dataSize); |
| |
| activities.ingestDocumentWithException(documentIdentifier,version,viewHttpAddress,rd); |
| |
| if (Logging.connectors.isDebugEnabled()) |
| Logging.connectors.debug("Livelink: Ingesting done "+contextMsg); |
| |
| } |
| finally |
| { |
| // Close stream via thread, since otherwise this can hang |
| is.close(); |
| } |
| } |
| catch (java.net.SocketTimeoutException e) |
| { |
| resultCode = "DATATIMEOUT"; |
| resultDescription = e.getMessage(); |
| currentTime = System.currentTimeMillis(); |
| Logging.connectors.warn("Livelink: Livelink socket timed out ingesting from the Livelink HTTP Server "+contextMsg+": "+e.getMessage(), e); |
| throw new ServiceInterruption("Socket timed out: "+e.getMessage(),e,currentTime+300000L,currentTime+6*3600000L,-1,false); |
| } |
| catch (java.net.SocketException e) |
| { |
| resultCode = "DATASOCKETERROR"; |
| resultDescription = e.getMessage(); |
| currentTime = System.currentTimeMillis(); |
| Logging.connectors.warn("Livelink: Livelink socket error ingesting from the Livelink HTTP Server "+contextMsg+": "+e.getMessage(), e); |
| throw new ServiceInterruption("Socket error: "+e.getMessage(),e,currentTime+300000L,currentTime+6*3600000L,-1,false); |
| } |
| catch (javax.net.ssl.SSLHandshakeException e) |
| { |
| resultCode = "DATASSLHANDSHAKEERROR"; |
| resultDescription = e.getMessage(); |
| currentTime = System.currentTimeMillis(); |
| Logging.connectors.warn("Livelink: SSL handshake failed authenticating "+contextMsg+": "+e.getMessage(),e); |
| throw new ServiceInterruption("SSL handshake error: "+e.getMessage(),e,currentTime+60000L,currentTime+300000L,-1,true); |
| } |
| catch (ConnectTimeoutException e) |
| { |
| resultCode = "CONNECTTIMEOUT"; |
| resultDescription = e.getMessage(); |
| currentTime = System.currentTimeMillis(); |
| Logging.connectors.warn("Livelink: Livelink socket timed out connecting to the Livelink HTTP Server "+contextMsg+": "+e.getMessage(), e); |
| throw new ServiceInterruption("Connect timed out: "+e.getMessage(),e,currentTime+300000L,currentTime+6*3600000L,-1,false); |
| } |
| catch (InterruptedException e) |
| { |
| wasInterrupted = true; |
| throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED); |
| } |
| catch (InterruptedIOException e) |
| { |
| wasInterrupted = true; |
| throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED); |
| } |
| catch (HttpException e) |
| { |
| resultCode = "HTTPEXCEPTION"; |
| resultDescription = e.getMessage(); |
| // Treat unknown error ingesting data as a transient condition |
| currentTime = System.currentTimeMillis(); |
| Logging.connectors.warn("Livelink: HTTP exception ingesting "+contextMsg+": "+e.getMessage(),e); |
| throw new ServiceInterruption("HTTP exception ingesting "+contextMsg+": "+e.getMessage(),e,currentTime+300000L,currentTime+6*3600000L,-1,false); |
| } |
| catch (IOException e) |
| { |
| resultCode = "DATAEXCEPTION"; |
| resultDescription = e.getMessage(); |
| // Treat unknown error ingesting data as a transient condition |
| currentTime = System.currentTimeMillis(); |
| Logging.connectors.warn("Livelink: IO exception ingesting "+contextMsg+": "+e.getMessage(),e); |
| throw new ServiceInterruption("IO exception ingesting "+contextMsg+": "+e.getMessage(),e,currentTime+300000L,currentTime+6*3600000L,-1,false); |
| } |
| readSize = dataSize; |
| } |
| else |
| { |
| resultCode = "SESSIONLOGINFAILED"; |
| activities.noDocument(documentIdentifier,version); |
| } |
| break; |
| case HttpStatus.SC_BAD_REQUEST: |
| case HttpStatus.SC_USE_PROXY: |
| case HttpStatus.SC_GONE: |
| resultCode = "ERROR "+Integer.toString(statusCode); |
| throw new ManifoldCFException("Unrecoverable request failure; error = "+Integer.toString(statusCode)); |
| default: |
| resultCode = "UNKNOWN"; |
| Logging.connectors.warn("Livelink: Attempt to retrieve document from '"+ingestHttpAddress+"' received a response of "+Integer.toString(statusCode)+"; retrying in one minute"); |
| currentTime = System.currentTimeMillis(); |
| throw new ServiceInterruption("Fetch failed; retrying in 1 minute",new ManifoldCFException("Fetch failed with unknown code "+Integer.toString(statusCode)), |
| currentTime+60000L,currentTime+600000L,-1,true); |
| } |
| } |
| catch (InterruptedException e) |
| { |
| // Drop the connection on the floor |
| methodThread.interrupt(); |
| methodThread = null; |
| throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED); |
| } |
| catch (java.net.SocketTimeoutException e) |
| { |
| Logging.connectors.warn("Livelink: Socket timed out reading from the Livelink HTTP Server "+contextMsg+": "+e.getMessage(), e); |
| resultCode = "TIMEOUT"; |
| resultDescription = e.getMessage(); |
| currentTime = System.currentTimeMillis(); |
| throw new ServiceInterruption("Socket timed out: "+e.getMessage(),e,currentTime+300000L,currentTime+6*3600000L,-1,true); |
| } |
| catch (java.net.SocketException e) |
| { |
| Logging.connectors.warn("Livelink: Socket error reading from Livelink HTTP Server "+contextMsg+": "+e.getMessage(), e); |
| resultCode = "SOCKETERROR"; |
| resultDescription = e.getMessage(); |
| currentTime = System.currentTimeMillis(); |
| throw new ServiceInterruption("Socket error: "+e.getMessage(),e,currentTime+300000L,currentTime+6*3600000L,-1,true); |
| } |
| catch (javax.net.ssl.SSLHandshakeException e) |
| { |
| currentTime = System.currentTimeMillis(); |
| Logging.connectors.warn("Livelink: SSL handshake failed "+contextMsg+": "+e.getMessage(),e); |
| resultCode = "SSLHANDSHAKEERROR"; |
| resultDescription = e.getMessage(); |
| throw new ServiceInterruption("SSL handshake error: "+e.getMessage(),e,currentTime+60000L,currentTime+300000L,-1,true); |
| } |
| catch (ConnectTimeoutException e) |
| { |
| Logging.connectors.warn("Livelink: Connect timed out reading from the Livelink HTTP Server "+contextMsg+": "+e.getMessage(), e); |
| resultCode = "CONNECTTIMEOUT"; |
| resultDescription = e.getMessage(); |
| currentTime = System.currentTimeMillis(); |
| throw new ServiceInterruption("Connect timed out: "+e.getMessage(),e,currentTime+300000L,currentTime+6*3600000L,-1,true); |
| } |
| catch (InterruptedIOException e) |
| { |
| methodThread.interrupt(); |
| throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED); |
| } |
| catch (HttpException e) |
| { |
| resultCode = "EXCEPTION"; |
| resultDescription = e.getMessage(); |
| throw new ManifoldCFException("Exception getting response "+contextMsg+": "+e.getMessage(), e); |
| } |
| catch (IOException e) |
| { |
| resultCode = "EXCEPTION"; |
| resultDescription = e.getMessage(); |
| throw new ManifoldCFException("Exception getting response "+contextMsg+": "+e.getMessage(), e); |
| } |
| finally |
| { |
| if (methodThread != null) |
| { |
| methodThread.abort(); |
| if (!wasInterrupted) |
| { |
| try |
| { |
| methodThread.finishUp(); |
| } |
| catch (InterruptedException e) |
| { |
| wasInterrupted = true; |
| throw new ManifoldCFException(e.getMessage(),e,ManifoldCFException.INTERRUPTED); |
| } |
| } |
| } |
| } |
| } |
| else |
| { |
| if (Logging.connectors.isDebugEnabled()) |
| Logging.connectors.debug("Livelink: No fetch URI "+contextMsg+" - not ingesting"); |
| resultCode = "NOURI"; |
| return; |
| } |
| } |
| else |
| { |
| // Use FetchVersion instead |
| long currentTime; |
| |
| // Fire up the document reading thread |
| DocumentReadingThread t = new DocumentReadingThread(vol,objID,0); |
| try |
| { |
| t.start(); |
| try |
| { |
| InputStream is = t.getSafeInputStream(); |
| try |
| { |
| // Can only index while background thread is running! |
| rd.setBinary(is, dataSize); |
| activities.ingestDocumentWithException(documentIdentifier, version, viewHttpAddress, rd); |
| } |
| finally |
| { |
| is.close(); |
| } |
| } |
| catch (ManifoldCFException e) |
| { |
| if (e.getErrorCode() == ManifoldCFException.INTERRUPTED) |
| wasInterrupted = true; |
| throw e; |
| } |
| catch (java.net.SocketTimeoutException e) |
| { |
| throw e; |
| } |
| catch (InterruptedIOException e) |
| { |
| wasInterrupted = true; |
| throw e; |
| } |
| finally |
| { |
| if (!wasInterrupted) |
| t.finishUp(); |
| } |
| |
| // No errors. Record the fact that we made it. |
| resultCode = "OK"; |
| readSize = dataSize; |
| } |
| catch (InterruptedException e) |
| { |
| t.interrupt(); |
| throw new ManifoldCFException("Interrupted: " + e.getMessage(), e, |
| ManifoldCFException.INTERRUPTED); |
| } |
| catch (ConnectTimeoutException e) |
| { |
| Logging.connectors.warn("Livelink: Connect timed out "+contextMsg+": "+e.getMessage(), e); |
| resultCode = "CONNECTTIMEOUT"; |
| resultDescription = e.getMessage(); |
| currentTime = System.currentTimeMillis(); |
| throw new ServiceInterruption("Connect timed out: "+e.getMessage(),e,currentTime+300000L,currentTime+6*3600000L,-1,true); |
| } |
| catch (InterruptedIOException e) |
| { |
| t.interrupt(); |
| throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED); |
| } |
| catch (IOException e) |
| { |
| resultCode = "EXCEPTION"; |
| resultDescription = e.getMessage(); |
| throw new ManifoldCFException("Exception getting response "+contextMsg+": "+e.getMessage(), e); |
| } |
| catch (ManifoldCFException e) |
| { |
| if (e.getErrorCode() != ManifoldCFException.INTERRUPTED) |
| { |
| resultCode = "EXCEPTION"; |
| resultDescription = e.getMessage(); |
| } |
| throw e; |
| } |
| catch (RuntimeException e) |
| { |
| resultCode = "EXCEPTION"; |
| resultDescription = e.getMessage(); |
| handleLivelinkRuntimeException(e,0,true); |
| } |
| } |
| } |
| else |
| { |
| // Document not indexable because of its length |
| resultDescription = "Document length ("+dataSize+") was rejected by output connector"; |
| if (Logging.connectors.isDebugEnabled()) |
| Logging.connectors.debug("Livelink: Excluding document "+documentIdentifier+" because its length ("+dataSize+") was rejected by output connector"); |
| resultCode = "DOCUMENTTOOLONG"; |
| activities.noDocument(documentIdentifier,version); |
| } |
| } |
| else |
| { |
| // Document not indexable because of its mime type |
| resultDescription = "Mime type ("+mimeType+") was rejected by output connector"; |
| if (Logging.connectors.isDebugEnabled()) |
| Logging.connectors.debug("Livelink: Excluding document "+documentIdentifier+" because its mime type ("+mimeType+") was rejected by output connector"); |
| resultCode = "MIMETYPEEXCLUSION"; |
| activities.noDocument(documentIdentifier,version); |
| } |
| } |
| else |
| { |
| // Document not ingestable due to URL |
| resultDescription = "URL ("+viewHttpAddress+") was rejected by output connector"; |
| if (Logging.connectors.isDebugEnabled()) |
| Logging.connectors.debug("Livelink: Excluding document "+documentIdentifier+" because its URL ("+viewHttpAddress+") was rejected by output connector"); |
| resultCode = "URLEXCLUSION"; |
| activities.noDocument(documentIdentifier,version); |
| } |
| } |
| finally |
| { |
| if (!wasInterrupted) |
| activities.recordActivity(new Long(startTime),ACTIVITY_FETCH,readSize,vol+":"+objID,resultCode,resultDescription,null); |
| } |
| } |
| |
| /** Initialize a livelink client connection */ |
| protected HttpClient getInitializedClient(String contextMsg) |
| throws ServiceInterruption, ManifoldCFException |
| { |
| long currentTime; |
| if (Logging.connectors.isDebugEnabled()) |
| Logging.connectors.debug("Livelink: Session authenticating via http "+contextMsg+"..."); |
| HttpGet authget = new HttpGet(getHost().toURI() + createLivelinkLoginURI()); |
| authget.setHeader(new BasicHeader("Accept","*/*")); |
| try |
| { |
| if (Logging.connectors.isDebugEnabled()) |
| Logging.connectors.debug("Livelink: Created new HttpGet "+contextMsg+"; executing authentication method"); |
| int statusCode = executeMethodViaThread(httpClient,authget); |
| |
| if (statusCode == 502 || statusCode == 500) |
| { |
| Logging.connectors.warn("Livelink: Service interruption during authentication "+contextMsg+" with Livelink HTTP Server, retrying..."); |
| currentTime = System.currentTimeMillis(); |
| throw new ServiceInterruption("502 error during authentication",new ManifoldCFException("502 error while authenticating"), |
| currentTime+60000L,currentTime+600000L,-1,true); |
| } |
| if (statusCode != HttpStatus.SC_OK) |
| { |
| Logging.connectors.error("Livelink: Failed to authenticate "+contextMsg+" against Livelink HTTP Server; Status code: " + statusCode); |
| // Ok, so we didn't get in - simply do not ingest |
| if (statusCode == HttpStatus.SC_UNAUTHORIZED) |
| throw new ManifoldCFException("Session authorization failed with a 401 code; are credentials correct?"); |
| else |
| throw new ManifoldCFException("Session authorization failed with code "+Integer.toString(statusCode)); |
| } |
| } |
| catch (InterruptedException e) |
| { |
| throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED); |
| } |
| catch (java.net.SocketTimeoutException e) |
| { |
| currentTime = System.currentTimeMillis(); |
| Logging.connectors.warn("Livelink: Socket timed out authenticating to the Livelink HTTP Server "+contextMsg+": "+e.getMessage(), e); |
| throw new ServiceInterruption("Socket timed out: "+e.getMessage(),e,currentTime+300000L,currentTime+6*3600000L,-1,true); |
| } |
| catch (java.net.SocketException e) |
| { |
| currentTime = System.currentTimeMillis(); |
| Logging.connectors.warn("Livelink: Socket error authenticating to the Livelink HTTP Server "+contextMsg+": "+e.getMessage(), e); |
| throw new ServiceInterruption("Socket error: "+e.getMessage(),e,currentTime+300000L,currentTime+6*3600000L,-1,true); |
| } |
| catch (javax.net.ssl.SSLHandshakeException e) |
| { |
| currentTime = System.currentTimeMillis(); |
| Logging.connectors.warn("Livelink: SSL handshake failed authenticating "+contextMsg+": "+e.getMessage(),e); |
| throw new ServiceInterruption("SSL handshake error: "+e.getMessage(),e,currentTime+60000L,currentTime+300000L,-1,true); |
| } |
| catch (ConnectTimeoutException e) |
| { |
| currentTime = System.currentTimeMillis(); |
| Logging.connectors.warn("Livelink: Connect timed out authenticating to the Livelink HTTP Server "+contextMsg+": "+e.getMessage(), e); |
| throw new ServiceInterruption("Connect timed out: "+e.getMessage(),e,currentTime+300000L,currentTime+6*3600000L,-1,true); |
| } |
| catch (InterruptedIOException e) |
| { |
| throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED); |
| } |
| catch (HttpException e) |
| { |
| Logging.connectors.error("Livelink: HTTP exception when authenticating to the Livelink HTTP Server "+contextMsg+": "+e.getMessage(), e); |
| throw new ManifoldCFException("Unable to communicate with the Livelink HTTP Server: "+e.getMessage(), e); |
| } |
| catch (IOException e) |
| { |
| Logging.connectors.error("Livelink: IO exception when authenticating to the Livelink HTTP Server "+contextMsg+": "+e.getMessage(), e); |
| throw new ManifoldCFException("Unable to communicate with the Livelink HTTP Server: "+e.getMessage(), e); |
| } |
| |
| return httpClient; |
| } |
| |
| /** Pack category and attribute */ |
| protected static String packCategoryAttribute(String category, String attribute) |
| { |
| StringBuilder sb = new StringBuilder(); |
| pack(sb,category,':'); |
| pack(sb,attribute,':'); |
| return sb.toString(); |
| } |
| |
| /** Unpack category and attribute */ |
| protected static void unpackCategoryAttribute(StringBuilder category, StringBuilder attribute, String value) |
| { |
| int startPos = 0; |
| startPos = unpack(category,value,startPos,':'); |
| startPos = unpack(attribute,value,startPos,':'); |
| } |
| |
| /** Given a path string, get a list of folders and projects under that node. |
| *@param pathString is the current path (folder names and project names, separated by dots (.)). |
| *@return a list of folder and project names, in sorted order, or null if the path was invalid. |
| */ |
| protected String[] getChildFolders(LivelinkContext llc, String pathString) |
| throws ManifoldCFException, ServiceInterruption |
| { |
| RootValue rv = new RootValue(llc,pathString); |
| |
| // Get the volume, object id of the folder/project the path describes |
| VolumeAndId vid = getPathId(rv); |
| if (vid == null) |
| return null; |
| |
| String filterString = "(SubType="+ LAPI_DOCUMENTS.FOLDERSUBTYPE + " or SubType=" + LAPI_DOCUMENTS.PROJECTSUBTYPE + |
| " or SubType=" + LAPI_DOCUMENTS.COMPOUNDDOCUMENTSUBTYPE + ")"; |
| |
| int sanityRetryCount = FAILURE_RETRY_COUNT; |
| while (true) |
| { |
| ListObjectsThread t = new ListObjectsThread(vid.getVolumeID(), vid.getPathId(), filterString); |
| try |
| { |
| t.start(); |
| LLValue children; |
| try |
| { |
| children = t.finishUp(); |
| } |
| catch (ManifoldCFException e) |
| { |
| sanityRetryCount = assessRetry(sanityRetryCount,e); |
| continue; |
| } |
| |
| String[] rval = new String[children.size()]; |
| int j = 0; |
| while (j < children.size()) |
| { |
| rval[j] = children.toString(j,"Name"); |
| j++; |
| } |
| return rval; |
| } |
| catch (InterruptedException e) |
| { |
| t.interrupt(); |
| throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED); |
| } |
| catch (RuntimeException e) |
| { |
| sanityRetryCount = handleLivelinkRuntimeException(e,sanityRetryCount,true); |
| continue; |
| } |
| } |
| } |
| |
| |
| /** Given a path string, get a list of categories under that node. |
| *@param pathString is the current path (folder names and project names, separated by dots (.)). |
| *@return a list of category names, in sorted order, or null if the path was invalid. |
| */ |
| protected String[] getChildCategories(LivelinkContext llc, String pathString) |
| throws ManifoldCFException, ServiceInterruption |
| { |
| // Start at root |
| RootValue rv = new RootValue(llc,pathString); |
| |
| // Get the volume, object id of the folder/project the path describes |
| VolumeAndId vid = getPathId(rv); |
| if (vid == null) |
| return null; |
| |
| // We want only folders that are children of the current object and which match the specified subfolder |
| String filterString = "SubType="+ LAPI_DOCUMENTS.CATEGORYSUBTYPE; |
| |
| int sanityRetryCount = FAILURE_RETRY_COUNT; |
| while (true) |
| { |
| ListObjectsThread t = new ListObjectsThread(vid.getVolumeID(), vid.getPathId(), filterString); |
| try |
| { |
| t.start(); |
| LLValue children; |
| try |
| { |
| children = t.finishUp(); |
| } |
| catch (ManifoldCFException e) |
| { |
| sanityRetryCount = assessRetry(sanityRetryCount,e); |
| continue; |
| } |
| |
| String[] rval = new String[children.size()]; |
| int j = 0; |
| while (j < children.size()) |
| { |
| rval[j] = children.toString(j,"Name"); |
| j++; |
| } |
| return rval; |
| } |
| catch (InterruptedException e) |
| { |
| t.interrupt(); |
| throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED); |
| } |
| catch (RuntimeException e) |
| { |
| sanityRetryCount = handleLivelinkRuntimeException(e,sanityRetryCount,true); |
| continue; |
| } |
| } |
| } |
| |
| protected class GetCategoryAttributesThread extends Thread |
| { |
| protected final int catObjectID; |
| protected Throwable exception = null; |
| protected LLValue rval = null; |
| |
| public GetCategoryAttributesThread(int catObjectID) |
| { |
| super(); |
| setDaemon(true); |
| this.catObjectID = catObjectID; |
| } |
| |
| public void run() |
| { |
| try |
| { |
| LLValue catID = new LLValue(); |
| catID.setAssoc(); |
| catID.add("ID", catObjectID); |
| catID.add("Type", LAPI_ATTRIBUTES.CATEGORY_TYPE_LIBRARY); |
| |
| LLValue catVersion = new LLValue(); |
| int status = LLDocs.FetchCategoryVersion(catID,catVersion); |
| if (status == 107105 || status == 107106) |
| return; |
| if (status != 0) |
| { |
| throw new ManifoldCFException("Error getting category version: "+Integer.toString(status)); |
| } |
| |
| LLValue children = new LLValue(); |
| status = LLAttributes.AttrListNames(catVersion,null,children); |
| if (status != 0) |
| { |
| throw new ManifoldCFException("Error getting attribute names: "+Integer.toString(status)); |
| } |
| rval = children; |
| } |
| catch (Throwable e) |
| { |
| this.exception = e; |
| } |
| } |
| |
| public LLValue finishUp() |
| throws ManifoldCFException, InterruptedException |
| { |
| join(); |
| Throwable thr = exception; |
| if (thr != null) |
| { |
| if (thr instanceof RuntimeException) |
| throw (RuntimeException)thr; |
| else if (thr instanceof ManifoldCFException) |
| throw (ManifoldCFException)thr; |
| else if (thr instanceof Error) |
| throw (Error)thr; |
| else |
| throw new RuntimeException("Unrecognized exception type: "+thr.getClass().getName()+": "+thr.getMessage(),thr); |
| } |
| return rval; |
| } |
| } |
| |
| |
| /** Given a category path, get a list of legal attribute names. |
| *@param catObjectID is the object id of the category. |
| *@return a list of attribute names, in sorted order, or null of the path was invalid. |
| */ |
| protected String[] getCategoryAttributes(int catObjectID) |
| throws ManifoldCFException, ServiceInterruption |
| { |
| int sanityRetryCount = FAILURE_RETRY_COUNT; |
| while (true) |
| { |
| GetCategoryAttributesThread t = new GetCategoryAttributesThread(catObjectID); |
| try |
| { |
| t.start(); |
| LLValue children; |
| try |
| { |
| children = t.finishUp(); |
| } |
| catch (ManifoldCFException e) |
| { |
| sanityRetryCount = assessRetry(sanityRetryCount,e); |
| continue; |
| } |
| |
| if (children == null) |
| return null; |
| |
| String[] rval = new String[children.size()]; |
| LLValueEnumeration en = children.enumerateValues(); |
| |
| int j = 0; |
| while (en.hasMoreElements()) |
| { |
| LLValue v = (LLValue)en.nextElement(); |
| rval[j] = v.toString(); |
| j++; |
| } |
| return rval; |
| } |
| catch (InterruptedException e) |
| { |
| t.interrupt(); |
| throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED); |
| } |
| catch (RuntimeException e) |
| { |
| sanityRetryCount = handleLivelinkRuntimeException(e,sanityRetryCount,true); |
| continue; |
| } |
| } |
| } |
| |
| protected class GetCategoryVersionThread extends Thread |
| { |
| protected final int objID; |
| protected final int catID; |
| protected Throwable exception = null; |
| protected LLValue rval = null; |
| |
| public GetCategoryVersionThread(int objID, int catID) |
| { |
| super(); |
| setDaemon(true); |
| this.objID = objID; |
| this.catID = catID; |
| } |
| |
| public void run() |
| { |
| try |
| { |
| // Set up the right llvalues |
| |
| // Object ID |
| LLValue objIDValue = new LLValue().setAssoc(); |
| objIDValue.add("ID", objID); |
| // Current version, so don't set the "Version" field |
| |
| // CatID |
| LLValue catIDValue = new LLValue().setAssoc(); |
| catIDValue.add("ID", catID); |
| catIDValue.add("Type", LAPI_ATTRIBUTES.CATEGORY_TYPE_LIBRARY); |
| |
| LLValue rvalue = new LLValue(); |
| |
| int status = LLDocs.GetObjectAttributesEx(objIDValue,catIDValue,rvalue); |
| // If either the object is wrong, or the object does not have the specified category, return null. |
| if (status == 103101 || status == 107205) |
| return; |
| |
| if (status != 0) |
| { |
| throw new ManifoldCFException("Error retrieving category version: "+Integer.toString(status)+": "+llServer.getErrors()); |
| } |
| |
| rval = rvalue; |
| |
| } |
| catch (Throwable e) |
| { |
| this.exception = e; |
| } |
| } |
| |
| public LLValue finishUp() |
| throws ManifoldCFException, InterruptedException |
| { |
| join(); |
| Throwable thr = exception; |
| if (thr != null) |
| { |
| if (thr instanceof RuntimeException) |
| throw (RuntimeException)thr; |
| else if (thr instanceof ManifoldCFException) |
| throw (ManifoldCFException)thr; |
| else if (thr instanceof Error) |
| throw (Error)thr; |
| else |
| throw new RuntimeException("Unrecognized exception type: "+thr.getClass().getName()+": "+thr.getMessage(),thr); |
| } |
| return rval; |
| } |
| |
| } |
| |
| /** Get a category version for document. |
| */ |
| protected LLValue getCatVersion(int objID, int catID) |
| throws ManifoldCFException, ServiceInterruption |
| { |
| int sanityRetryCount = FAILURE_RETRY_COUNT; |
| while (true) |
| { |
| GetCategoryVersionThread t = new GetCategoryVersionThread(objID,catID); |
| try |
| { |
| t.start(); |
| try |
| { |
| return t.finishUp(); |
| } |
| catch (ManifoldCFException e) |
| { |
| sanityRetryCount = assessRetry(sanityRetryCount,e); |
| continue; |
| } |
| } |
| catch (InterruptedException e) |
| { |
| t.interrupt(); |
| throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED); |
| } |
| catch (NullPointerException npe) |
| { |
| // LAPI throws a null pointer exception under very rare conditions when the GetObjectAttributesEx is |
| // called. The conditions are not clear at this time - it could even be due to Livelink corruption. |
| // However, I'm going to have to treat this as |
| // indicating that this category version does not exist for this document. |
| Logging.connectors.warn("Livelink: Null pointer exception thrown trying to get cat version for category "+ |
| Integer.toString(catID)+" for object "+Integer.toString(objID)); |
| return null; |
| } |
| catch (RuntimeException e) |
| { |
| sanityRetryCount = handleLivelinkRuntimeException(e,sanityRetryCount,true); |
| continue; |
| } |
| } |
| } |
| |
| protected class GetAttributeValueThread extends Thread |
| { |
| protected final LLValue categoryVersion; |
| protected final String attributeName; |
| protected Throwable exception = null; |
| protected LLValue rval = null; |
| |
| public GetAttributeValueThread(LLValue categoryVersion, String attributeName) |
| { |
| super(); |
| setDaemon(true); |
| this.categoryVersion = categoryVersion; |
| this.attributeName = attributeName; |
| } |
| |
| public void run() |
| { |
| try |
| { |
| // Set up the right llvalues |
| LLValue children = new LLValue(); |
| int status = LLAttributes.AttrGetValues(categoryVersion,attributeName, |
| 0,null,children); |
| // "Not found" status - I don't know if it possible to get this here, but if so, behave civilly |
| if (status == 103101) |
| return; |
| // This seems to be the real error LAPI returns if you don't have an attribute of this name |
| if (status == 8000604) |
| return; |
| |
| if (status != 0) |
| { |
| throw new ManifoldCFException("Error retrieving attribute value: "+Integer.toString(status)+": "+llServer.getErrors()); |
| } |
| rval = children; |
| } |
| catch (Throwable e) |
| { |
| this.exception = e; |
| } |
| } |
| |
| public LLValue finishUp() |
| throws ManifoldCFException, InterruptedException |
| { |
| join(); |
| Throwable thr = exception; |
| if (thr != null) |
| { |
| if (thr instanceof RuntimeException) |
| throw (RuntimeException)thr; |
| else if (thr instanceof ManifoldCFException) |
| throw (ManifoldCFException)thr; |
| else if (thr instanceof Error) |
| throw (Error)thr; |
| else |
| throw new RuntimeException("Unrecognized exception type: "+thr.getClass().getName()+": "+thr.getMessage(),thr); |
| } |
| return rval; |
| } |
| |
| } |
| |
| /** Get an attribute value from a category version. |
| */ |
| protected String[] getAttributeValue(LLValue categoryVersion, String attributeName) |
| throws ManifoldCFException, ServiceInterruption |
| { |
| int sanityRetryCount = FAILURE_RETRY_COUNT; |
| while (true) |
| { |
| GetAttributeValueThread t = new GetAttributeValueThread(categoryVersion, attributeName); |
| try |
| { |
| t.start(); |
| LLValue children; |
| try |
| { |
| children = t.finishUp(); |
| } |
| catch (ManifoldCFException e) |
| { |
| sanityRetryCount = assessRetry(sanityRetryCount,e); |
| continue; |
| } |
| |
| if (children == null) |
| return null; |
| String[] rval = new String[children.size()]; |
| LLValueEnumeration en = children.enumerateValues(); |
| |
| int j = 0; |
| while (en.hasMoreElements()) |
| { |
| LLValue v = (LLValue)en.nextElement(); |
| rval[j] = v.toString(); |
| j++; |
| } |
| return rval; |
| } |
| catch (InterruptedException e) |
| { |
| t.interrupt(); |
| throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED); |
| } |
| catch (RuntimeException e) |
| { |
| sanityRetryCount = handleLivelinkRuntimeException(e,sanityRetryCount,true); |
| continue; |
| } |
| } |
| } |
| |
| protected class GetObjectRightsThread extends Thread |
| { |
| protected final int vol; |
| protected final int objID; |
| protected Throwable exception = null; |
| protected LLValue rval = null; |
| |
| public GetObjectRightsThread(int vol, int objID) |
| { |
| super(); |
| setDaemon(true); |
| this.vol = vol; |
| this.objID = objID; |
| } |
| |
| public void run() |
| { |
| try |
| { |
| LLValue childrenObjects = new LLValue(); |
| int status = LLDocs.GetObjectRights(vol, objID, childrenObjects); |
| // If the rights object doesn't exist, behave civilly |
| if (status == 103101) |
| return; |
| |
| if (status != 0) |
| { |
| throw new ManifoldCFException("Error retrieving document rights: "+Integer.toString(status)+": "+llServer.getErrors()); |
| } |
| |
| rval = childrenObjects; |
| } |
| catch (Throwable e) |
| { |
| this.exception = e; |
| } |
| } |
| |
| public LLValue finishUp() |
| throws ManifoldCFException, InterruptedException |
| { |
| join(); |
| Throwable thr = exception; |
| if (thr != null) |
| { |
| if (thr instanceof RuntimeException) |
| throw (RuntimeException)thr; |
| else if (thr instanceof ManifoldCFException) |
| throw (ManifoldCFException)thr; |
| else if (thr instanceof Error) |
| throw (Error)thr; |
| else |
| throw new RuntimeException("Unrecognized exception type: "+thr.getClass().getName()+": "+thr.getMessage(),thr); |
| } |
| return rval; |
| } |
| |
| } |
| |
| /** Get an object's rights. This will be an array of right id's, including the special |
| * ones defined by Livelink, or null will be returned (if the object is not found). |
| *@param vol is the volume id |
| *@param objID is the object id |
| *@return the array. |
| */ |
| protected int[] getObjectRights(int vol, int objID) |
| throws ManifoldCFException, ServiceInterruption |
| { |
| int sanityRetryCount = FAILURE_RETRY_COUNT; |
| while (true) |
| { |
| GetObjectRightsThread t = new GetObjectRightsThread(vol,objID); |
| try |
| { |
| t.start(); |
| LLValue childrenObjects; |
| try |
| { |
| childrenObjects = t.finishUp(); |
| } |
| catch (ManifoldCFException e) |
| { |
| sanityRetryCount = assessRetry(sanityRetryCount,e); |
| continue; |
| } |
| |
| if (childrenObjects == null) |
| return null; |
| |
| int size; |
| if (childrenObjects.isRecord()) |
| size = 1; |
| else if (childrenObjects.isTable()) |
| size = childrenObjects.size(); |
| else |
| size = 0; |
| |
| int minPermission = LAPI_DOCUMENTS.PERM_SEE + |
| LAPI_DOCUMENTS.PERM_SEECONTENTS; |
| |
| |
| int j = 0; |
| int count = 0; |
| while (j < size) |
| { |
| int permission = childrenObjects.toInteger(j, "Permissions"); |
| // Only if the permission is "see contents" can we consider this |
| // access token! |
| if ((permission & minPermission) == minPermission) |
| count++; |
| j++; |
| } |
| |
| int[] rval = new int[count]; |
| j = 0; |
| count = 0; |
| while (j < size) |
| { |
| int token = childrenObjects.toInteger(j, "RightID"); |
| int permission = childrenObjects.toInteger(j, "Permissions"); |
| // Only if the permission is "see contents" can we consider this |
| // access token! |
| if ((permission & minPermission) == minPermission) |
| rval[count++] = token; |
| j++; |
| } |
| return rval; |
| } |
| catch (InterruptedException e) |
| { |
| t.interrupt(); |
| throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED); |
| } |
| catch (RuntimeException e) |
| { |
| sanityRetryCount = handleLivelinkRuntimeException(e,sanityRetryCount,true); |
| continue; |
| } |
| } |
| } |
| |
| /** Local cache for various kinds of objects that may be useful more than once. |
| */ |
| protected class LivelinkContext |
| { |
| /** Cache of ObjectInformation objects. */ |
| protected Map<ObjectInformation,ObjectInformation> objectInfoMap = new HashMap<ObjectInformation,ObjectInformation>(); |
| /** Cache of VersionInformation objects. */ |
| protected Map<VersionInformation,VersionInformation> versionInfoMap = new HashMap<VersionInformation,VersionInformation>(); |
| /** Cache of UserInformation objects */ |
| protected Map<UserInformation,UserInformation> userInfoMap = new HashMap<UserInformation,UserInformation>(); |
| |
| public LivelinkContext() |
| { |
| } |
| |
| public ObjectInformation getObjectInformation(int volumeID, int objectID) |
| { |
| ObjectInformation oi = new ObjectInformation(volumeID,objectID); |
| ObjectInformation lookupValue = objectInfoMap.get(oi); |
| if (lookupValue == null) |
| { |
| objectInfoMap.put(oi,oi); |
| return oi; |
| } |
| return lookupValue; |
| } |
| |
| public VersionInformation getVersionInformation(int volumeID, int objectID, int revisionNumber) |
| { |
| VersionInformation vi = new VersionInformation(volumeID,objectID,revisionNumber); |
| VersionInformation lookupValue = versionInfoMap.get(vi); |
| if (lookupValue == null) |
| { |
| versionInfoMap.put(vi,vi); |
| return vi; |
| } |
| return lookupValue; |
| } |
| |
| public UserInformation getUserInformation(int userID) |
| { |
| UserInformation ui = new UserInformation(userID); |
| UserInformation lookupValue = userInfoMap.get(ui); |
| if (lookupValue == null) |
| { |
| userInfoMap.put(ui,ui); |
| return ui; |
| } |
| return lookupValue; |
| } |
| } |
| |
| /** This object represents a cache of user information. |
| * Initialize it with the user ID. Then, request desired fields from it. |
| */ |
| protected class UserInformation |
| { |
| protected final int userID; |
| |
| protected LLValue userValue = null; |
| |
| public UserInformation(int userID) |
| { |
| this.userID = userID; |
| } |
| |
| public boolean exists() |
| throws ServiceInterruption, ManifoldCFException |
| { |
| return getUserValue() != null; |
| } |
| |
| public String getName() |
| throws ServiceInterruption, ManifoldCFException |
| { |
| LLValue userValue = getUserValue(); |
| if (userValue == null) |
| return null; |
| return userValue.toString("NAME"); |
| } |
| |
| protected LLValue getUserValue() |
| throws ServiceInterruption, ManifoldCFException |
| { |
| if (userValue == null) |
| { |
| int sanityRetryCount = FAILURE_RETRY_COUNT; |
| while (true) |
| { |
| GetUserInfoThread t = new GetUserInfoThread(userID); |
| try |
| { |
| t.start(); |
| try |
| { |
| userValue = t.finishUp(); |
| } |
| catch (ManifoldCFException e) |
| { |
| sanityRetryCount = assessRetry(sanityRetryCount,e); |
| continue; |
| } |
| break; |
| } |
| catch (InterruptedException e) |
| { |
| t.interrupt(); |
| throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED); |
| } |
| catch (RuntimeException e) |
| { |
| sanityRetryCount = handleLivelinkRuntimeException(e,sanityRetryCount,true); |
| continue; |
| } |
| } |
| } |
| return userValue; |
| } |
| |
| @Override |
| public String toString() |
| { |
| return "("+userID+")"; |
| } |
| |
| @Override |
| public int hashCode() |
| { |
| return (userID << 5) ^ (userID >> 3); |
| } |
| |
| @Override |
| public boolean equals(Object o) |
| { |
| if (!(o instanceof UserInformation)) |
| return false; |
| UserInformation other = (UserInformation)o; |
| return userID == other.userID; |
| } |
| |
| } |
| |
| /** This object represents a cache of version information. |
| * Initialize it with the volume ID and object ID and revision number (usually zero). |
| * Then, request the desired fields from it. |
| */ |
| protected class VersionInformation |
| { |
| protected final int volumeID; |
| protected final int objectID; |
| protected final int revisionNumber; |
| |
| protected LLValue versionValue = null; |
| |
| public VersionInformation(int volumeID, int objectID, int revisionNumber) |
| { |
| this.volumeID = volumeID; |
| this.objectID = objectID; |
| this.revisionNumber = revisionNumber; |
| } |
| |
| public boolean exists() |
| throws ServiceInterruption, ManifoldCFException |
| { |
| return getVersionValue() != null; |
| } |
| |
| /** Get data size. |
| */ |
| public Long getDataSize() |
| throws ServiceInterruption, ManifoldCFException |
| { |
| LLValue elem = getVersionValue(); |
| if (elem == null) |
| return null; |
| return new Long(elem.toLong("FILEDATASIZE")); |
| } |
| |
| /** Get file name. |
| */ |
| public String getFileName() |
| throws ServiceInterruption, ManifoldCFException |
| { |
| LLValue elem = getVersionValue(); |
| if (elem == null) |
| return null; |
| return elem.toString("FILENAME"); |
| } |
| |
| /** Get mime type. |
| */ |
| public String getMimeType() |
| throws ServiceInterruption, ManifoldCFException |
| { |
| LLValue elem = getVersionValue(); |
| if (elem == null) |
| return null; |
| return elem.toString("MIMETYPE"); |
| } |
| |
| /** Get modify date. |
| */ |
| public Date getModifyDate() |
| throws ServiceInterruption, ManifoldCFException |
| { |
| LLValue elem = getVersionValue(); |
| if (elem == null) |
| return null; |
| return elem.toDate("MODIFYDATE"); |
| } |
| |
| /** Get modifier. |
| */ |
| public Integer getOwnerId() |
| throws ServiceInterruption, ManifoldCFException |
| { |
| LLValue elem = getVersionValue(); |
| if (elem == null) |
| return null; |
| return new Integer(elem.toInteger("OWNER")); |
| } |
| |
| /** Get version LLValue */ |
| protected LLValue getVersionValue() |
| throws ServiceInterruption, ManifoldCFException |
| { |
| if (versionValue == null) |
| { |
| int sanityRetryCount = FAILURE_RETRY_COUNT; |
| while (true) |
| { |
| GetVersionInfoThread t = new GetVersionInfoThread(volumeID,objectID,revisionNumber); |
| try |
| { |
| t.start(); |
| try |
| { |
| versionValue = t.finishUp(); |
| } |
| catch (ManifoldCFException e) |
| { |
| sanityRetryCount = assessRetry(sanityRetryCount,e); |
| continue; |
| } |
| break; |
| } |
| catch (InterruptedException e) |
| { |
| t.interrupt(); |
| throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED); |
| } |
| catch (RuntimeException e) |
| { |
| sanityRetryCount = handleLivelinkRuntimeException(e,sanityRetryCount,true); |
| continue; |
| } |
| } |
| } |
| return versionValue; |
| } |
| |
| @Override |
| public int hashCode() |
| { |
| return (volumeID << 5) ^ (volumeID >> 3) ^ (objectID << 5) ^ (objectID >> 3) ^ (revisionNumber << 5) ^ (revisionNumber >> 3); |
| } |
| |
| @Override |
| public boolean equals(Object o) |
| { |
| if (!(o instanceof VersionInformation)) |
| return false; |
| VersionInformation other = (VersionInformation)o; |
| return volumeID == other.volumeID && objectID == other.objectID && revisionNumber == other.revisionNumber; |
| } |
| |
| } |
| |
| /** This object represents an object information cache. |
| * Initialize it with the volume ID and object ID, and then request |
| * the appropriate fields from it. Keep it around as long as needed; it functions as a cache |
| * of sorts... |
| */ |
| protected class ObjectInformation |
| { |
| protected final int volumeID; |
| protected final int objectID; |
| |
| protected LLValue objectValue = null; |
| |
| public ObjectInformation(int volumeID, int objectID) |
| { |
| this.volumeID = volumeID; |
| this.objectID = objectID; |
| } |
| |
| /** |
| * Check whether object seems to exist or not. |
| */ |
| public boolean exists() |
| throws ServiceInterruption, ManifoldCFException |
| { |
| return getObjectValue() != null; |
| } |
| |
| /** Check if this object is the category workspace. |
| */ |
| public boolean isCategoryWorkspace() |
| { |
| return objectID == LLCATWK_ID; |
| } |
| |
| /** Check if this object is the entity workspace. |
| */ |
| public boolean isEntityWorkspace() |
| { |
| return objectID == LLENTWK_ID; |
| } |
| |
| /** toString override */ |
| @Override |
| public String toString() |
| { |
| return "(Volume: "+volumeID+", Object: "+objectID+")"; |
| } |
| |
| /** |
| * Returns the object ID specified by the path name. |
| * @param startPath is the folder name (a string with dots as separators) |
| */ |
| public VolumeAndId getPathId(String startPath) |
| throws ServiceInterruption, ManifoldCFException |
| { |
| LLValue objInfo = getObjectValue(); |
| if (objInfo == null) |
| return null; |
| |
| // Grab the volume ID and starting object |
| int obj = objInfo.toInteger("ID"); |
| int vol = objInfo.toInteger("VolumeID"); |
| |
| // Pick apart the start path. This is a string separated by slashes. |
| int charindex = 0; |
| while (charindex < startPath.length()) |
| { |
| StringBuilder currentTokenBuffer = new StringBuilder(); |
| // Find the current token |
| while (charindex < startPath.length()) |
| { |
| char x = startPath.charAt(charindex++); |
| if (x == '/') |
| break; |
| if (x == '\\') |
| { |
| // Attempt to escape what follows |
| x = startPath.charAt(charindex); |
| charindex++; |
| } |
| currentTokenBuffer.append(x); |
| } |
| |
| String subFolder = currentTokenBuffer.toString(); |
| // We want only folders that are children of the current object and which match the specified subfolder |
| String filterString = "(SubType="+ LAPI_DOCUMENTS.FOLDERSUBTYPE + " or SubType=" + LAPI_DOCUMENTS.PROJECTSUBTYPE + |
| " or SubType=" + LAPI_DOCUMENTS.COMPOUNDDOCUMENTSUBTYPE + ") and Name='" + subFolder + "'"; |
| |
| int sanityRetryCount = FAILURE_RETRY_COUNT; |
| while (true) |
| { |
| ListObjectsThread t = new ListObjectsThread(vol,obj,filterString); |
| try |
| { |
| t.start(); |
| LLValue children; |
| try |
| { |
| children = t.finishUp(); |
| } |
| catch (ManifoldCFException e) |
| { |
| sanityRetryCount = assessRetry(sanityRetryCount,e); |
| continue; |
| } |
| |
| if (children == null) |
| return null; |
| |
| // If there is one child, then we are okay. |
| if (children.size() == 1) |
| { |
| // New starting point is the one we found. |
| obj = children.toInteger(0, "ID"); |
| int subtype = children.toInteger(0, "SubType"); |
| if (subtype == LAPI_DOCUMENTS.PROJECTSUBTYPE) |
| { |
| vol = obj; |
| obj = -obj; |
| } |
| } |
| else |
| { |
| // Couldn't find the path. Instead of throwing up, return null to indicate |
| // illegal node. |
| return null; |
| } |
| break; |
| } |
| catch (InterruptedException e) |
| { |
| t.interrupt(); |
| throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED); |
| } |
| catch (RuntimeException e) |
| { |
| sanityRetryCount = handleLivelinkRuntimeException(e,sanityRetryCount,true); |
| continue; |
| |
| } |
| } |
| |
| } |
| return new VolumeAndId(vol,obj); |
| } |
| |
| /** |
| * Returns the category ID specified by the path name. |
| * @param startPath is the folder name, ending in a category name (a string with slashes as separators) |
| */ |
| public int getCategoryId(String startPath) |
| throws ManifoldCFException, ServiceInterruption |
| { |
| LLValue objInfo = getObjectValue(); |
| if (objInfo == null) |
| return -1; |
| |
| // Grab the volume ID and starting object |
| int obj = objInfo.toInteger("ID"); |
| int vol = objInfo.toInteger("VolumeID"); |
| |
| // Pick apart the start path. This is a string separated by slashes. |
| if (startPath.length() == 0) |
| return -1; |
| |
| int charindex = 0; |
| while (charindex < startPath.length()) |
| { |
| StringBuilder currentTokenBuffer = new StringBuilder(); |
| // Find the current token |
| while (charindex < startPath.length()) |
| { |
| char x = startPath.charAt(charindex++); |
| if (x == '/') |
| break; |
| if (x == '\\') |
| { |
| // Attempt to escape what follows |
| x = startPath.charAt(charindex); |
| charindex++; |
| } |
| currentTokenBuffer.append(x); |
| } |
| String subFolder = currentTokenBuffer.toString(); |
| String filterString; |
| |
| // We want only folders that are children of the current object and which match the specified subfolder |
| if (charindex < startPath.length()) |
| filterString = "(SubType="+ LAPI_DOCUMENTS.FOLDERSUBTYPE + " or SubType=" + LAPI_DOCUMENTS.PROJECTSUBTYPE + |
| " or SubType=" + LAPI_DOCUMENTS.COMPOUNDDOCUMENTSUBTYPE + ")"; |
| else |
| filterString = "SubType="+LAPI_DOCUMENTS.CATEGORYSUBTYPE; |
| |
| filterString += " and Name='" + subFolder + "'"; |
| |
| int sanityRetryCount = FAILURE_RETRY_COUNT; |
| while (true) |
| { |
| ListObjectsThread t = new ListObjectsThread(vol,obj,filterString); |
| try |
| { |
| t.start(); |
| LLValue children; |
| try |
| { |
| children = t.finishUp(); |
| } |
| catch (ManifoldCFException e) |
| { |
| sanityRetryCount = assessRetry(sanityRetryCount,e); |
| continue; |
| } |
| |
| if (children == null) |
| return -1; |
| |
| // If there is one child, then we are okay. |
| if (children.size() == 1) |
| { |
| // New starting point is the one we found. |
| obj = children.toInteger(0, "ID"); |
| int subtype = children.toInteger(0, "SubType"); |
| if (subtype == LAPI_DOCUMENTS.PROJECTSUBTYPE) |
| { |
| vol = obj; |
| obj = -obj; |
| } |
| } |
| else |
| { |
| // Couldn't find the path. Instead of throwing up, return null to indicate |
| // illegal node. |
| return -1; |
| } |
| break; |
| } |
| catch (InterruptedException e) |
| { |
| t.interrupt(); |
| throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED); |
| } |
| catch (RuntimeException e) |
| { |
| sanityRetryCount = handleLivelinkRuntimeException(e,sanityRetryCount,true); |
| continue; |
| } |
| } |
| } |
| return obj; |
| } |
| |
| /** Get permissions. |
| */ |
| public Integer getPermissions() |
| throws ServiceInterruption, ManifoldCFException |
| { |
| LLValue elem = getObjectValue(); |
| if (elem == null) |
| return null; |
| return new Integer(objectValue.toInteger("Permissions")); |
| } |
| |
| /** Get OpenText document name. |
| */ |
| public String getName() |
| throws ServiceInterruption, ManifoldCFException |
| { |
| LLValue elem = getObjectValue(); |
| if (elem == null) |
| return null; |
| return elem.toString("NAME"); |
| } |
| |
| /** Get OpenText comments/description. |
| */ |
| public String getComments() |
| throws ServiceInterruption, ManifoldCFException |
| { |
| LLValue elem = getObjectValue(); |
| if (elem == null) |
| return null; |
| return elem.toString("COMMENT"); |
| } |
| |
| /** Get parent ID. |
| */ |
| public Integer getParentId() |
| throws ServiceInterruption, ManifoldCFException |
| { |
| LLValue elem = getObjectValue(); |
| if (elem == null) |
| return null; |
| return new Integer(elem.toInteger("ParentId")); |
| } |
| |
| /** Get owner ID. |
| */ |
| public Integer getOwnerId() |
| throws ServiceInterruption, ManifoldCFException |
| { |
| LLValue elem = getObjectValue(); |
| if (elem == null) |
| return null; |
| return new Integer(elem.toInteger("UserId")); |
| } |
| |
| /** Get group ID. |
| */ |
| public Integer getGroupId() |
| throws ServiceInterruption, ManifoldCFException |
| { |
| LLValue elem = getObjectValue(); |
| if (elem == null) |
| return null; |
| return new Integer(elem.toInteger("GroupId")); |
| } |
| |
| /** Get creation date. |
| */ |
| public Date getCreationDate() |
| throws ServiceInterruption, ManifoldCFException |
| { |
| LLValue elem = getObjectValue(); |
| if (elem == null) |
| return null; |
| return elem.toDate("CREATEDATE"); |
| } |
| |
| /** Get creator ID. |
| */ |
| public Integer getCreatorId() |
| throws ServiceInterruption, ManifoldCFException |
| { |
| LLValue elem = getObjectValue(); |
| if (elem == null) |
| return null; |
| return new Integer(elem.toInteger("CREATEDBY")); |
| } |
| |
| /* Get modify date. |
| */ |
| public Date getModifyDate() |
| throws ServiceInterruption, ManifoldCFException |
| { |
| LLValue elem = getObjectValue(); |
| if (elem == null) |
| return null; |
| return elem.toDate("ModifyDate"); |
| } |
| |
| /** Get the objInfo object. |
| */ |
| protected LLValue getObjectValue() |
| throws ServiceInterruption, ManifoldCFException |
| { |
| if (objectValue == null) |
| { |
| int sanityRetryCount = FAILURE_RETRY_COUNT; |
| while (true) |
| { |
| GetObjectInfoThread t = new GetObjectInfoThread(volumeID,objectID); |
| try |
| { |
| t.start(); |
| try |
| { |
| objectValue = t.finishUp(); |
| } |
| catch (ManifoldCFException e) |
| { |
| sanityRetryCount = assessRetry(sanityRetryCount,e); |
| continue; |
| } |
| break; |
| } |
| catch (InterruptedException e) |
| { |
| t.interrupt(); |
| throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED); |
| } |
| catch (RuntimeException e) |
| { |
| sanityRetryCount = handleLivelinkRuntimeException(e,sanityRetryCount,true); |
| continue; |
| } |
| } |
| } |
| return objectValue; |
| } |
| |
| @Override |
| public int hashCode() |
| { |
| return (volumeID << 5) ^ (volumeID >> 3) ^ (objectID << 5) ^ (objectID >> 3); |
| } |
| |
| @Override |
| public boolean equals(Object o) |
| { |
| if (!(o instanceof ObjectInformation)) |
| return false; |
| ObjectInformation other = (ObjectInformation)o; |
| return volumeID == other.volumeID && objectID == other.objectID; |
| } |
| } |
| |
| /** Thread we can abandon that lists all users (except admin). |
| */ |
| protected class ListUsersThread extends Thread |
| { |
| protected LLValue rval = null; |
| protected Throwable exception = null; |
| |
| public ListUsersThread() |
| { |
| super(); |
| setDaemon(true); |
| } |
| |
| public void run() |
| { |
| try |
| { |
| LLValue userList = new LLValue(); |
| int status = LLUsers.ListUsers(userList); |
| |
| if (Logging.connectors.isDebugEnabled()) |
| { |
| Logging.connectors.debug("Livelink: User list retrieved: status="+Integer.toString(status)); |
| } |
| |
| if (status < 0) |
| { |
| Logging.connectors.debug("Livelink: User list inaccessable ("+llServer.getErrors()+")"); |
| return; |
| } |
| |
| if (status != 0) |
| { |
| throw new ManifoldCFException("Error retrieving user list: status="+Integer.toString(status)+" ("+llServer.getErrors()+")"); |
| } |
| |
| rval = userList; |
| } |
| catch (Throwable e) |
| { |
| this.exception = e; |
| } |
| } |
| |
| public LLValue finishUp() |
| throws ManifoldCFException, InterruptedException |
| { |
| join(); |
| Throwable thr = exception; |
| if (thr != null) |
| { |
| if (thr instanceof RuntimeException) |
| throw (RuntimeException)thr; |
| else if (thr instanceof ManifoldCFException) |
| throw (ManifoldCFException)thr; |
| else if (thr instanceof Error) |
| throw (Error)thr; |
| else |
| throw new RuntimeException("Unrecognized exception type: "+thr.getClass().getName()+": "+thr.getMessage(),thr); |
| } |
| return rval; |
| } |
| |
| } |
| |
| /** Thread we can abandon that gets user information for a userID. |
| */ |
| protected class GetUserInfoThread extends Thread |
| { |
| protected final int user; |
| protected Throwable exception = null; |
| protected LLValue rval = null; |
| |
| public GetUserInfoThread(int user) |
| { |
| super(); |
| setDaemon(true); |
| this.user = user; |
| } |
| |
| public void run() |
| { |
| try |
| { |
| LLValue userinfo = new LLValue().setAssoc(); |
| int status = LLUsers.GetUserByID(user,userinfo); |
| |
| // Need to detect if object was deleted, and return null in this case!!! |
| if (Logging.connectors.isDebugEnabled()) |
| { |
| Logging.connectors.debug("Livelink: User status retrieved for "+Integer.toString(user)+": status="+Integer.toString(status)); |
| } |
| |
| // Treat both 103101 and 103102 as 'object not found'. |
| if (status == 103101 || status == 103102) |
| return; |
| |
| // This error means we don't have permission to get the object's status, apparently |
| if (status < 0) |
| { |
| Logging.connectors.debug("Livelink: User info inaccessable for user "+Integer.toString(user)+ |
| " ("+llServer.getErrors()+")"); |
| return; |
| } |
| |
| if (status != 0) |
| { |
| throw new ManifoldCFException("Error retrieving user "+Integer.toString(user)+": status="+Integer.toString(status)+" ("+llServer.getErrors()+")"); |
| } |
| rval = userinfo; |
| } |
| catch (Throwable e) |
| { |
| this.exception = e; |
| } |
| } |
| |
| public LLValue finishUp() |
| throws ManifoldCFException, InterruptedException |
| { |
| join(); |
| Throwable thr = exception; |
| if (thr != null) |
| { |
| if (thr instanceof RuntimeException) |
| throw (RuntimeException)thr; |
| else if (thr instanceof ManifoldCFException) |
| throw (ManifoldCFException)thr; |
| else if (thr instanceof Error) |
| throw (Error)thr; |
| else |
| throw new RuntimeException("Unrecognized exception type: "+thr.getClass().getName()+": "+thr.getMessage(),thr); |
| } |
| return rval; |
| } |
| } |
| |
| /** Thread we can abandon that gets version information for a volume and an id and a revision. |
| */ |
| protected class GetVersionInfoThread extends Thread |
| { |
| protected final int vol; |
| protected final int id; |
| protected final int revNumber; |
| protected Throwable exception = null; |
| protected LLValue rval = null; |
| |
| public GetVersionInfoThread(int vol, int id, int revNumber) |
| { |
| super(); |
| setDaemon(true); |
| this.vol = vol; |
| this.id = id; |
| this.revNumber = revNumber; |
| } |
| |
| public void run() |
| { |
| try |
| { |
| LLValue versioninfo = new LLValue().setAssocNotSet(); |
| int status = LLDocs.GetVersionInfo(vol,id,revNumber,versioninfo); |
| |
| // Need to detect if object was deleted, and return null in this case!!! |
| if (Logging.connectors.isDebugEnabled()) |
| { |
| Logging.connectors.debug("Livelink: Version status retrieved for "+Integer.toString(vol)+":"+Integer.toString(id)+", rev "+revNumber+": status="+Integer.toString(status)); |
| } |
| |
| // Treat both 103101 and 103102 as 'object not found'. |
| if (status == 103101 || status == 103102) |
| return; |
| |
| // This error means we don't have permission to get the object's status, apparently |
| if (status < 0) |
| { |
| Logging.connectors.debug("Livelink: Version info inaccessable for object "+Integer.toString(vol)+":"+Integer.toString(id)+", rev "+revNumber+ |
| " ("+llServer.getErrors()+")"); |
| return; |
| } |
| |
| if (status != 0) |
| { |
| throw new ManifoldCFException("Error retrieving document version "+Integer.toString(vol)+":"+Integer.toString(id)+", rev "+revNumber+": status="+Integer.toString(status)+" ("+llServer.getErrors()+")"); |
| } |
| rval = versioninfo; |
| } |
| catch (Throwable e) |
| { |
| this.exception = e; |
| } |
| } |
| |
| public LLValue finishUp() |
| throws ManifoldCFException, InterruptedException |
| { |
| join(); |
| Throwable thr = exception; |
| if (thr != null) |
| { |
| if (thr instanceof RuntimeException) |
| throw (RuntimeException)thr; |
| else if (thr instanceof ManifoldCFException) |
| throw (ManifoldCFException)thr; |
| else if (thr instanceof Error) |
| throw (Error)thr; |
| else |
| throw new RuntimeException("Unrecognized exception type: "+thr.getClass().getName()+": "+thr.getMessage(),thr); |
| } |
| return rval; |
| } |
| } |
| |
| /** Thread we can abandon that gets object information for a volume and an id. |
| */ |
| protected class GetObjectInfoThread extends Thread |
| { |
| protected int vol; |
| protected int id; |
| protected Throwable exception = null; |
| protected LLValue rval = null; |
| |
| public GetObjectInfoThread(int vol, int id) |
| { |
| super(); |
| setDaemon(true); |
| this.vol = vol; |
| this.id = id; |
| } |
| |
| public void run() |
| { |
| try |
| { |
| LLValue objinfo = new LLValue().setAssocNotSet(); |
| int status = LLDocs.GetObjectInfo(vol,id,objinfo); |
| |
| // Need to detect if object was deleted, and return null in this case!!! |
| if (Logging.connectors.isDebugEnabled()) |
| { |
| Logging.connectors.debug("Livelink: Status retrieved for "+Integer.toString(vol)+":"+Integer.toString(id)+": status="+Integer.toString(status)); |
| } |
| |
| // Treat both 103101 and 103102 as 'object not found'. |
| if (status == 103101 || status == 103102) |
| return; |
| |
| // This error means we don't have permission to get the object's status, apparently |
| if (status < 0) |
| { |
| Logging.connectors.debug("Livelink: Object info inaccessable for object "+Integer.toString(vol)+":"+Integer.toString(id)+ |
| " ("+llServer.getErrors()+")"); |
| return; |
| } |
| |
| if (status != 0) |
| { |
| throw new ManifoldCFException("Error retrieving document object "+Integer.toString(vol)+":"+Integer.toString(id)+": status="+Integer.toString(status)+" ("+llServer.getErrors()+")"); |
| } |
| rval = objinfo; |
| } |
| catch (Throwable e) |
| { |
| this.exception = e; |
| } |
| } |
| |
| public LLValue finishUp() |
| throws ManifoldCFException, InterruptedException |
| { |
| join(); |
| Throwable thr = exception; |
| if (thr != null) |
| { |
| if (thr instanceof RuntimeException) |
| throw (RuntimeException)thr; |
| else if (thr instanceof ManifoldCFException) |
| throw (ManifoldCFException)thr; |
| else if (thr instanceof Error) |
| throw (Error)thr; |
| else |
| throw new RuntimeException("Unrecognized exception type: "+thr.getClass().getName()+": "+thr.getMessage(),thr); |
| } |
| return rval; |
| } |
| } |
| |
| /** Build a set of actual acls given a set of rights */ |
| protected String[] lookupTokens(int[] rights, ObjectInformation objInfo) |
| throws ManifoldCFException, ServiceInterruption |
| { |
| if (!objInfo.exists()) |
| return null; |
| |
| String[] convertedAcls = new String[rights.length]; |
| |
| LLValue infoObject = null; |
| int j = 0; |
| int k = 0; |
| while (j < rights.length) |
| { |
| int token = rights[j++]; |
| String tokenValue; |
| // Consider this token |
| switch (token) |
| { |
| case LAPI_DOCUMENTS.RIGHT_OWNER: |
| // Look up user for current document (UserID attribute) |
| tokenValue = objInfo.getOwnerId().toString(); |
| break; |
| case LAPI_DOCUMENTS.RIGHT_GROUP: |
| tokenValue = objInfo.getGroupId().toString(); |
| break; |
| case LAPI_DOCUMENTS.RIGHT_WORLD: |
| // Add "Guest" token |
| tokenValue = "GUEST"; |
| break; |
| case LAPI_DOCUMENTS.RIGHT_SYSTEM: |
| // Add "System" token |
| tokenValue = "SYSTEM"; |
| break; |
| default: |
| tokenValue = Integer.toString(token); |
| break; |
| } |
| |
| // This might return a null if we could not look up the object corresponding to the right. If so, it is safe to skip it because |
| // that always RESTRICTS view of the object (maybe erroneously), but does not expand visibility. |
| if (tokenValue != null) |
| convertedAcls[k++] = tokenValue; |
| } |
| String[] actualAcls = new String[k]; |
| j = 0; |
| while (j < k) |
| { |
| actualAcls[j] = convertedAcls[j]; |
| j++; |
| } |
| return actualAcls; |
| } |
| |
| protected class GetObjectCategoryIDsThread extends Thread |
| { |
| protected final int vol; |
| protected final int id; |
| protected Throwable exception = null; |
| protected LLValue rval = null; |
| |
| public GetObjectCategoryIDsThread(int vol, int id) |
| { |
| super(); |
| setDaemon(true); |
| this.vol = vol; |
| this.id = id; |
| } |
| |
| public void run() |
| { |
| try |
| { |
| // Object ID |
| LLValue objIDValue = new LLValue().setAssocNotSet(); |
| objIDValue.add("ID", id); |
| |
| // Category ID List |
| LLValue catIDList = new LLValue().setAssocNotSet(); |
| |
| int status = LLDocs.ListObjectCategoryIDs(objIDValue,catIDList); |
| |
| // Need to detect if object was deleted, and return null in this case!!! |
| if (Logging.connectors.isDebugEnabled()) |
| { |
| Logging.connectors.debug("Livelink: Status value for getting object categories for "+Integer.toString(vol)+":"+Integer.toString(id)+" is: "+Integer.toString(status)); |
| } |
| |
| if (status == 103101) |
| return; |
| |
| if (status != 0) |
| { |
| throw new ManifoldCFException("Error retrieving document categories for "+Integer.toString(vol)+":"+Integer.toString(id)+": status="+Integer.toString(status)+" ("+llServer.getErrors()+")"); |
| } |
| rval = catIDList; |
| } |
| catch (Throwable e) |
| { |
| this.exception = e; |
| } |
| } |
| |
| public LLValue finishUp() |
| throws ManifoldCFException, InterruptedException |
| { |
| join(); |
| Throwable thr = exception; |
| if (thr != null) |
| { |
| if (thr instanceof RuntimeException) |
| throw (RuntimeException)thr; |
| else if (thr instanceof ManifoldCFException) |
| throw (ManifoldCFException)thr; |
| else if (thr instanceof Error) |
| throw (Error)thr; |
| else |
| throw new RuntimeException("Unrecognized exception type: "+thr.getClass().getName()+": "+thr.getMessage(),thr); |
| } |
| return rval; |
| } |
| } |
| |
| /** Get category IDs associated with an object. |
| * @param vol is the volume ID |
| * @param id the object ID |
| * @return an array of integers containing category identifiers, or null if the object is not found. |
| */ |
| protected int[] getObjectCategoryIDs(int vol, int id) |
| throws ManifoldCFException, ServiceInterruption |
| { |
| int sanityRetryCount = FAILURE_RETRY_COUNT; |
| while (true) |
| { |
| GetObjectCategoryIDsThread t = new GetObjectCategoryIDsThread(vol,id); |
| try |
| { |
| t.start(); |
| LLValue catIDList; |
| try |
| { |
| catIDList = t.finishUp(); |
| } |
| catch (ManifoldCFException e) |
| { |
| sanityRetryCount = assessRetry(sanityRetryCount,e); |
| continue; |
| } |
| |
| if (catIDList == null) |
| return null; |
| |
| int size = catIDList.size(); |
| |
| if (Logging.connectors.isDebugEnabled()) |
| { |
| Logging.connectors.debug("Livelink: Object "+Integer.toString(vol)+":"+Integer.toString(id)+" has "+Integer.toString(size)+" attached categories"); |
| } |
| |
| // Count the category ids |
| int count = 0; |
| int j = 0; |
| while (j < size) |
| { |
| int type = catIDList.toValue(j).toInteger("Type"); |
| if (type == LAPI_ATTRIBUTES.CATEGORY_TYPE_LIBRARY) |
| count++; |
| j++; |
| } |
| |
| int[] rval = new int[count]; |
| |
| // Do the scan |
| j = 0; |
| count = 0; |
| while (j < size) |
| { |
| int type = catIDList.toValue(j).toInteger("Type"); |
| if (type == LAPI_ATTRIBUTES.CATEGORY_TYPE_LIBRARY) |
| { |
| int childID = catIDList.toValue(j).toInteger("ID"); |
| rval[count++] = childID; |
| } |
| j++; |
| } |
| |
| if (Logging.connectors.isDebugEnabled()) |
| { |
| Logging.connectors.debug("Livelink: Object "+Integer.toString(vol)+":"+Integer.toString(id)+" has "+Integer.toString(rval.length)+" attached library categories"); |
| } |
| |
| return rval; |
| } |
| catch (InterruptedException e) |
| { |
| t.interrupt(); |
| throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED); |
| } |
| catch (RuntimeException e) |
| { |
| sanityRetryCount = handleLivelinkRuntimeException(e,sanityRetryCount,true); |
| continue; |
| } |
| } |
| } |
| |
| /** RootValue version of getPathId. |
| */ |
| protected VolumeAndId getPathId(RootValue rv) |
| throws ManifoldCFException, ServiceInterruption |
| { |
| return rv.getRootValue().getPathId(rv.getRemainderPath()); |
| } |
| |
| /** Rootvalue version of getCategoryId. |
| */ |
| protected int getCategoryId(RootValue rv) |
| throws ManifoldCFException, ServiceInterruption |
| { |
| return rv.getRootValue().getCategoryId(rv.getRemainderPath()); |
| } |
| |
| // Protected static methods |
| |
| /** Convert the document specification to a set of query clauses meant to match file names. |
| *@param spec is the specification string. |
| *@return the correct part of the filter string. Empty string is returned if there is no filtering. |
| */ |
| protected static String buildFilterString(DocumentSpecification spec) |
| { |
| StringBuilder rval = new StringBuilder(); |
| |
| |
| boolean first = true; |
| int i = 0; |
| while (i < spec.getChildCount()) |
| { |
| SpecificationNode sn = spec.getChild(i++); |
| if (sn.getType().equals("include")) |
| { |
| String includeMatch = sn.getAttributeValue("filespec"); |
| if (includeMatch != null) |
| { |
| // Peel off the extension |
| int index = includeMatch.lastIndexOf("."); |
| if (index != -1) |
| { |
| String type = includeMatch.substring(index+1).toLowerCase().replace('*','%'); |
| if (first) |
| first = false; |
| else |
| rval.append(" or "); |
| rval.append("lower(FileType) like '").append(type).append("'"); |
| } |
| } |
| } |
| } |
| String filterStringPiece = rval.toString(); |
| if (filterStringPiece.length() == 0) |
| return "0>1"; |
| StringBuilder sb = new StringBuilder(); |
| sb.append("SubType=").append(new Integer(LAPI_DOCUMENTS.FOLDERSUBTYPE).toString()); |
| sb.append(" or SubType=").append(new Integer(LAPI_DOCUMENTS.COMPOUNDDOCUMENTSUBTYPE).toString()); |
| sb.append(" or SubType=").append(new Integer(LAPI_DOCUMENTS.PROJECTSUBTYPE).toString()); |
| sb.append(" or (SubType=").append(new Integer(LAPI_DOCUMENTS.DOCUMENTSUBTYPE).toString()); |
| sb.append(" and ("); |
| // Walk through the document spec to find the documents that match under the specified root |
| // include lower(column)=spec |
| sb.append(filterStringPiece); |
| sb.append("))"); |
| return sb.toString(); |
| } |
| |
| |
| /** 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; |
| boolean securityOn = true; |
| while (i < spec.getChildCount()) |
| { |
| SpecificationNode sn = spec.getChild(i++); |
| if (sn.getType().equals("access")) |
| { |
| String token = sn.getAttributeValue("token"); |
| map.put(token,token); |
| } |
| else if (sn.getType().equals("security")) |
| { |
| String value = sn.getAttributeValue("value"); |
| if (value.equals("on")) |
| securityOn = true; |
| else if (value.equals("off")) |
| securityOn = false; |
| } |
| } |
| if (!securityOn) |
| return null; |
| |
| String[] rval = new String[map.size()]; |
| Iterator iter = map.keySet().iterator(); |
| i = 0; |
| while (iter.hasNext()) |
| { |
| rval[i++] = (String)iter.next(); |
| } |
| return rval; |
| } |
| |
| /** Check if a file or directory should be included, given a document specification. |
| *@param filename is the name of the "file". |
| *@param documentSpecification is the specification. |
| *@return true if it should be included. |
| */ |
| protected static boolean checkInclude(String filename, DocumentSpecification documentSpecification) |
| throws ManifoldCFException |
| { |
| // Scan includes to insure we match |
| int i = 0; |
| while (i < documentSpecification.getChildCount()) |
| { |
| SpecificationNode sn = documentSpecification.getChild(i); |
| if (sn.getType().equals("include")) |
| { |
| String filespec = sn.getAttributeValue("filespec"); |
| // If it matches, we can exit this loop. |
| if (checkMatch(filename,0,filespec)) |
| break; |
| } |
| i++; |
| } |
| if (i == documentSpecification.getChildCount()) |
| return false; |
| |
| // We matched an include. Now, scan excludes to ditch it if needed. |
| i = 0; |
| while (i < documentSpecification.getChildCount()) |
| { |
| SpecificationNode sn = documentSpecification.getChild(i); |
| if (sn.getType().equals("exclude")) |
| { |
| String filespec = sn.getAttributeValue("filespec"); |
| // If it matches, we return false. |
| if (checkMatch(filename,0,filespec)) |
| return false; |
| } |
| i++; |
| } |
| |
| // System.out.println("Match!"); |
| return true; |
| } |
| |
| /** Check if a file should be ingested, given a document specification. It is presumed that |
| * documents that pass checkInclude() will be checked with this method. |
| *@param objID is the file ID. |
| *@param documentSpecification is the specification. |
| */ |
| protected boolean checkIngest(LivelinkContext llc, int objID, DocumentSpecification documentSpecification) |
| throws ManifoldCFException |
| { |
| // Since the only exclusions at this point are not based on file contents, this is a no-op. |
| return true; |
| } |
| |
| /** Check a match between two strings with wildcards. |
| *@param sourceMatch is the expanded string (no wildcards) |
| *@param sourceIndex is the starting point in the expanded string. |
| *@param match is the wildcard-based string. |
| *@return true if there is a match. |
| */ |
| protected static boolean checkMatch(String sourceMatch, int sourceIndex, String match) |
| { |
| // Note: The java regex stuff looks pretty heavyweight for this purpose. |
| // I've opted to try and do a simple recursive version myself, which is not compiled. |
| // Basically, the match proceeds by recursive descent through the string, so that all *'s cause |
| // recursion. |
| boolean caseSensitive = false; |
| |
| return processCheck(caseSensitive, sourceMatch, sourceIndex, match, 0); |
| } |
| |
| /** Recursive worker method for checkMatch. Returns 'true' if there is a path that consumes both |
| * strings in their entirety in a matched way. |
| *@param caseSensitive is true if file names are case sensitive. |
| *@param sourceMatch is the source string (w/o wildcards) |
| *@param sourceIndex is the current point in the source string. |
| *@param match is the match string (w/wildcards) |
| *@param matchIndex is the current point in the match string. |
| *@return true if there is a match. |
| */ |
| protected static boolean processCheck(boolean caseSensitive, String sourceMatch, int sourceIndex, |
| String match, int matchIndex) |
| { |
| // Logging.connectors.debug("Matching '"+sourceMatch+"' position "+Integer.toString(sourceIndex)+ |
| // " against '"+match+"' position "+Integer.toString(matchIndex)); |
| |
| // Match up through the next * we encounter |
| while (true) |
| { |
| // If we've reached the end, it's a match. |
| if (sourceMatch.length() == sourceIndex && match.length() == matchIndex) |
| return true; |
| // If one has reached the end but the other hasn't, no match |
| if (match.length() == matchIndex) |
| return false; |
| if (sourceMatch.length() == sourceIndex) |
| { |
| if (match.charAt(matchIndex) != '*') |
| return false; |
| matchIndex++; |
| continue; |
| } |
| char x = sourceMatch.charAt(sourceIndex); |
| char y = match.charAt(matchIndex); |
| if (!caseSensitive) |
| { |
| if (x >= 'A' && x <= 'Z') |
| x -= 'A'-'a'; |
| if (y >= 'A' && y <= 'Z') |
| y -= 'A'-'a'; |
| } |
| if (y == '*') |
| { |
| // Wildcard! |
| // We will recurse at this point. |
| // Basically, we want to combine the results for leaving the "*" in the match string |
| // at this point and advancing the source index, with skipping the "*" and leaving the source |
| // string alone. |
| return processCheck(caseSensitive,sourceMatch,sourceIndex+1,match,matchIndex) || |
| processCheck(caseSensitive,sourceMatch,sourceIndex,match,matchIndex+1); |
| } |
| if (y == '?' || x == y) |
| { |
| sourceIndex++; |
| matchIndex++; |
| } |
| else |
| return false; |
| } |
| } |
| |
| /** Class for returning volume id/folder id combination on path lookup. |
| */ |
| protected static class VolumeAndId |
| { |
| protected int volumeID; |
| protected int folderID; |
| |
| public VolumeAndId(int volumeID, int folderID) |
| { |
| this.volumeID = volumeID; |
| this.folderID = folderID; |
| } |
| |
| public int getVolumeID() |
| { |
| return volumeID; |
| } |
| |
| public int getPathId() |
| { |
| return folderID; |
| } |
| } |
| |
| /** Class that describes a metadata catid and path. |
| */ |
| protected static class MetadataPathItem |
| { |
| protected int catID; |
| protected String catName; |
| |
| /** Constructor. |
| */ |
| public MetadataPathItem(int catID, String catName) |
| { |
| this.catID = catID; |
| this.catName = catName; |
| } |
| |
| /** Get the cat ID. |
| *@return the id. |
| */ |
| public int getCatID() |
| { |
| return catID; |
| } |
| |
| /** Get the cat name. |
| *@return the category name path. |
| */ |
| public String getCatName() |
| { |
| return catName; |
| } |
| |
| } |
| |
| /** Class that describes a metadata catid and attribute set. |
| */ |
| protected static class MetadataItem |
| { |
| protected MetadataPathItem pathItem; |
| protected Map attributeNames = new HashMap(); |
| |
| /** Constructor. |
| */ |
| public MetadataItem(MetadataPathItem pathItem) |
| { |
| this.pathItem = pathItem; |
| } |
| |
| /** Add an attribute name. |
| */ |
| public void addAttribute(String attributeName) |
| { |
| attributeNames.put(attributeName,attributeName); |
| } |
| |
| /** Get the path object. |
| *@return the object. |
| */ |
| public MetadataPathItem getPathItem() |
| { |
| return pathItem; |
| } |
| |
| /** Get an iterator over the attribute names. |
| *@return the iterator. |
| */ |
| public Iterator getAttributeNames() |
| { |
| return attributeNames.keySet().iterator(); |
| } |
| |
| } |
| |
| /** Class that tracks paths associated with nodes, and also keeps track of the name |
| * of the metadata attribute to use for the path. |
| */ |
| protected class SystemMetadataDescription |
| { |
| // The livelink context |
| protected final LivelinkContext llc; |
| |
| // The path attribute name |
| protected String pathAttributeName; |
| |
| // The path separator |
| protected String pathSeparator; |
| |
| // The node ID to path name mapping (which acts like a cache) |
| protected Map pathMap = new HashMap(); |
| |
| // The path name map |
| protected MatchMap matchMap = new MatchMap(); |
| |
| /** Constructor */ |
| public SystemMetadataDescription(LivelinkContext llc, DocumentSpecification spec) |
| throws ManifoldCFException |
| { |
| this.llc = llc; |
| pathAttributeName = null; |
| pathSeparator = null; |
| int i = 0; |
| while (i < spec.getChildCount()) |
| { |
| SpecificationNode n = spec.getChild(i++); |
| if (n.getType().equals("pathnameattribute")) |
| { |
| pathAttributeName = n.getAttributeValue("value"); |
| pathSeparator = n.getAttributeValue("separator"); |
| if (pathSeparator == null) |
| pathSeparator = "/"; |
| } |
| else if (n.getType().equals("pathmap")) |
| { |
| String pathMatch = n.getAttributeValue("match"); |
| String pathReplace = n.getAttributeValue("replace"); |
| matchMap.appendMatchPair(pathMatch,pathReplace); |
| } |
| } |
| } |
| |
| /** Get the path attribute name. |
| *@return the path attribute name, or null if none specified. |
| */ |
| public String getPathAttributeName() |
| { |
| return pathAttributeName; |
| } |
| |
| /** Given an identifier, get the translated string that goes into the metadata. |
| */ |
| public String getPathAttributeValue(String documentIdentifier) |
| throws ManifoldCFException, ServiceInterruption |
| { |
| String path = getNodePathString(documentIdentifier); |
| if (path == null) |
| return null; |
| return matchMap.translate(path); |
| } |
| |
| /** For a given node, get its path. |
| */ |
| public String getNodePathString(String documentIdentifier) |
| throws ManifoldCFException, ServiceInterruption |
| { |
| if (Logging.connectors.isDebugEnabled()) |
| Logging.connectors.debug("Looking up path for '"+documentIdentifier+"'"); |
| String path = (String)pathMap.get(documentIdentifier); |
| if (path == null) |
| { |
| // Not yet present. Look it up, recursively |
| String identifierPart = documentIdentifier; |
| // Get the current node's name first |
| // D = Document; anything else = Folder |
| if (identifierPart.startsWith("D") || identifierPart.startsWith("F")) |
| { |
| // Strip off the letter |
| identifierPart = identifierPart.substring(1); |
| } |
| // See if there's a volume label; if not, use the default. |
| int colonPosition = identifierPart.indexOf(":"); |
| int volumeID; |
| int objectID; |
| try |
| { |
| if (colonPosition == -1) |
| { |
| // Default volume ID |
| volumeID = LLENTWK_VOL; |
| objectID = Integer.parseInt(identifierPart); |
| } |
| else |
| { |
| volumeID = Integer.parseInt(identifierPart.substring(0,colonPosition)); |
| objectID = Integer.parseInt(identifierPart.substring(colonPosition+1)); |
| } |
| } |
| catch (NumberFormatException e) |
| { |
| throw new ManifoldCFException("Bad document identifier: "+e.getMessage(),e); |
| } |
| |
| ObjectInformation objInfo = llc.getObjectInformation(volumeID,objectID); |
| if (!objInfo.exists()) |
| { |
| // The document identifier describes a path that does not exist. |
| // This is unexpected, but don't die: just log a warning and allow the higher level to deal with it. |
| Logging.connectors.warn("Livelink: Bad document identifier: '"+documentIdentifier+"' apparently does not exist, but need to find its path"); |
| return null; |
| } |
| |
| // Get the name attribute |
| String name = objInfo.getName(); |
| // Get the parentID attribute |
| int parentID = objInfo.getParentId().intValue(); |
| if (parentID == -1) |
| path = name; |
| else |
| { |
| String parentIdentifier = "F"+Integer.toString(volumeID)+":"+Integer.toString(parentID); |
| String parentPath = getNodePathString(parentIdentifier); |
| if (parentPath == null) |
| return null; |
| path = parentPath + pathSeparator + name; |
| } |
| |
| pathMap.put(documentIdentifier,path); |
| } |
| |
| return path; |
| } |
| } |
| |
| |
| /** Class that manages to find catid's and attribute names that have been specified. |
| * This accepts a part of the version string which contains the string-ified metadata |
| * spec, rather than pulling it out of the document specification. That guarantees that |
| * the version string actually corresponds to the document that was ingested. |
| */ |
| protected class MetadataDescription |
| { |
| protected final LivelinkContext llc; |
| |
| // This is a map of category name to category ID and attributes |
| protected Map categoryMap = new HashMap(); |
| |
| /** Constructor. |
| */ |
| public MetadataDescription(LivelinkContext llc) |
| { |
| this.llc = llc; |
| } |
| |
| /** Iterate over the metadata items represented by the specified chunk of version string. |
| *@return an iterator over MetadataItem objects. |
| */ |
| public Iterator getItems(ArrayList metadataItems) |
| throws ManifoldCFException, ServiceInterruption |
| { |
| // This is the map that will be iterated over for a return value. |
| // It gets built out of (hopefully cached) data from categoryMap. |
| HashMap newMap = new HashMap(); |
| |
| // Start at root |
| ObjectInformation rootValue = null; |
| |
| // Walk through string and process each metadata element in turn. |
| int i = 0; |
| while (i < metadataItems.size()) |
| { |
| String metadataSpec = (String)metadataItems.get(i++); |
| StringBuilder categoryBuffer = new StringBuilder(); |
| StringBuilder attributeBuffer = new StringBuilder(); |
| unpackCategoryAttribute(categoryBuffer,attributeBuffer,metadataSpec); |
| String category = categoryBuffer.toString(); |
| String attributeName = attributeBuffer.toString(); |
| |
| // If there's already an entry for this category in the return map, use it |
| MetadataItem mi = (MetadataItem)newMap.get(category); |
| if (mi == null) |
| { |
| // Now, look up the node information |
| // Convert category to cat id. |
| MetadataPathItem item = (MetadataPathItem)categoryMap.get(category); |
| if (item == null) |
| { |
| RootValue rv = new RootValue(llc,category); |
| if (rootValue == null) |
| { |
| rootValue = rv.getRootValue(); |
| } |
| |
| // Get the object id of the category the path describes. |
| // NOTE: We don't use the RootValue version of getCategoryId because |
| // we want to use the cached value of rootValue, if it was around. |
| int catObjectID = rootValue.getCategoryId(rv.getRemainderPath()); |
| if (catObjectID != -1) |
| { |
| item = new MetadataPathItem(catObjectID,rv.getRemainderPath()); |
| categoryMap.put(category,item); |
| } |
| } |
| mi = new MetadataItem(item); |
| newMap.put(category,mi); |
| } |
| // Add attribute name to category |
| mi.addAttribute(attributeName); |
| } |
| |
| return newMap.values().iterator(); |
| } |
| |
| } |
| |
| |
| /** This class caches the category path strings associated with a given category object identifier. |
| * The goal is to allow reasonably speedy lookup of the path name, so we can put it into the metadata part of the |
| * version string. |
| */ |
| protected class CategoryPathAccumulator |
| { |
| // Livelink context |
| protected final LivelinkContext llc; |
| |
| // This is the map from category ID to category path name. |
| // It's keyed by an Integer formed from the id, and has String values. |
| protected HashMap categoryPathMap = new HashMap(); |
| |
| // This is the map from category ID to attribute names. Keyed |
| // by an Integer formed from the id, and has a String[] value. |
| protected HashMap attributeMap = new HashMap(); |
| |
| /** Constructor */ |
| public CategoryPathAccumulator(LivelinkContext llc) |
| { |
| this.llc = llc; |
| } |
| |
| /** Get a specified set of packed category paths with attribute names, given the category identifiers */ |
| public String[] getCategoryPathsAttributeNames(int[] catIDs) |
| throws ManifoldCFException, ServiceInterruption |
| { |
| HashMap set = new HashMap(); |
| int i = 0; |
| while (i < catIDs.length) |
| { |
| Integer key = new Integer(catIDs[i++]); |
| String pathValue = (String)categoryPathMap.get(key); |
| if (pathValue == null) |
| { |
| // Chase the path back up the chain |
| pathValue = findPath(key.intValue()); |
| if (pathValue == null) |
| continue; |
| categoryPathMap.put(key,pathValue); |
| } |
| String[] attributeNames = (String[])attributeMap.get(key); |
| if (attributeNames == null) |
| { |
| // Get the attributes for this category |
| attributeNames = findAttributes(key.intValue()); |
| if (attributeNames == null) |
| continue; |
| attributeMap.put(key,attributeNames); |
| } |
| // Now, put the path and the attributes into the hash. |
| int j = 0; |
| while (j < attributeNames.length) |
| { |
| String metadataName = packCategoryAttribute(pathValue,attributeNames[j++]); |
| set.put(metadataName,metadataName); |
| } |
| } |
| |
| String[] rval = new String[set.size()]; |
| i = 0; |
| Iterator iter = set.keySet().iterator(); |
| while (iter.hasNext()) |
| { |
| rval[i++] = (String)iter.next(); |
| } |
| |
| return rval; |
| } |
| |
| /** Find a category path given a category ID */ |
| protected String findPath(int catID) |
| throws ManifoldCFException, ServiceInterruption |
| { |
| return getObjectPath(llc.getObjectInformation(0,catID)); |
| } |
| |
| /** Get the complete path for an object. |
| */ |
| protected String getObjectPath(ObjectInformation currentObject) |
| throws ManifoldCFException, ServiceInterruption |
| { |
| String path = null; |
| while (true) |
| { |
| if (currentObject.isCategoryWorkspace()) |
| return CATEGORY_NAME + ((path==null)?"":":" + path); |
| else if (currentObject.isEntityWorkspace()) |
| return ENTWKSPACE_NAME + ((path==null)?"":":" + path); |
| |
| if (!currentObject.exists()) |
| { |
| // The document identifier describes a path that does not exist. |
| // This is unexpected, but an exception would terminate the job, and we don't want that. |
| Logging.connectors.warn("Livelink: Bad identifier found? "+currentObject.toString()+" apparently does not exist, but need to look up its path"); |
| return null; |
| } |
| |
| // Get the name attribute |
| String name = currentObject.getName(); |
| if (path == null) |
| path = name; |
| else |
| path = name + "/" + path; |
| |
| // Get the parentID attribute |
| int parentID = currentObject.getParentId().intValue(); |
| if (parentID == -1) |
| { |
| // Oops, hit the top of the path without finding the workspace we're in. |
| // No idea where it lives; note this condition and exit. |
| Logging.connectors.warn("Livelink: Object ID "+currentObject.toString()+" doesn't seem to live in enterprise or category workspace! Path I got was '"+path+"'"); |
| return null; |
| } |
| currentObject = llc.getObjectInformation(0,parentID); |
| } |
| } |
| |
| /** Find a set of attributes given a category ID */ |
| protected String[] findAttributes(int catID) |
| throws ManifoldCFException, ServiceInterruption |
| { |
| return getCategoryAttributes(catID); |
| } |
| |
| } |
| |
| /** Class representing a root value object, plus remainder string. |
| * This class peels off the workspace name prefix from a path string or |
| * attribute string, and finds the right workspace root node and remainder |
| * path. |
| */ |
| protected class RootValue |
| { |
| protected final LivelinkContext llc; |
| protected String workspaceName; |
| protected ObjectInformation rootValue = null; |
| protected String remainderPath; |
| |
| /** Constructor. |
| *@param pathString is the path string. |
| */ |
| public RootValue(LivelinkContext llc, String pathString) |
| { |
| this.llc = llc; |
| int colonPos = pathString.indexOf(":"); |
| if (colonPos == -1) |
| { |
| remainderPath = pathString; |
| workspaceName = ENTWKSPACE_NAME; |
| } |
| else |
| { |
| workspaceName = pathString.substring(0,colonPos); |
| remainderPath = pathString.substring(colonPos+1); |
| } |
| } |
| |
| /** Get the path string. |
| *@return the path string (without the workspace name prefix). |
| */ |
| public String getRemainderPath() |
| { |
| return remainderPath; |
| } |
| |
| /** Get the root node. |
| *@return the root node. |
| */ |
| public ObjectInformation getRootValue() |
| throws ManifoldCFException, ServiceInterruption |
| { |
| if (rootValue == null) |
| { |
| if (workspaceName.equals(CATEGORY_NAME)) |
| rootValue = llc.getObjectInformation(LLCATWK_VOL,LLCATWK_ID); |
| else if (workspaceName.equals(ENTWKSPACE_NAME)) |
| rootValue = llc.getObjectInformation(LLENTWK_VOL,LLENTWK_ID); |
| else |
| throw new ManifoldCFException("Bad workspace name: "+workspaceName); |
| } |
| |
| if (!rootValue.exists()) |
| { |
| Logging.connectors.warn("Livelink: Could not get workspace/volume ID! Retrying..."); |
| // This cannot mean a real failure; it MUST mean that we have had an intermittent communication hiccup. So, pass it off as a service interruption. |
| throw new ServiceInterruption("Service interruption getting root value",new ManifoldCFException("Could not get workspace/volume id"),System.currentTimeMillis()+60000L, |
| System.currentTimeMillis()+600000L,-1,true); |
| } |
| |
| return rootValue; |
| } |
| } |
| |
| // Here's an interesting note. All of the LAPI exceptions are subclassed off of RuntimeException. This makes life |
| // hell because there is no superclass exception to capture, and even tweaky server communication issues wind up throwing |
| // uncaught RuntimeException's up the stack. |
| // |
| // To fix this rather bad design, all places that invoke LAPI need to catch RuntimeException and run it through the following |
| // method for interpretation and logging. |
| // |
| |
| /** Interpret runtimeexception to search for livelink API errors. Throws an appropriately reinterpreted exception, or |
| * just returns if the exception indicates that a short-cycle retry attempt should be made. (In that case, the appropriate |
| * wait has been already performed). |
| *@param e is the RuntimeException caught |
| *@param failIfTimeout is true if, for transient conditions, we want to signal failure if the timeout condition is acheived. |
| */ |
| protected int handleLivelinkRuntimeException(RuntimeException e, int sanityRetryCount, boolean failIfTimeout) |
| throws ManifoldCFException, ServiceInterruption |
| { |
| if ( |
| e instanceof com.opentext.api.LLHTTPAccessDeniedException || |
| e instanceof com.opentext.api.LLHTTPClientException || |
| e instanceof com.opentext.api.LLHTTPServerException || |
| e instanceof com.opentext.api.LLIndexOutOfBoundsException || |
| e instanceof com.opentext.api.LLNoFieldSpecifiedException || |
| e instanceof com.opentext.api.LLNoValueSpecifiedException || |
| e instanceof com.opentext.api.LLSecurityProviderException || |
| e instanceof com.opentext.api.LLUnknownFieldException || |
| e instanceof NumberFormatException || |
| e instanceof ArrayIndexOutOfBoundsException |
| ) |
| { |
| String details = llServer.getErrors(); |
| long currentTime = System.currentTimeMillis(); |
| throw new ServiceInterruption("Livelink API error: "+e.getMessage()+((details==null)?"":"; "+details),e,currentTime + 5*60000L,currentTime+12*60*60000L,-1,failIfTimeout); |
| } |
| else if ( |
| e instanceof com.opentext.api.LLBadServerCertificateException || |
| e instanceof com.opentext.api.LLHTTPCGINotFoundException || |
| e instanceof com.opentext.api.LLCouldNotConnectHTTPException || |
| e instanceof com.opentext.api.LLHTTPForbiddenException || |
| e instanceof com.opentext.api.LLHTTPProxyAuthRequiredException || |
| e instanceof com.opentext.api.LLHTTPRedirectionException || |
| e instanceof com.opentext.api.LLUnsupportedAuthMethodException || |
| e instanceof com.opentext.api.LLWebAuthInitException |
| ) |
| { |
| String details = llServer.getErrors(); |
| throw new ManifoldCFException("Livelink API error: "+e.getMessage()+((details==null)?"":"; "+details),e); |
| } |
| else if (e instanceof com.opentext.api.LLSSLNotAvailableException) |
| { |
| String details = llServer.getErrors(); |
| throw new ManifoldCFException("Missing llssl.jar error: "+e.getMessage()+((details==null)?"":"; "+details),e); |
| } |
| else if (e instanceof com.opentext.api.LLIllegalOperationException) |
| { |
| // This usually means that LAPI has had a minor communication difficulty but hasn't reported it accurately. |
| // We *could* throw a ServiceInterruption, but OpenText recommends to just retry almost immediately. |
| String details = llServer.getErrors(); |
| return assessRetry(sanityRetryCount,new ManifoldCFException("Livelink API illegal operation error: "+e.getMessage()+((details==null)?"":"; "+details),e)); |
| } |
| else if (e instanceof com.opentext.api.LLIOException || (e instanceof RuntimeException && e.getClass().getName().startsWith("com.opentext.api."))) |
| { |
| // Catching obfuscated and unspecified opentext runtime exceptions now too - these come from llssl.jar. We |
| // have to presume these are SSL connection errors; nothing else to go by unfortunately. UGH. |
| |
| // Treat this as a transient error; try again in 5 minutes, and only fail after 12 hours of trying |
| |
| // LAPI is returning errors that are not terribly explicit, and I don't have control over their wording, so check that server can be resolved by DNS, |
| // so that a better error message can be returned. |
| try |
| { |
| InetAddress.getByName(serverName); |
| } |
| catch (UnknownHostException e2) |
| { |
| throw new ManifoldCFException("Server name '"+serverName+"' cannot be resolved",e2); |
| } |
| |
| long currentTime = System.currentTimeMillis(); |
| throw new ServiceInterruption(e.getMessage(),e,currentTime + 5*60000L,currentTime+12*60*60000L,-1,failIfTimeout); |
| } |
| else |
| throw e; |
| } |
| |
| /** Do a retry, or throw an exception if the retry count has been exhausted |
| */ |
| protected static int assessRetry(int sanityRetryCount, ManifoldCFException e) |
| throws ManifoldCFException |
| { |
| if (sanityRetryCount == 0) |
| { |
| throw e; |
| } |
| |
| sanityRetryCount--; |
| |
| try |
| { |
| ManifoldCF.sleep(1000L); |
| } |
| catch (InterruptedException e2) |
| { |
| throw new ManifoldCFException(e2.getMessage(),e2,ManifoldCFException.INTERRUPTED); |
| } |
| // Exit the method |
| return sanityRetryCount; |
| |
| } |
| |
| /** This thread performs a LAPI FetchVersion command, streaming the resulting |
| * document back through a XThreadInputStream to the invoking thread. |
| */ |
| protected class DocumentReadingThread extends Thread |
| { |
| |
| protected Throwable exception = null; |
| protected final int volumeID; |
| protected final int docID; |
| protected final int versionNumber; |
| protected final XThreadInputStream stream; |
| |
| public DocumentReadingThread(int volumeID, int docID, int versionNumber) |
| { |
| super(); |
| this.volumeID = volumeID; |
| this.docID = docID; |
| this.versionNumber = versionNumber; |
| this.stream = new XThreadInputStream(); |
| setDaemon(true); |
| } |
| |
| @Override |
| public void run() |
| { |
| try |
| { |
| XThreadOutputStream outputStream = new XThreadOutputStream(stream); |
| try |
| { |
| int status = LLDocs.FetchVersion(volumeID, docID, versionNumber, outputStream); |
| if (status != 0) |
| { |
| throw new ManifoldCFException("Error retrieving contents of document "+Integer.toString(volumeID)+":"+Integer.toString(docID)+" revision "+versionNumber+" : Status="+Integer.toString(status)+" ("+llServer.getErrors()+")"); |
| } |
| } |
| finally |
| { |
| outputStream.close(); |
| } |
| } catch (Throwable e) { |
| this.exception = e; |
| } |
| } |
| |
| public InputStream getSafeInputStream() { |
| return stream; |
| } |
| |
| public void finishUp() |
| throws InterruptedException, ManifoldCFException |
| { |
| // This will be called during the finally |
| // block in the case where all is well (and |
| // the stream completed) and in the case where |
| // there were exceptions. |
| stream.abort(); |
| join(); |
| Throwable thr = exception; |
| if (thr != null) { |
| if (thr instanceof ManifoldCFException) |
| throw (ManifoldCFException) thr; |
| else if (thr instanceof RuntimeException) |
| throw (RuntimeException) thr; |
| else if (thr instanceof Error) |
| throw (Error) thr; |
| else |
| throw new RuntimeException("Unhandled exception of type: "+thr.getClass().getName(),thr); |
| } |
| } |
| |
| } |
| |
| /** This thread does the actual socket communication with the server. |
| * It's set up so that it can be abandoned at shutdown time. |
| * |
| * The way it works is as follows: |
| * - it starts the transaction |
| * - it receives the response, and saves that for the calling class to inspect |
| * - it transfers the data part to an input stream provided to the calling class |
| * - it shuts the connection down |
| * |
| * If there is an error, the sequence is aborted, and an exception is recorded |
| * for the calling class to examine. |
| * |
| * The calling class basically accepts the sequence above. It starts the |
| * thread, and tries to get a response code. If instead an exception is seen, |
| * the exception is thrown up the stack. |
| */ |
| protected static class ExecuteMethodThread extends Thread |
| { |
| /** Client and method, all preconfigured */ |
| protected final HttpClient httpClient; |
| protected final HttpRequestBase executeMethod; |
| |
| protected HttpResponse response = null; |
| protected Throwable responseException = null; |
| protected XThreadInputStream threadStream = null; |
| protected InputStream bodyStream = null; |
| protected boolean streamCreated = false; |
| protected Throwable streamException = null; |
| protected boolean abortThread = false; |
| |
| protected Throwable shutdownException = null; |
| |
| protected Throwable generalException = null; |
| |
| public ExecuteMethodThread(HttpClient httpClient, HttpRequestBase executeMethod) |
| { |
| super(); |
| setDaemon(true); |
| this.httpClient = httpClient; |
| this.executeMethod = executeMethod; |
| } |
| |
| public void run() |
| { |
| try |
| { |
| try |
| { |
| // Call the execute method appropriately |
| synchronized (this) |
| { |
| if (!abortThread) |
| { |
| try |
| { |
| response = httpClient.execute(executeMethod); |
| } |
| catch (java.net.SocketTimeoutException e) |
| { |
| responseException = e; |
| } |
| catch (ConnectTimeoutException e) |
| { |
| responseException = e; |
| } |
| catch (InterruptedIOException e) |
| { |
| throw e; |
| } |
| catch (Throwable e) |
| { |
| responseException = e; |
| } |
| this.notifyAll(); |
| } |
| } |
| |
| // Start the transfer of the content |
| if (responseException == null) |
| { |
| synchronized (this) |
| { |
| if (!abortThread) |
| { |
| try |
| { |
| bodyStream = response.getEntity().getContent(); |
| if (bodyStream != null) |
| { |
| threadStream = new XThreadInputStream(bodyStream); |
| } |
| streamCreated = true; |
| } |
| catch (java.net.SocketTimeoutException e) |
| { |
| streamException = e; |
| } |
| catch (ConnectTimeoutException e) |
| { |
| streamException = e; |
| } |
| catch (InterruptedIOException e) |
| { |
| throw e; |
| } |
| catch (Throwable e) |
| { |
| streamException = e; |
| } |
| this.notifyAll(); |
| } |
| } |
| } |
| |
| if (responseException == null && streamException == null) |
| { |
| if (threadStream != null) |
| { |
| // Stuff the content until we are done |
| threadStream.stuffQueue(); |
| } |
| } |
| |
| } |
| finally |
| { |
| if (bodyStream != null) |
| { |
| try |
| { |
| bodyStream.close(); |
| } |
| catch (IOException e) |
| { |
| } |
| bodyStream = null; |
| } |
| synchronized (this) |
| { |
| try |
| { |
| executeMethod.abort(); |
| } |
| catch (Throwable e) |
| { |
| shutdownException = e; |
| } |
| this.notifyAll(); |
| } |
| } |
| } |
| catch (Throwable e) |
| { |
| // We catch exceptions here that should ONLY be InterruptedExceptions, as a result of the thread being aborted. |
| this.generalException = e; |
| } |
| } |
| |
| public int getResponseCode() |
| throws InterruptedException, IOException, HttpException |
| { |
| // Must wait until the response object is there |
| while (true) |
| { |
| synchronized (this) |
| { |
| checkException(responseException); |
| if (response != null) |
| return response.getStatusLine().getStatusCode(); |
| wait(); |
| } |
| } |
| } |
| |
| public long getResponseContentLength() |
| throws InterruptedException, IOException, HttpException |
| { |
| String contentLength = getFirstHeader("Content-Length"); |
| if (contentLength == null || contentLength.length() == 0) |
| return -1L; |
| return new Long(contentLength.trim()).longValue(); |
| } |
| |
| public String getFirstHeader(String headerName) |
| throws InterruptedException, IOException, HttpException |
| { |
| // Must wait for the response object to appear |
| while (true) |
| { |
| synchronized (this) |
| { |
| checkException(responseException); |
| if (response != null) |
| { |
| Header h = response.getFirstHeader(headerName); |
| if (h == null) |
| return null; |
| return h.getValue(); |
| } |
| wait(); |
| } |
| } |
| } |
| |
| public InputStream getSafeInputStream() |
| throws InterruptedException, IOException, HttpException |
| { |
| // Must wait until stream is created, or until we note an exception was thrown. |
| while (true) |
| { |
| synchronized (this) |
| { |
| if (responseException != null) |
| throw new IllegalStateException("Check for response before getting stream"); |
| checkException(streamException); |
| if (streamCreated) |
| return threadStream; |
| wait(); |
| } |
| } |
| } |
| |
| public void abort() |
| { |
| // This will be called during the finally |
| // block in the case where all is well (and |
| // the stream completed) and in the case where |
| // there were exceptions. |
| synchronized (this) |
| { |
| if (streamCreated) |
| { |
| if (threadStream != null) |
| threadStream.abort(); |
| } |
| abortThread = true; |
| } |
| } |
| |
| public void finishUp() |
| throws InterruptedException |
| { |
| join(); |
| } |
| |
| protected synchronized void checkException(Throwable exception) |
| throws IOException, HttpException |
| { |
| if (exception != null) |
| { |
| // Throw the current exception, but clear it, so no further throwing is possible on the same problem. |
| Throwable e = exception; |
| if (e instanceof IOException) |
| throw (IOException)e; |
| else if (e instanceof HttpException) |
| throw (HttpException)e; |
| else if (e instanceof RuntimeException) |
| throw (RuntimeException)e; |
| else if (e instanceof Error) |
| throw (Error)e; |
| else |
| throw new RuntimeException("Unhandled exception of type: "+e.getClass().getName(),e); |
| } |
| } |
| |
| } |
| |
| |
| } |
| |
| |