/* $Id: SvnConnector.java 994959 2010-09-08 10:04:42Z krycek $ */ /** * 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.generic; import java.io.*; import java.net.MalformedURLException; import java.net.URL; import org.apache.manifoldcf.core.util.URLEncoder; import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import javax.xml.bind.JAXBContext; import javax.xml.bind.JAXBException; import javax.xml.bind.Unmarshaller; import javax.xml.parsers.FactoryConfigurationError; import javax.xml.parsers.ParserConfigurationException; import javax.xml.parsers.SAXParser; import javax.xml.parsers.SAXParserFactory; import org.apache.http.HttpException; import org.apache.http.HttpRequest; import org.apache.http.HttpRequestInterceptor; import org.apache.http.HttpResponse; import org.apache.http.HttpStatus; import org.apache.http.auth.AuthScope; import org.apache.http.auth.Credentials; import org.apache.http.auth.UsernamePasswordCredentials; import org.apache.http.client.HttpClient; import org.apache.http.client.methods.HttpGet; import org.apache.http.impl.auth.BasicScheme; import org.apache.http.impl.client.DefaultHttpClient; import org.apache.http.params.HttpConnectionParams; import org.apache.http.protocol.HttpContext; import org.apache.http.util.EntityUtils; import org.apache.manifoldcf.agents.interfaces.*; import org.apache.manifoldcf.connectorcommon.common.XThreadInputStream; import org.apache.manifoldcf.connectorcommon.common.XThreadStringBuffer; import org.apache.manifoldcf.core.interfaces.*; import org.apache.manifoldcf.core.system.ManifoldCF; import org.apache.manifoldcf.crawler.connectors.BaseRepositoryConnector; import org.apache.manifoldcf.crawler.connectors.generic.api.Item; import org.apache.manifoldcf.crawler.connectors.generic.api.Items; import org.apache.manifoldcf.crawler.connectors.generic.api.Meta; import org.apache.manifoldcf.crawler.interfaces.*; import org.apache.manifoldcf.ui.util.Encoder; import org.xml.sax.Attributes; import org.xml.sax.SAXException; import org.xml.sax.helpers.DefaultHandler; public class GenericConnector extends BaseRepositoryConnector { public static final String _rcsid = "@(#)$Id: GenericConnector.java 994959 2010-09-08 10:04:42Z redguy $"; /** * Deny access token for default authority */ private final static String defaultAuthorityDenyToken = "DEAD_AUTHORITY"; private final static String ACTION_PARAM_NAME = "action"; private final static String ACTION_CHECK = "check"; private final static String ACTION_SEED = "seed"; private final static String ACTION_ITEMS = "items"; private final static String ACTION_ITEM = "item"; private String genericLogin = null; private String genericPassword = null; private String genericEntryPoint = null; private int connectionTimeoutMillis = 60 * 1000; private int socketTimeoutMillis = 30 * 60 * 1000; protected static final String RELATIONSHIP_RELATED = "related"; private ConcurrentHashMap<String, Item> documentCache = new ConcurrentHashMap<String, Item>(10); /** * Constructor. */ public GenericConnector() { } @Override public int getMaxDocumentRequest() { return 10; } @Override public String[] getRelationshipTypes() { return new String[]{RELATIONSHIP_RELATED}; } @Override public int getConnectorModel() { return GenericConnector.MODEL_ADD_CHANGE; } /** * For any given document, list the bins that it is a member of. */ @Override public String[] getBinNames(String documentIdentifier) { // Return the host name return new String[]{genericEntryPoint}; } // All methods below this line will ONLY be called if a connect() call succeeded // on this instance! /** * Connect. The configuration parameters are included. * * @param configParams are the configuration parameters for this connection. * Note well: There are no exceptions allowed from this call, since it is * expected to mainly establish connection parameters. */ @Override public void connect(ConfigParams configParams) { super.connect(configParams); genericEntryPoint = getParam(configParams, "genericEntryPoint", null); genericLogin = getParam(configParams, "genericLogin", null); genericPassword = ""; try { genericPassword = ManifoldCF.deobfuscate(getParam(configParams, "genericPassword", "")); } catch (ManifoldCFException ignore) { } connectionTimeoutMillis = Integer.parseInt(getParam(configParams, "genericConnectionTimeout", "60000")); if (connectionTimeoutMillis == 0) { connectionTimeoutMillis = 60000; } socketTimeoutMillis = Integer.parseInt(getParam(configParams, "genericSocketTimeout", "1800000")); if (socketTimeoutMillis == 0) { socketTimeoutMillis = 1800000; } } protected DefaultHttpClient getClient() throws ManifoldCFException { DefaultHttpClient cl = new DefaultHttpClient(); if (genericLogin != null && !genericLogin.isEmpty()) { try { URL url = new URL(genericEntryPoint); Credentials credentials = new UsernamePasswordCredentials(genericLogin, genericPassword); cl.getCredentialsProvider().setCredentials(new AuthScope(url.getHost(), url.getPort() > 0 ? url.getPort() : 80, AuthScope.ANY_REALM), credentials); cl.addRequestInterceptor(new PreemptiveAuth(credentials), 0); } catch (MalformedURLException ex) { throw new ManifoldCFException("getClient exception: " + ex.getMessage(), ex); } } HttpConnectionParams.setConnectionTimeout(cl.getParams(), connectionTimeoutMillis); HttpConnectionParams.setSoTimeout(cl.getParams(), socketTimeoutMillis); return cl; } @Override public String check() throws ManifoldCFException { HttpClient client = getClient(); try { CheckThread checkThread = new CheckThread(client, genericEntryPoint + "?" + ACTION_PARAM_NAME + "=" + ACTION_CHECK); checkThread.start(); checkThread.join(); if (checkThread.getException() != null) { Throwable thr = checkThread.getException(); return "Check exception: " + thr.getMessage(); } return checkThread.getResult(); } catch (InterruptedException ex) { throw new ManifoldCFException(ex.getMessage(), ex, ManifoldCFException.INTERRUPTED); } } @Override public String addSeedDocuments(ISeedingActivity activities, Specification spec, String lastSeedVersion, long seedTime, int jobMode) throws ManifoldCFException, ServiceInterruption { long startTime; if (lastSeedVersion == null) startTime = 0L; else { // Unpack seed time from seed version string startTime = new Long(lastSeedVersion).longValue(); } HttpClient client = getClient(); SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.ROOT); StringBuilder url = new StringBuilder(genericEntryPoint); url.append("?").append(ACTION_PARAM_NAME).append("=").append(ACTION_SEED); if (startTime > 0) { url.append("&startTime=").append(sdf.format(new Date(startTime))); } url.append("&endTime=").append(sdf.format(new Date(seedTime))); for (int i = 0; i < spec.getChildCount(); i++) { SpecificationNode sn = spec.getChild(i); if (sn.getType().equals("param")) { String paramName = sn.getAttributeValue("name"); String paramValue = sn.getValue(); url.append("&").append(URLEncoder.encode(paramName)).append("=").append(URLEncoder.encode(paramValue)); } } ExecuteSeedingThread t = new ExecuteSeedingThread(client, url.toString()); try { t.start(); boolean wasInterrupted = false; try { XThreadStringBuffer seedBuffer = t.getBuffer(); // Pick up the paths, and add them to the activities, before we join with the child thread. while (true) { // The only kind of exceptions this can throw are going to shut the process down. String docPath = seedBuffer.fetch(); if (docPath == null) { break; } // Add the pageID to the queue activities.addSeedDocument(docPath); } } catch (InterruptedException e) { wasInterrupted = true; throw e; } catch (ManifoldCFException e) { if (e.getErrorCode() == ManifoldCFException.INTERRUPTED) { wasInterrupted = true; } throw e; } finally { if (!wasInterrupted) { t.finishUp(); } } } catch (InterruptedException e) { t.interrupt(); throw new ManifoldCFException("Interrupted: " + e.getMessage(), e, ManifoldCFException.INTERRUPTED); } return new Long(seedTime).toString(); } @Override public void processDocuments(String[] documentIdentifiers, IExistingVersions statuses, Specification spec, IProcessActivity activities, int jobMode, boolean usesDefaultAuthority) throws ManifoldCFException, ServiceInterruption { // Forced acls String[] acls = getAcls(spec); // Sort it, java.util.Arrays.sort(acls); String rights = java.util.Arrays.toString(acls); String genericAuthMode = "provided"; for (int i = 0; i < spec.getChildCount(); i++) { SpecificationNode sn = spec.getChild(i); if (sn.getType().equals("genericAuthMode")) { genericAuthMode = sn.getValue(); break; } } HttpClient client = getClient(); StringBuilder url = new StringBuilder(genericEntryPoint); url.append("?").append(ACTION_PARAM_NAME).append("=").append(ACTION_ITEMS); for (int i = 0; i < documentIdentifiers.length; i++) { url.append("&id[]=").append(URLEncoder.encode(documentIdentifiers[i])); } for (int i = 0; i < spec.getChildCount(); i++) { SpecificationNode sn = spec.getChild(i); if (sn.getType().equals("param")) { String paramName = sn.getAttributeValue("name"); String paramValue = sn.getValue(); url.append("&").append(URLEncoder.encode(paramName)).append("=").append(URLEncoder.encode(paramValue)); } } String[] versions = null; try { DocumentVersionThread versioningThread = new DocumentVersionThread(client, url.toString(), documentIdentifiers, genericAuthMode, rights, documentCache); versioningThread.start(); try { versions = versioningThread.finishUp(); } catch (IOException ex) { handleIOException((IOException)ex); } catch (InterruptedException ex) { throw new ManifoldCFException(ex.getMessage(), ex, ManifoldCFException.INTERRUPTED); } // Figure out which ones we need to process, and which we should delete for (int i = 0; i < documentIdentifiers.length; i++) { String documentIdentifier = documentIdentifiers[i]; String versionString = versions[i]; if (versionString == null) { activities.deleteDocument(documentIdentifier); continue; } Item item = documentCache.get(documentIdentifier); if (item == null) { throw new ManifoldCFException("processDocuments error - no cache entry for: " + documentIdentifier); } if (item.related != null) { for (String rel : item.related) { activities.addDocumentReference(rel, documentIdentifier, RELATIONSHIP_RELATED); } } if (versionString.length() == 0 || activities.checkDocumentNeedsReindexing(documentIdentifier,versionString)) { // Process the document RepositoryDocument doc = new RepositoryDocument(); if (item.mimeType != null) { doc.setMimeType(item.mimeType); } if (item.created != null) { doc.setCreatedDate(item.created); } if (item.updated != null) { doc.setModifiedDate(item.updated); } if (item.fileName != null) { doc.setFileName(item.fileName); } if (item.metadata != null) { HashMap<String, List<String>> meta = new HashMap<String, List<String>>(); for (Meta m : item.metadata) { if (meta.containsKey(m.name)) { meta.get(m.name).add(m.value); } else { List<String> list = new ArrayList<String>(1); list.add(m.value); meta.put(m.name, list); } } for (String name : meta.keySet()) { List<String> values = meta.get(name); if (values.size() > 1) { String[] svals = new String[values.size()]; for (int j = 0; j < values.size(); j++) { svals[j] = values.get(j); } doc.addField(name, svals); } else { doc.addField(name, values.get(0)); } } } if ("provided".equals(genericAuthMode)) { if (item.auth != null) { String[] acl = new String[item.auth.size()]; for (int j = 0; j < item.auth.size(); j++) { acl[j] = item.auth.get(j); } doc.setSecurity(RepositoryDocument.SECURITY_TYPE_DOCUMENT,acl,new String[]{defaultAuthorityDenyToken}); } } else { if (acls.length > 0) { doc.setSecurity(RepositoryDocument.SECURITY_TYPE_DOCUMENT,acls,new String[]{defaultAuthorityDenyToken}); } } if (item.content != null) { try { byte[] content = item.content.getBytes(StandardCharsets.UTF_8); ByteArrayInputStream is = new ByteArrayInputStream(content); try { doc.setBinary(is, content.length); activities.ingestDocumentWithException(documentIdentifier, versionString, item.url, doc); is.close(); } finally { is.close(); } } catch (IOException ex) { handleIOException(ex); } } else { url = new StringBuilder(genericEntryPoint); url.append("?").append(ACTION_PARAM_NAME).append("=").append(ACTION_ITEM); url.append("&id=").append(URLEncoder.encode(documentIdentifier)); for (int j = 0; j < spec.getChildCount(); j++) { SpecificationNode sn = spec.getChild(j); if (sn.getType().equals("param")) { String paramName = sn.getAttributeValue("name"); String paramValue = sn.getValue(); url.append("&").append(URLEncoder.encode(paramName)).append("=").append(URLEncoder.encode(paramValue)); } } ExecuteProcessThread t = new ExecuteProcessThread(client, url.toString()); try { t.start(); boolean wasInterrupted = false; try { InputStream is = t.getSafeInputStream(); long fileLength = t.getStreamLength(); try { // Can only index while background thread is running! doc.setBinary(is, fileLength); activities.ingestDocumentWithException(documentIdentifier, versionString, item.url, doc); } 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(); } } } catch (InterruptedException e) { t.interrupt(); throw new ManifoldCFException("Interrupted: " + e.getMessage(), e, ManifoldCFException.INTERRUPTED); } catch (InterruptedIOException e) { t.interrupt(); throw new ManifoldCFException("Interrupted: " + e.getMessage(), e, ManifoldCFException.INTERRUPTED); } catch (IOException e) { handleIOException(e); } } } } } finally { for (String documentIdentifier : documentIdentifiers) { if (documentCache.containsKey(documentIdentifier)) { documentCache.remove(documentIdentifier); } } } } @Override public void outputConfigurationHeader(IThreadContext threadContext, IHTTPOutput out, Locale locale, ConfigParams parameters, List<String> tabsArray) throws ManifoldCFException, IOException { tabsArray.add(Messages.getString(locale, "generic.EntryPoint")); out.print( "<script type=\"text/javascript\">\n" + "<!--\n" + "function checkConfig() {\n" + " return true;\n" + "}\n" + "\n" + "function checkConfigForSave() {\n" + " return true;\n" + "}\n" + "//-->\n" + "</script>\n"); } @Override public void outputConfigurationBody(IThreadContext threadContext, IHTTPOutput out, Locale locale, ConfigParams parameters, String tabName) throws ManifoldCFException, IOException { String server = getParam(parameters, "genericEntryPoint", ""); String login = getParam(parameters, "genericLogin", ""); String password = ""; try { password = out.mapPasswordToKey(ManifoldCF.deobfuscate(getParam(parameters, "genericPassword", ""))); } catch (ManifoldCFException ignore) { } String conTimeout = getParam(parameters, "genericConnectionTimeout", "60000"); String soTimeout = getParam(parameters, "genericSocketTimeout", "1800000"); if (tabName.equals(Messages.getString(locale, "generic.EntryPoint"))) { 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, "generic.EntryPointColon") + "</nobr></td>\n" + " <td class=\"value\"><input type=\"text\" size=\"32\" name=\"genericEntryPoint\" value=\"" + Encoder.attributeEscape(server) + "\"/></td>\n" + " </tr>\n" + " <tr>\n" + " <td class=\"description\"><nobr>" + Messages.getBodyString(locale, "generic.LoginColon") + "</nobr></td>\n" + " <td class=\"value\"><input type=\"text\" size=\"32\" name=\"genericLogin\" value=\"" + Encoder.attributeEscape(login) + "\"/></td>\n" + " </tr>\n" + " <tr>\n" + " <td class=\"description\"><nobr>" + Messages.getBodyString(locale, "generic.PasswordColon") + "</nobr></td>\n" + " <td class=\"value\"><input type=\"password\" size=\"32\" name=\"genericPassword\" value=\"" + Encoder.attributeEscape(password) + "\"/></td>\n" + " </tr>\n" + " <tr>\n" + " <td class=\"description\"><nobr>" + Messages.getBodyString(locale, "generic.ConnectionTimeoutColon") + "</nobr></td>\n" + " <td class=\"value\"><input type=\"text\" size=\"32\" name=\"genericConTimeout\" value=\"" + Encoder.attributeEscape(conTimeout) + "\"/></td>\n" + " </tr>\n" + " <tr>\n" + " <td class=\"description\"><nobr>" + Messages.getBodyString(locale, "generic.SocketTimeoutColon") + "</nobr></td>\n" + " <td class=\"value\"><input type=\"text\" size=\"32\" name=\"genericSoTimeout\" value=\"" + Encoder.attributeEscape(soTimeout) + "\"/></td>\n" + " </tr>\n" + "</table>\n"); } else { out.print("<input type=\"hidden\" name=\"genericEntryPoint\" value=\"" + Encoder.attributeEscape(server) + "\"/>\n"); out.print("<input type=\"hidden\" name=\"genericLogin\" value=\"" + Encoder.attributeEscape(login) + "\"/>\n"); out.print("<input type=\"hidden\" name=\"genericPassword\" value=\"" + Encoder.attributeEscape(password) + "\"/>\n"); out.print("<input type=\"hidden\" name=\"genericConTimeout\" value=\"" + Encoder.attributeEscape(conTimeout) + "\"/>\n"); out.print("<input type=\"hidden\" name=\"genericSoTimeout\" value=\"" + Encoder.attributeEscape(soTimeout) + "\"/>\n"); } } @Override public String processConfigurationPost(IThreadContext threadContext, IPostParameters variableContext, Locale locale, ConfigParams parameters) throws ManifoldCFException { copyParam(variableContext, parameters, "genericLogin"); copyParam(variableContext, parameters, "genericEntryPoint"); copyParam(variableContext, parameters, "genericConTimeout"); copyParam(variableContext, parameters, "genericSoTimeout"); String password = variableContext.getParameter("genericPassword"); if (password == null) { password = ""; } parameters.setParameter("genericPassword", ManifoldCF.obfuscate(variableContext.mapKeyToPassword(password))); return null; } @Override public void viewConfiguration(IThreadContext threadContext, IHTTPOutput out, Locale locale, ConfigParams parameters) throws ManifoldCFException, IOException { String login = getParam(parameters, "genericLogin", ""); String server = getParam(parameters, "genericEntryPoint", ""); String conTimeout = getParam(parameters, "genericConnectionTimeout", "60000"); String soTimeout = getParam(parameters, "genericSocketTimeout", "1800000"); 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, "generic.EntryPointColon") + "</nobr></td>\n" + " <td class=\"value\">" + Encoder.bodyEscape(server) + "</td>\n" + " </tr>\n" + " <tr>\n" + " <td class=\"description\"><nobr>" + Messages.getBodyString(locale, "generic.LoginColon") + "</nobr></td>\n" + " <td class=\"value\">" + Encoder.bodyEscape(login) + "</td>\n" + " </tr>\n" + " <tr>\n" + " <td class=\"description\"><nobr>" + Messages.getBodyString(locale, "generic.PasswordColon") + "</nobr></td>\n" + " <td class=\"value\">**********</td>\n" + " </tr>\n" + " <tr>\n" + " <td class=\"description\"><nobr>" + Messages.getBodyString(locale, "generic.ConnectionTimeoutColon") + "</nobr></td>\n" + " <td class=\"value\">" + Encoder.bodyEscape(conTimeout) + "</td>\n" + " </tr>\n" + " <tr>\n" + " <td class=\"description\"><nobr>" + Messages.getBodyString(locale, "generic.SocketTimeoutColon") + "</nobr></td>\n" + " <td class=\"value\">" + Encoder.bodyEscape(soTimeout) + "</td>\n" + " </tr>\n" + "</table>\n"); } @Override public void outputSpecificationHeader(IHTTPOutput out, Locale locale, Specification ds, int connectionSequenceNumber, List<String> tabsArray) throws ManifoldCFException, IOException { tabsArray.add(Messages.getString(locale, "generic.Parameters")); tabsArray.add(Messages.getString(locale, "generic.Security")); String seqPrefix = "s"+connectionSequenceNumber+"_"; out.print( "<script type=\"text/javascript\">\n" + "<!--\n" + "function "+seqPrefix+"SpecOp(n, opValue, anchorvalue) {\n" + " eval(\"editjob.\"+n+\".value = \\\"\"+opValue+\"\\\"\");\n" + " postFormSetAnchor(anchorvalue);\n" + "}\n" + "\n" + "function "+seqPrefix+"SpecAddToken(anchorvalue) {\n" + " if (editjob."+seqPrefix+"spectoken.value == \"\")\n" + " {\n" + " alert(\"" + Messages.getBodyJavascriptString(locale, "generic.TypeInAnAccessToken") + "\");\n" + " editjob."+seqPrefix+"spectoken.focus();\n" + " return;\n" + " }\n" + " "+seqPrefix+"SpecOp(\""+seqPrefix+"accessop\",\"Add\",anchorvalue);\n" + "}\n" + "function "+seqPrefix+"SpecAddParam(anchorvalue) {\n" + " if (editjob."+seqPrefix+"specparamname.value == \"\")\n" + " {\n" + " alert(\"" + Messages.getBodyJavascriptString(locale, "generic.TypeInParamName") + "\");\n" + " editjob."+seqPrefix+"specparamname.focus();\n" + " return;\n" + " }\n" + " "+seqPrefix+"SpecOp(\""+seqPrefix+"paramop\",\"Add\",anchorvalue);\n" + "}\n" + "//-->\n" + "</script>\n"); } @Override public void outputSpecificationBody(IHTTPOutput out, Locale locale, Specification ds, int connectionSequenceNumber, int actualSequenceNumber, String tabName) throws ManifoldCFException, IOException { String seqPrefix = "s"+connectionSequenceNumber+"_"; int k, i; if (tabName.equals(Messages.getString(locale, "generic.Parameters")) && connectionSequenceNumber == actualSequenceNumber) { out.print("<table class=\"displaytable\">" + "<tr><td class=\"description\"><nobr>" + Messages.getBodyString(locale, "generic.ParametersColon") + "</nobr></td>" + "<td class=\"value\">"); out.print("<table class=\"formtable\">\n" + "<tr class=\"formheaderrow\">" + "<td class=\"formcolumnheader\"></td>" + "<td class=\"formcolumnheader\">" + Messages.getBodyString(locale, "generic.ParameterName") + "</td>" + "<td class=\"formcolumnheader\">" + Messages.getBodyString(locale, "generic.ParameterValue") + "</td>" + "</tr>"); i = 0; k = 0; while (i < ds.getChildCount()) { SpecificationNode sn = ds.getChild(i++); if (sn.getType().equals("param")) { String paramDescription = "_" + Integer.toString(k); String paramOpName = seqPrefix + "paramop" + paramDescription; String paramName = sn.getAttributeValue("name"); String paramValue = sn.getValue(); out.print( " <tr class=\"evenformrow\">\n" + " <td class=\"formcolumncell\">\n" + " <input type=\"hidden\" name=\"" + paramOpName + "\" value=\"\"/>\n" + " <a name=\"" + seqPrefix + "param_" + Integer.toString(k) + "\">\n" + " <input type=\"button\" value=\"" + Messages.getAttributeString(locale, "generic.Delete") + "\" onClick='Javascript:SpecOp(\"" + paramOpName + "\",\"Delete\",\"param" + paramDescription + "\")' alt=\"" + Messages.getAttributeString(locale, "generic.DeleteParameter") + Integer.toString(k) + "\"/>\n" + " </a> \n" + " </td>\n" + " <td class=\"formcolumncell\">\n" + " <input type=\"text\" name=\""+seqPrefix+"specparamname" + paramDescription + "\" value=\"" + Encoder.attributeEscape(paramName) + "\"/>\n" + " </td>\n" + " <td class=\"formcolumncell\">\n" + " <input type=\"text\" name=\""+seqPrefix+"specparamvalue" + paramDescription + "\" value=\"" + Encoder.attributeEscape(paramValue) + "\"/>\n" + " </td>\n" + " </tr>\n"); k++; } } if (k == 0) { out.print( " <tr>\n" + " <td class=\"message\" colspan=\"3\">" + Messages.getBodyString(locale, "generic.NoParametersSpecified") + "</td>\n" + " </tr>\n"); } out.print( " <tr><td class=\"lightseparator\" colspan=\"3\"><hr/></td></tr>\n" + " <tr class=\"evenformrow\">\n" + " <td class=\"formcolumncell\">\n" + " <input type=\"hidden\" name=\""+seqPrefix+"paramcount\" value=\"" + Integer.toString(k) + "\"/>\n" + " <input type=\"hidden\" name=\""+seqPrefix+"paramop\" value=\"\"/>\n" + " <a name=\""+seqPrefix+"param_" + Integer.toString(k) + "\">\n" + " <input type=\"button\" value=\"" + Messages.getAttributeString(locale, "generic.Add") + "\" onClick='Javascript:"+seqPrefix+"SpecAddParam(\""+seqPrefix+"param_" + Integer.toString(k + 1) + "\")' alt=\"" + Messages.getAttributeString(locale, "generic.AddParameter") + "\"/>\n" + " </a> \n" + " </td>\n" + " <td class=\"formcolumncell\">\n" + " <input type=\"text\" size=\"30\" name=\""+seqPrefix+"specparamname\" value=\"\"/>\n" + " </td>\n" + " <td class=\"formcolumncell\">\n" + " <input type=\"text\" size=\"30\" name=\""+seqPrefix+"specparamvalue\" value=\"\"/>\n" + " </td>\n" + " </tr>\n" + "</table>\n"); out.print("</td></tr></table>"); } else { i = 0; k = 0; while (i < ds.getChildCount()) { SpecificationNode sn = ds.getChild(i++); if (sn.getType().equals("param")) { String accessDescription = "_" + Integer.toString(k); String paramName = sn.getAttributeValue("name"); String paramValue = sn.getValue(); out.print( "<input type=\"hidden\" name=\"" + seqPrefix + "specparamname" + accessDescription + "\" value=\"" + Encoder.attributeEscape(paramName) + "\"/>\n" + "<input type=\"hidden\" name=\"" + seqPrefix + "specparamvalue" + accessDescription + "\" value=\"" + Encoder.attributeEscape(paramValue) + "\"/>\n"); k++; } } out.print("<input type=\"hidden\" name=\""+seqPrefix+"paramcount\" value=\"" + Integer.toString(k) + "\"/>\n"); } // Security tab String genericAuthMode = "provided"; for (i = 0; i < ds.getChildCount(); i++) { SpecificationNode sn = ds.getChild(i); if (sn.getType().equals("genericAuthMode")) { genericAuthMode = sn.getValue(); } } if (tabName.equals(Messages.getString(locale, "generic.Security")) && connectionSequenceNumber == actualSequenceNumber) { out.print( "<table class=\"displaytable\">\n" + " <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n"); out.print(" <tr>\n" + " <td class=\"description\">" + Messages.getBodyString(locale, "generic.AuthMode") + "</td>\n" + " <td class=\"value\" >\n" + " <input type=\"radio\" name=\""+seqPrefix+"genericAuthMode\" value=\"provided\" " + ("provided".equals(genericAuthMode) ? "checked=\"checked\"" : "") + "/>" + Messages.getBodyString(locale, "generic.AuthModeProvided") + "<br/>\n" + " <input type=\"radio\" name=\""+seqPrefix+"genericAuthMode\" value=\"forced\" " + ("forced".equals(genericAuthMode) ? "checked=\"checked\"" : "") + "/>" + Messages.getBodyString(locale, "generic.AuthModeForced") + "<br/>\n" + " </td>\n" + " </tr>\n"); // Go through forced ACL out.print("<tr><td class=\"description\"><nobr>" + Messages.getBodyString(locale, "generic.TokensColon") + "</nobr></td>" + "<td class=\"value\">"); out.print("<table class=\"formtable\">\n" + "<tr class=\"formheaderrow\">" + "<td class=\"formcolumnheader\"></td>" + "<td class=\"formcolumnheader\">" + Messages.getBodyString(locale, "generic.Token") + "</td>" + "</tr>"); 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 = seqPrefix + "accessop" + accessDescription; String token = sn.getAttributeValue("token"); out.print( " <tr class=\"evenformrow\">\n" + " <td class=\"formcolumncell\">\n" + " <input type=\"hidden\" name=\"" + accessOpName + "\" value=\"\"/>\n" + " <input type=\"hidden\" name=\"" + seqPrefix + "spectoken" + accessDescription + "\" value=\"" + Encoder.attributeEscape(token) + "\"/>\n" + " <a name=\"" + seqPrefix + "token_" + Integer.toString(k) + "\">\n" + " <input type=\"button\" value=\"" + Messages.getAttributeString(locale, "generic.Delete") + "\" onClick='Javascript:"+seqPrefix+"SpecOp(\"" + accessOpName + "\",\"Delete\",\""+seqPrefix+"token_" + Integer.toString(k) + "\")' alt=\"" + Messages.getAttributeString(locale, "generic.DeleteToken") + Integer.toString(k) + "\"/>\n" + " </a> \n" + " </td>\n" + " <td class=\"formcolumncell\">\n" + " " + Encoder.bodyEscape(token) + "\n" + " </td>\n" + " </tr>\n"); k++; } } if (k == 0) { out.print( " <tr>\n" + " <td class=\"message\" colspan=\"2\">" + Messages.getBodyString(locale, "generic.NoAccessTokensSpecified") + "</td>\n" + " </tr>\n"); } out.print( " <tr><td class=\"lightseparator\" colspan=\"2\"><hr/></td></tr>\n" + " <tr class=\"evenformrow\">\n" + " <td class=\"formcolumncell\">\n" + " <input type=\"hidden\" name=\""+seqPrefix+"tokencount\" value=\"" + Integer.toString(k) + "\"/>\n" + " <input type=\"hidden\" name=\""+seqPrefix+"accessop\" value=\"\"/>\n" + " <a name=\"" + seqPrefix + "token_" + Integer.toString(k) + "\">\n" + " <input type=\"button\" value=\"" + Messages.getAttributeString(locale, "generic.Add") + "\" onClick='Javascript:"+seqPrefix+"SpecAddToken(\""+seqPrefix+"token_" + Integer.toString(k + 1) + "\")' alt=\"" + Messages.getAttributeString(locale, "generic.AddAccessToken") + "\"/>\n" + " </a> \n" + " </td>\n" + " <td class=\"formcolumncell\">\n" + " <input type=\"text\" size=\"30\" name=\""+seqPrefix+"spectoken\" value=\"\"/>\n" + " </td>\n" + " </tr>\n" + "</table>\n"); out.print("</td></tr></table>"); } else { // 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=\"" + seqPrefix + "spectoken" + accessDescription + "\" value=\"" + Encoder.attributeEscape(token) + "\"/>\n"); k++; } } out.print("<input type=\"hidden\" name=\""+seqPrefix+"tokencount\" value=\"" + Integer.toString(k) + "\"/>\n"); out.print("<input type=\"hidden\" name=\""+seqPrefix+"genericAuthMode\" value=\"" + Encoder.attributeEscape(genericAuthMode) + "\"/>\n"); } } @Override public String processSpecificationPost(IPostParameters variableContext, Locale locale, Specification ds, int connectionSequenceNumber) throws ManifoldCFException { String seqPrefix = "s"+connectionSequenceNumber+"_"; String xc = variableContext.getParameter(seqPrefix+"paramcount"); if (xc != null) { // Delete all tokens first int i = 0; while (i < ds.getChildCount()) { SpecificationNode sn = ds.getChild(i); if (sn.getType().equals("param")) { ds.removeChild(i); } else { i++; } } int accessCount = Integer.parseInt(xc); i = 0; while (i < accessCount) { String paramDescription = "_" + Integer.toString(i); String paramOpName = seqPrefix + "paramop" + paramDescription; xc = variableContext.getParameter(paramOpName); if (xc != null && xc.equals("Delete")) { // Next row i++; continue; } // Get the stuff we need String paramName = variableContext.getParameter(seqPrefix + "specparamname" + paramDescription); String paramValue = variableContext.getParameter(seqPrefix + "specparamvalue" + paramDescription); SpecificationNode node = new SpecificationNode("param"); node.setAttribute("name", paramName); node.setValue(paramValue); ds.addChild(ds.getChildCount(), node); i++; } String op = variableContext.getParameter(seqPrefix+"paramop"); if (op != null && op.equals("Add")) { String paramName = variableContext.getParameter(seqPrefix+"specparamname"); String paramValue = variableContext.getParameter(seqPrefix+"specparamvalue"); SpecificationNode node = new SpecificationNode("param"); node.setAttribute("name", paramName); node.setValue(paramValue); ds.addChild(ds.getChildCount(), node); } } String redmineAuthMode = variableContext.getParameter(seqPrefix+"genericAuthMode"); if (redmineAuthMode != null) { // Delete existing seeds record first int i = 0; while (i < ds.getChildCount()) { SpecificationNode sn = ds.getChild(i); if (sn.getType().equals("genericAuthMode")) { ds.removeChild(i); } else { i++; } } SpecificationNode cn = new SpecificationNode("genericAuthMode"); cn.setValue(redmineAuthMode); ds.addChild(ds.getChildCount(), cn); } xc = variableContext.getParameter(seqPrefix+"tokencount"); if (xc != null) { // Delete all tokens 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 = seqPrefix + "accessop" + accessDescription; xc = variableContext.getParameter(accessOpName); if (xc != null && xc.equals("Delete")) { // Next row i++; continue; } // Get the stuff we need String accessSpec = variableContext.getParameter(seqPrefix + "spectoken" + accessDescription); SpecificationNode node = new SpecificationNode("access"); node.setAttribute("token", accessSpec); ds.addChild(ds.getChildCount(), node); i++; } String op = variableContext.getParameter(seqPrefix+"accessop"); if (op != null && op.equals("Add")) { String accessspec = variableContext.getParameter(seqPrefix+"spectoken"); SpecificationNode node = new SpecificationNode("access"); node.setAttribute("token", accessspec); ds.addChild(ds.getChildCount(), node); } } return null; } @Override public void viewSpecification(IHTTPOutput out, Locale locale, Specification ds, int connectionSequenceNumber) throws ManifoldCFException, IOException { boolean seenAny; int i; i = 0; seenAny = false; while (i < ds.getChildCount()) { SpecificationNode sn = ds.getChild(i++); if (sn.getType().equals("param")) { if (seenAny == false) { out.print( " <tr>\n" + " <td class=\"description\"><nobr>" + Messages.getBodyString(locale, "generic.Parameters") + "</nobr></td>\n" + " <td class=\"value\">\n"); seenAny = true; } String paramName = sn.getAttributeValue("name"); String paramValue = sn.getValue(); out.print(Encoder.bodyEscape(paramName) + " = " + Encoder.bodyEscape(paramValue) + "<br/>\n"); } } if (seenAny) { out.print( " </td>\n" + " </tr>\n"); } else { out.print( " <tr><td class=\"message\" colspan=\"4\"><nobr>" + Messages.getBodyString(locale, "generic.NoParametersSpecified") + "</nobr></td></tr>\n"); } out.print( " <tr><td class=\"separator\" colspan=\"4\"><hr/></td></tr>\n"); // Go through looking for access tokens i = 0; seenAny = false; while (i < ds.getChildCount()) { SpecificationNode sn = ds.getChild(i++); if (sn.getType().equals("access")) { if (seenAny == false) { out.print( " <tr>\n" + " <td class=\"description\"><nobr>" + Messages.getBodyString(locale, "generic.AccessTokens") + "</nobr></td>\n" + " <td class=\"value\">\n"); seenAny = true; } String token = sn.getAttributeValue("token"); out.print(Encoder.bodyEscape(token) + "<br/>\n"); } } if (seenAny) { out.print( " </td>\n" + " </tr>\n"); } else { out.print( " <tr><td class=\"message\" colspan=\"4\"><nobr>" + Messages.getBodyString(locale, "generic.NoAccessTokensSpecified") + "</nobr></td></tr>\n"); } out.print( " <tr><td class=\"separator\" colspan=\"4\"><hr/></td></tr>\n"); } private String getParam(ConfigParams parameters, String name, String def) { return parameters.getParameter(name) != null ? parameters.getParameter(name) : def; } private boolean copyParam(IPostParameters variableContext, ConfigParams parameters, String name) { String val = variableContext.getParameter(name); if (val == null) { return false; } parameters.setParameter(name, val); return true; } protected static String[] getAcls(Specification spec) { HashMap map = new HashMap(); int i = 0; while (i < spec.getChildCount()) { SpecificationNode sn = spec.getChild(i++); if (sn.getType().equals("access")) { String token = sn.getAttributeValue("token"); map.put(token, token); } } String[] rval = new String[map.size()]; Iterator iter = map.keySet().iterator(); i = 0; while (iter.hasNext()) { rval[i++] = (String) iter.next(); } return rval; } protected static void handleIOException(IOException e) throws ManifoldCFException, ServiceInterruption { if (!(e instanceof java.net.SocketTimeoutException) && (e instanceof InterruptedIOException)) { throw new ManifoldCFException("Interrupted: " + e.getMessage(), e, ManifoldCFException.INTERRUPTED); } long currentTime = System.currentTimeMillis(); throw new ServiceInterruption("IO exception: " + e.getMessage(), e, currentTime + 300000L, currentTime + 3 * 60 * 60000L, -1, false); } static class PreemptiveAuth implements HttpRequestInterceptor { private Credentials credentials; public PreemptiveAuth(Credentials creds) { this.credentials = creds; } @Override public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException { request.addHeader(new BasicScheme(StandardCharsets.US_ASCII).authenticate(credentials, request, context)); } } protected static class CheckThread extends Thread { protected HttpClient client; protected String url; protected Throwable exception = null; protected String result = "Unknown"; public CheckThread(HttpClient client, String url) { super(); setDaemon(true); this.client = client; this.url = url; } @Override public void run() { HttpGet method = new HttpGet(url); try { HttpResponse response = client.execute(method); try { if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { result = "Connection failed: " + response.getStatusLine().getReasonPhrase(); return; } EntityUtils.consume(response.getEntity()); result = "Connection OK"; } finally { EntityUtils.consume(response.getEntity()); method.releaseConnection(); } } catch (IOException ex) { exception = ex; } } public Throwable getException() { return exception; } public String getResult() { return result; } } protected static class ExecuteSeedingThread extends Thread { protected final HttpClient client; protected final String url; protected final XThreadStringBuffer seedBuffer; protected Throwable exception = null; public ExecuteSeedingThread(HttpClient client, String url) { super(); setDaemon(true); this.client = client; this.url = url; seedBuffer = new XThreadStringBuffer(); } public XThreadStringBuffer getBuffer() { return seedBuffer; } public void finishUp() throws InterruptedException { seedBuffer.abandon(); join(); Throwable thr = exception; if (thr != null) { 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); } } } @Override public void run() { HttpGet method = new HttpGet(url.toString()); try { HttpResponse response = client.execute(method); try { if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { exception = new ManifoldCFException("addSeedDocuments error - interface returned incorrect return code for: " + url + " - " + response.getStatusLine().toString()); return; } try { SAXParserFactory factory = SAXParserFactory.newInstance(); factory.setNamespaceAware(true); SAXParser parser = factory.newSAXParser(); DefaultHandler handler = new SAXSeedingHandler(seedBuffer); parser.parse(response.getEntity().getContent(), handler); } catch (FactoryConfigurationError ex) { exception = new ManifoldCFException("addSeedDocuments error: " + ex.getMessage(), ex); } catch (ParserConfigurationException ex) { exception = new ManifoldCFException("addSeedDocuments error: " + ex.getMessage(), ex); } catch (SAXException ex) { exception = new ManifoldCFException("addSeedDocuments error: " + ex.getMessage(), ex); } seedBuffer.signalDone(); } finally { EntityUtils.consume(response.getEntity()); method.releaseConnection(); } } catch (IOException ex) { exception = ex; } } public Throwable getException() { return exception; } } protected static class DocumentVersionThread extends Thread { protected final HttpClient client; protected final String url; protected Throwable exception = null; protected final String[] versions; protected final ConcurrentHashMap<String, Item> documentCache; protected final String[] documentIdentifiers; protected final String genericAuthMode; protected final String defaultRights; public DocumentVersionThread(HttpClient client, String url, String[] documentIdentifiers, String genericAuthMode, String defaultRights, ConcurrentHashMap<String, Item> documentCache) { super(); setDaemon(true); this.client = client; this.url = url; this.documentCache = documentCache; this.documentIdentifiers = documentIdentifiers; this.genericAuthMode = genericAuthMode; this.defaultRights = defaultRights; this.versions = new String[documentIdentifiers.length]; for (int i = 0; i < versions.length; i++) { versions[i] = null; } } @Override public void run() { try { HttpGet method = new HttpGet(url.toString()); HttpResponse response = client.execute(method); try { if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { exception = new ManifoldCFException("addSeedDocuments error - interface returned incorrect return code for: " + url + " - " + response.getStatusLine().toString()); return; } JAXBContext context; context = JAXBContext.newInstance(Items.class); Unmarshaller m = context.createUnmarshaller(); Items items = (Items) m.unmarshal(response.getEntity().getContent()); if (items.items != null) { for (Item item : items.items) { documentCache.put(item.id, item); for (int i = 0; i < versions.length; i++) { if (documentIdentifiers[i].equals(item.id)) { if ("provided".equals(genericAuthMode)) { versions[i] = item.getVersionString(); } else { versions[i] = item.version + defaultRights; } break; } } } } } catch (JAXBException ex) { exception = ex; } finally { EntityUtils.consume(response.getEntity()); method.releaseConnection(); } } catch (Exception ex) { exception = ex; } } public String[] finishUp() throws ManifoldCFException, ServiceInterruption, IOException, InterruptedException { join(); Throwable thr = exception; if (thr != null) { if (thr instanceof ManifoldCFException) { throw (ManifoldCFException) thr; } else if (thr instanceof ServiceInterruption) { throw (ServiceInterruption) thr; } else if (thr instanceof IOException) { throw (IOException) thr; } else if (thr instanceof RuntimeException) { throw (RuntimeException) thr; } else if (thr instanceof Error) { throw (Error) thr; } throw new ManifoldCFException("getDocumentVersions error: " + thr.getMessage(), thr); } return versions; } } protected static class ExecuteProcessThread extends Thread { protected final HttpClient client; protected final String url; protected Throwable exception = null; protected XThreadInputStream threadStream; protected boolean abortThread = false; protected long streamLength = 0; public ExecuteProcessThread(HttpClient client, String url) { super(); setDaemon(true); this.client = client; this.url = url; } @Override public void run() { try { HttpGet method = new HttpGet(url); HttpResponse response = client.execute(method); try { if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) { exception = new ManifoldCFException("processDocuments error - interface returned incorrect return code for: " + url + " - " + response.getStatusLine().toString()); return; } synchronized (this) { if (!abortThread) { streamLength = response.getEntity().getContentLength(); threadStream = new XThreadInputStream(response.getEntity().getContent()); this.notifyAll(); } } if (threadStream != null) { // Stuff the content until we are done threadStream.stuffQueue(); } } catch (Throwable ex) { exception = ex; } finally { EntityUtils.consume(response.getEntity()); method.releaseConnection(); } } catch (Throwable e) { exception = e; } } public InputStream getSafeInputStream() throws InterruptedException, IOException, ManifoldCFException { while (true) { synchronized (this) { if (exception != null) { throw new IllegalStateException("Check for response before getting stream"); } checkException(exception); if (threadStream != null) { return threadStream; } wait(); } } } public long getStreamLength() throws IOException, InterruptedException, ManifoldCFException { while (true) { synchronized (this) { if (exception != null) { throw new IllegalStateException("Check for response before getting stream"); } checkException(exception); if (threadStream != null) { return streamLength; } wait(); } } } protected synchronized void checkException(Throwable exception) throws IOException, ManifoldCFException { if (exception != null) { Throwable e = exception; if (e instanceof IOException) { throw (IOException) e; } else if (e instanceof ManifoldCFException) { throw (ManifoldCFException) 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); } } } public void finishUp() throws InterruptedException, IOException, 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. synchronized (this) { if (threadStream != null) { threadStream.abort(); } abortThread = true; } join(); checkException(exception); } public Throwable getException() { return exception; } } static public class SAXSeedingHandler extends DefaultHandler { protected XThreadStringBuffer seedBuffer; public SAXSeedingHandler(XThreadStringBuffer seedBuffer) { this.seedBuffer = seedBuffer; } @Override public void startElement(String uri, String localName, String qName, Attributes attributes) throws SAXException { if ("seed".equals(localName) && attributes.getValue("id") != null) { try { seedBuffer.add(attributes.getValue("id")); } catch (InterruptedException ex) { throw new SAXException("Adding seed failed: " + ex.getMessage(), ex); } } } } }