blob: 161e1c49fcffaa35ff44277ec8de3ff4c413e636 [file] [log] [blame]
/* $Id$ */
/**
* 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.wiki;
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.xml.sax.Attributes;
import org.apache.manifoldcf.core.common.XMLDoc;
import org.apache.manifoldcf.agents.common.XMLStream;
import org.apache.manifoldcf.agents.common.XMLContext;
import org.apache.manifoldcf.agents.common.XMLStringContext;
import org.apache.manifoldcf.agents.common.XMLFileContext;
import org.apache.commons.httpclient.*;
import org.apache.commons.httpclient.methods.*;
import org.apache.commons.httpclient.params.*;
import org.apache.commons.httpclient.auth.*;
import org.apache.commons.httpclient.protocol.*;
import java.util.*;
import java.io.*;
import java.net.*;
import java.util.logging.Level;
import java.util.logging.Logger;
/** This is the repository connector for a wiki.
*/
public class WikiConnector extends org.apache.manifoldcf.crawler.connectors.BaseRepositoryConnector
{
public static final String _rcsid = "@(#)$Id$";
/**
* Deny access token for default authority
*/
private final static String defaultAuthorityDenyToken = "DEAD_AUTHORITY";
// Activities that we know about
/** Fetch activity */
protected final static String ACTIVITY_FETCH = "fetch document";
/** Activities list */
protected static final String[] activitiesList = new String[]{ACTIVITY_FETCH};
/** Has setup been called? */
protected boolean hasBeenSetup = false;
/** Server name */
protected String server = null;
/** Base URL */
protected String baseURL = null;
protected String serverLogin = null;
protected String serverPass = null;
protected String serverDomain = null;
/** Connection management */
protected MultiThreadedHttpConnectionManager connectionManager = null;
protected HttpClient httpClient = null;
/** Constructor.
*/
public WikiConnector()
{
}
/** List the activities we might report on.
*/
@Override
public String[] getActivitiesList()
{
return activitiesList;
}
/** 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[]{server};
}
/** Connect.
*@param configParameters is the set of configuration parameters, which
* in this case describe the target appliance, basic auth configuration, etc.
*/
@Override
public void connect(ConfigParams configParameters)
{
super.connect(configParameters);
server = params.getParameter(WikiConfig.PARAM_SERVER);
serverLogin = params.getParameter(WikiConfig.PARAM_LOGIN);
serverPass = params.getObfuscatedParameter(WikiConfig.PARAM_PASSWORD);
serverDomain = params.getParameter(WikiConfig.PARAM_DOMAIN);
}
protected void getSession()
throws ManifoldCFException, ServiceInterruption
{
if (hasBeenSetup == false)
{
String protocol = params.getParameter(WikiConfig.PARAM_PROTOCOL);
if (protocol == null || protocol.length() == 0)
protocol = "http";
String portString = params.getParameter(WikiConfig.PARAM_PORT);
if (portString == null || portString.length() == 0)
portString = null;
String path = params.getParameter(WikiConfig.PARAM_PATH);
if (path == null)
path = "/w";
baseURL = protocol + "://" + server + ((portString!=null)?":" + portString:"") + path + "/api.php?format=xml&";
// Set up connection manager
connectionManager = new MultiThreadedHttpConnectionManager();
connectionManager.getParams().setMaxTotalConnections(1);
httpClient = new HttpClient(connectionManager);
loginToAPI();
hasBeenSetup = true;
}
}
/** Log in via the Wiki API.
* Call this method whenever login is apparently needed.
*@return true if the login was successful, false otherwise.
*/
protected boolean loginToAPI()
throws ManifoldCFException, ServiceInterruption
{
if (serverLogin == null || serverLogin.length() == 0)
return false;
// Grab the httpclient, and use the same one throughout.
HttpClient client = getInitializedClient();
// First step in login process: get the token
Map<String, String> loginParams = new HashMap<String, String>();
String token = null;
String loginURL = baseURL + "action=login";
loginParams.put("action", "login");
loginParams.put("lgname", serverLogin);
loginParams.put("lgpassword", serverPass);
if (serverDomain != null && !"".equals(serverDomain)) {
loginParams.put("lgdomain", serverDomain);
}
APILoginResult result = new APILoginResult();
HttpMethodBase method = getInitializedPostMethod(loginURL,loginParams);
try {
ExecuteAPILoginThread t = new ExecuteAPILoginThread(client, method, result);
try {
t.start();
t.join();
Throwable thr = t.getException();
if (thr != null) {
if (thr instanceof ManifoldCFException) {
if (((ManifoldCFException) thr).getErrorCode() == ManifoldCFException.INTERRUPTED) {
throw new InterruptedException(thr.getMessage());
}
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 {
throw (Error) thr;
}
}
} catch (ManifoldCFException e) {
t.interrupt();
throw e;
} catch (ServiceInterruption e) {
t.interrupt();
throw e;
} catch (IOException e) {
t.interrupt();
throw e;
} catch (InterruptedException e) {
t.interrupt();
// We need the caller to abandon any connections left around, so rethrow in a way that forces them to process the event properly.
throw e;
}
if (result.result)
return true;
// Grab the token from the first call
token = t.getToken();
if (token == null)
{
// We don't need a token, we just couldn't log in
Logging.connectors.debug("WIKI API login error: '" + result.reason + "'");
throw new ManifoldCFException("WIKI API login error: " + result.reason, null, ManifoldCFException.REPOSITORY_CONNECTION_ERROR);
}
} catch (InterruptedException e) {
// Drop the connection on the floor
method = null;
throw new ManifoldCFException("Interrupted: " + e.getMessage(), e, ManifoldCFException.INTERRUPTED);
} catch (ManifoldCFException e) {
if (e.getErrorCode() == ManifoldCFException.INTERRUPTED) // Drop the connection on the floor
{
method = null;
}
throw e;
} catch (java.net.SocketTimeoutException e) {
long currentTime = System.currentTimeMillis();
throw new ServiceInterruption("Login timed out reading from the Wiki server: " + e.getMessage(), e, currentTime + 300000L, currentTime + 12L * 60000L, -1, false);
} catch (java.net.SocketException e) {
long currentTime = System.currentTimeMillis();
throw new ServiceInterruption("Login received a socket error reading from Wiki server: " + e.getMessage(), e, currentTime + 300000L, currentTime + 12L * 60000L, -1, false);
} catch (org.apache.commons.httpclient.ConnectTimeoutException e) {
long currentTime = System.currentTimeMillis();
throw new ServiceInterruption("Login connection timed out reading from Wiki server: " + e.getMessage(), e, currentTime + 300000L, currentTime + 12L * 60000L, -1, false);
} catch (InterruptedIOException e) {
method = null;
throw new ManifoldCFException("Interrupted: " + e.getMessage(), e, ManifoldCFException.INTERRUPTED);
} catch (IOException e) {
throw new ManifoldCFException("Login had an IO failure: " + e.getMessage(), e);
} finally {
if (method != null) {
method.releaseConnection();
}
}
// First request is finished. Fire off the second one.
loginParams.put("lgtoken", token);
method = getInitializedPostMethod(loginURL,loginParams);
try {
ExecuteTokenAPILoginThread t = new ExecuteTokenAPILoginThread(client, method, result);
try {
t.start();
t.join();
Throwable thr = t.getException();
if (thr != null) {
if (thr instanceof ManifoldCFException) {
if (((ManifoldCFException) thr).getErrorCode() == ManifoldCFException.INTERRUPTED) {
throw new InterruptedException(thr.getMessage());
}
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 {
throw (Error) thr;
}
}
} catch (ManifoldCFException e) {
t.interrupt();
throw e;
} catch (ServiceInterruption e) {
t.interrupt();
throw e;
} catch (IOException e) {
t.interrupt();
throw e;
} catch (InterruptedException e) {
t.interrupt();
// We need the caller to abandon any connections left around, so rethrow in a way that forces them to process the event properly.
throw e;
}
// Fall through
} catch (InterruptedException e) {
// Drop the connection on the floor
method = null;
throw new ManifoldCFException("Interrupted: " + e.getMessage(), e, ManifoldCFException.INTERRUPTED);
} catch (ManifoldCFException e) {
if (e.getErrorCode() == ManifoldCFException.INTERRUPTED) // Drop the connection on the floor
{
method = null;
}
throw e;
} catch (java.net.SocketTimeoutException e) {
long currentTime = System.currentTimeMillis();
throw new ServiceInterruption("Login timed out reading from the Wiki server: " + e.getMessage(), e, currentTime + 300000L, currentTime + 12L * 60000L, -1, false);
} catch (java.net.SocketException e) {
long currentTime = System.currentTimeMillis();
throw new ServiceInterruption("Login received a socket error reading from Wiki server: " + e.getMessage(), e, currentTime + 300000L, currentTime + 12L * 60000L, -1, false);
} catch (org.apache.commons.httpclient.ConnectTimeoutException e) {
long currentTime = System.currentTimeMillis();
throw new ServiceInterruption("Login connection timed out reading from Wiki server: " + e.getMessage(), e, currentTime + 300000L, currentTime + 12L * 60000L, -1, false);
} catch (InterruptedIOException e) {
method = null;
throw new ManifoldCFException("Interrupted: " + e.getMessage(), e, ManifoldCFException.INTERRUPTED);
} catch (IOException e) {
throw new ManifoldCFException("Login had an IO failure: " + e.getMessage(), e);
} finally {
if (method != null) {
method.releaseConnection();
}
}
// Check result
if (!result.result)
{
Logging.connectors.debug("WIKI API login error: '" + result.reason + "'");
throw new ManifoldCFException("WIKI API login error: " + result.reason, null, ManifoldCFException.REPOSITORY_CONNECTION_ERROR);
}
return true;
}
/**
* Thread to execute a "login" operation. This thread both executes
* the operation and parses the result.
*/
protected class ExecuteAPILoginThread extends Thread {
protected HttpClient client;
protected HttpMethodBase executeMethod;
protected APILoginResult result;
protected Throwable exception = null;
protected String token = null;
public ExecuteAPILoginThread(HttpClient client, HttpMethodBase executeMethod, APILoginResult result) {
super();
setDaemon(true);
this.client = client;
this.executeMethod = executeMethod;
this.result = result;
}
public void run() {
try {
// Call the execute method appropriately
int rval = client.executeMethod(executeMethod);
if (rval != 200) {
throw new ManifoldCFException("Unexpected response code " + rval + ": " + executeMethod.getResponseBodyAsString());
}
// Read response and make sure it's valid
InputStream is = executeMethod.getResponseBodyAsStream();
try {
// Parse the document. This will cause various things to occur, within the instantiated XMLContext class.
//<api>
// <login
// result="NeedToken"
// token="b5780b6e2f27e20b450921d9461010b4"
// cookieprefix="enwiki"
// sessionid="17ab96bd8ffbe8ca58a78657a918558e"
// />
//</api>
XMLStream x = new XMLStream();
WikiLoginAPIContext c = new WikiLoginAPIContext(x,result);
x.setContext(c);
try {
try {
x.parse(is);
token = c.getToken();
}
catch (IOException e)
{
long time = System.currentTimeMillis();
throw new ServiceInterruption(e.getMessage(),e,time + 300000L,time + 12L * 60000L,-1,false);
}
} finally {
x.cleanup();
}
} finally {
try {
is.close();
} catch (IllegalStateException e) {
// Ignore this error
}
}
} catch (Throwable e) {
this.exception = e;
}
}
public Throwable getException() {
return exception;
}
public String getToken()
{
return token;
}
}
/**
* Class representing the "api" context of a "login" response
*/
protected class WikiLoginAPIContext extends SingleLevelContext {
protected APILoginResult result;
protected String token = null;
public WikiLoginAPIContext(XMLStream theStream, APILoginResult result) {
super(theStream, "api");
this.result = result;
}
protected BaseProcessingContext createChild(String namespaceURI, String localName, String qName, Attributes atts) {
return new WikiLoginAPIResultAPIContext(theStream, namespaceURI, localName, qName, atts, result);
}
protected void finishChild(BaseProcessingContext child)
throws ManifoldCFException {
token = ((WikiLoginAPIResultAPIContext)child).getToken();
}
public String getToken()
{
return token;
}
}
/**
* Class representing the "api/result" context of a "login"
* response
*/
protected class WikiLoginAPIResultAPIContext extends BaseProcessingContext {
protected APILoginResult result;
protected String token = null;
public WikiLoginAPIResultAPIContext(XMLStream theStream, String namespaceURI, String localName, String qName, Attributes atts, APILoginResult result) {
super(theStream, namespaceURI, localName, qName, atts);
this.result = result;
}
protected XMLContext beginTag(String namespaceURI, String localName, String qName, Attributes atts)
throws ManifoldCFException, ServiceInterruption {
if (qName.equals("login")) {
String loginResult = atts.getValue("result");
if ("NeedToken".equals(loginResult)) {
token = atts.getValue("token");
} else if ("Success".equals(loginResult)) {
result.result = true;
} else {
result.reason = loginResult;
}
}
return super.beginTag(namespaceURI, localName, qName, atts);
}
public String getToken()
{
return token;
}
}
protected static class APILoginResult {
public boolean result = false;
public String reason = "";
}
/**
* Thread to finish a "login" operation. This thread both executes
* the operation and parses the result.
*/
protected class ExecuteTokenAPILoginThread extends Thread {
protected HttpClient client;
protected HttpMethodBase executeMethod;
protected Throwable exception = null;
protected APILoginResult result;
public ExecuteTokenAPILoginThread(HttpClient client, HttpMethodBase executeMethod, APILoginResult result) {
super();
setDaemon(true);
this.client = client;
this.executeMethod = executeMethod;
this.result = result;
}
public void run() {
try {
// Call the execute method appropriately
int rval = client.executeMethod(executeMethod);
if (rval != 200) {
throw new ManifoldCFException("Unexpected response code " + rval + ": " + executeMethod.getResponseBodyAsString());
}
// Read response and make sure it's valid
InputStream is = executeMethod.getResponseBodyAsStream();
try {
// Parse the document. This will cause various things to occur, within the instantiated XMLContext class.
//<api>
// <login
// result="NeedToken"
// token="b5780b6e2f27e20b450921d9461010b4"
// cookieprefix="enwiki"
// sessionid="17ab96bd8ffbe8ca58a78657a918558e"
// />
//</api>
XMLStream x = new XMLStream();
WikiTokenLoginAPIContext c = new WikiTokenLoginAPIContext(x,result);
x.setContext(c);
try {
try {
x.parse(is);
}
catch (IOException e)
{
long time = System.currentTimeMillis();
throw new ServiceInterruption(e.getMessage(),e,time + 300000L,time + 12L * 60000L,-1,false);
}
} finally {
x.cleanup();
}
} finally {
try {
is.close();
} catch (IllegalStateException e) {
// Ignore this error
}
}
} catch (Throwable e) {
this.exception = e;
}
}
public Throwable getException() {
return exception;
}
}
/**
* Class representing the "api" context of a "login" response
*/
protected class WikiTokenLoginAPIContext extends SingleLevelContext {
protected APILoginResult result;
public WikiTokenLoginAPIContext(XMLStream theStream, APILoginResult result) {
super(theStream, "api");
this.result = result;
}
protected BaseProcessingContext createChild(String namespaceURI, String localName, String qName, Attributes atts) {
return new WikiTokenLoginAPIResultAPIContext(theStream, namespaceURI, localName, qName, atts, result);
}
protected void finishChild(BaseProcessingContext child)
throws ManifoldCFException {
}
}
/**
* Class representing the "api/result" context of a "login"
* response
*/
protected class WikiTokenLoginAPIResultAPIContext extends BaseProcessingContext {
protected APILoginResult result;
public WikiTokenLoginAPIResultAPIContext(XMLStream theStream, String namespaceURI, String localName, String qName, Attributes atts, APILoginResult result) {
super(theStream, namespaceURI, localName, qName, atts);
this.result = result;
}
protected XMLContext beginTag(String namespaceURI, String localName, String qName, Attributes atts)
throws ManifoldCFException, ServiceInterruption {
if (qName.equals("login")) {
String loginResult = atts.getValue("result");
if ("Success".equals(loginResult)) {
result.result = true;
} else {
result.reason = loginResult;
}
}
return super.beginTag(namespaceURI, localName, qName, atts);
}
}
/** Check status of connection.
*/
@Override
public String check()
throws ManifoldCFException
{
try
{
// Destroy saved session setup and repeat it
hasBeenSetup = false;
performCheck();
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 (connectionManager != null)
connectionManager.closeIdleConnections(60000L);
}
/** Close the connection. Call this before discarding the connection.
*/
@Override
public void disconnect()
throws ManifoldCFException
{
hasBeenSetup = false;
server = null;
serverLogin = null;
serverPass = null;
serverDomain = null;
baseURL = null;
if (httpClient != null) {
httpClient = null;
}
if (connectionManager != null)
{
connectionManager.shutdown();
connectionManager = null;
}
super.disconnect();
}
/** 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()
{
return 20;
}
/** Queue "seed" documents. Seed documents are the starting places for crawling activity. Documents
* are seeded when this method calls appropriate methods in the passed in ISeedingActivity object.
*
* This method can choose to find repository changes that happen only during the specified time interval.
* The seeds recorded by this method will be viewed by the framework based on what the
* getConnectorModel() method returns.
*
* It is not a big problem if the connector chooses to create more seeds than are
* strictly necessary; it is merely a question of overall work required.
*
* The times passed to this method may be interpreted for greatest efficiency. The time ranges
* any given job uses with this connector will not overlap, but will proceed starting at 0 and going
* to the "current time", each time the job is run. For continuous crawling jobs, this method will
* be called once, when the job starts, and at various periodic intervals as the job executes.
*
* When a job's specification is changed, the framework automatically resets the seeding start time to 0. The
* seeding start time may also be set to 0 on each job run, depending on the connector model returned by
* getConnectorModel().
*
* Note that it is always ok to send MORE documents rather than less to this method.
*@param activities is the interface this method should use to perform whatever framework actions are desired.
*@param spec is a document specification (that comes from the job).
*@param startTime is the beginning of the time range to consider, inclusive.
*@param endTime is the end of the time range to consider, exclusive.
*/
@Override
public void addSeedDocuments(ISeedingActivity activities, DocumentSpecification spec,
long startTime, long endTime)
throws ManifoldCFException, ServiceInterruption
{
// Scan specification nodes and extract prefixes and namespaces
boolean seenAny = false;
for (int i = 0 ; i < spec.getChildCount() ; i++)
{
SpecificationNode sn = spec.getChild(i);
if (sn.getType().equals(WikiConfig.NODE_NAMESPACE_TITLE_PREFIX))
{
String namespace = sn.getAttributeValue(WikiConfig.ATTR_NAMESPACE);
String titleprefix = sn.getAttributeValue(WikiConfig.ATTR_TITLEPREFIX);
listAllPages(activities,namespace,titleprefix,startTime,endTime);
seenAny = true;
}
}
if (!seenAny)
listAllPages(activities,null,null,startTime,endTime);
}
/** 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
{
Map<String,String> versions = new HashMap<String,String>();
getTimestamps(documentIdentifiers,versions,activities);
String[] rval = new String[documentIdentifiers.length];
for (int i = 0 ; i < rval.length ; i++)
{
rval[i] = versions.get(documentIdentifiers[i]);
}
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 versions is the corresponding document versions to process, as returned by getDocumentVersions() above.
* The implementation may choose to ignore this parameter and always process the current version.
*@param activities is the interface this method should use to queue up new document references
* and ingest documents.
*@param spec is the document specification.
*@param scanOnly is an array corresponding to the document identifiers. It is set to true to indicate when the processing
* should only find other references, and should not actually call the ingestion methods.
*@param jobMode is an integer describing how the job is being run, whether continuous or once-only.
*/
public void processDocuments(String[] documentIdentifiers, String[] versions, IProcessActivity activities,
DocumentSpecification spec, boolean[] scanOnly, int jobMode)
throws ManifoldCFException, ServiceInterruption
{
// Forced acls
String[] acls = getAcls(spec);
Map<String,String> urls = new HashMap<String,String>();
getDocURLs(documentIdentifiers,urls);
for (int i = 0 ; i < documentIdentifiers.length ; i++)
{
if (!scanOnly[i])
{
String url = urls.get(documentIdentifiers[i]);
if (url != null)
getDocInfo(documentIdentifiers[i], versions[i], url, activities, acls);
}
}
}
/**
* Grab forced acl out of document specification.
*
* @param spec is the document specification.
* @return the acls.
*/
protected static String[] getAcls(DocumentSpecification spec) {
HashMap map = new HashMap();
int i = 0;
while (i < spec.getChildCount()) {
SpecificationNode sn = spec.getChild(i++);
if (sn.getType().equals("access")) {
String token = sn.getAttributeValue("token");
map.put(token, token);
}
}
String[] rval = new String[map.size()];
Iterator iter = map.keySet().iterator();
i = 0;
while (iter.hasNext()) {
rval[i++] = (String) iter.next();
}
return rval;
}
// 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,"WikiConnector.Server"));
out.print(
"<script type=\"text/javascript\">\n"+
"<!--\n"+
"function checkConfig()\n"+
"{\n"+
" if (editconnection.serverport.value != \"\" && !isInteger(editconnection.serverport.value))\n"+
" {\n"+
" alert(\""+Messages.getBodyJavascriptString(locale,"WikiConnector.WikiServerPortMustBeAValidInteger")+"\");\n"+
" editconnection.serverport.focus();\n"+
" return false;\n"+
" }\n"+
" if (editconnection.serverpath.value != \"\" && editconnection.serverpath.value.indexOf(\"/\") != 0)\n"+
" {\n"+
" alert(\""+Messages.getBodyJavascriptString(locale,"WikiConnector.PathMustStartWithACharacter")+"\");\n"+
" editconnection.serverpath.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,"WikiConnector.PleaseSupplyAValidWikiServerName")+"\");\n"+
" SelectTab(\""+Messages.getBodyJavascriptString(locale,"WikiConnector.Server")+"\");\n"+
" editconnection.servername.focus();\n"+
" return false;\n"+
" }\n"+
" if (editconnection.serverport.value != \"\" && !isInteger(editconnection.serverport.value))\n"+
" {\n"+
" alert(\""+Messages.getBodyJavascriptString(locale,"WikiConnector.WikiServerPortMustBeAValidInteger")+"\");\n"+
" SelectTab(\""+Messages.getBodyJavascriptString(locale,"WikiConnector.Server")+"\");\n"+
" editconnection.serverport.focus();\n"+
" return false;\n"+
" }\n"+
" if (editconnection.serverpath.value != \"\" && editconnection.serverpath.value.indexOf(\"/\") != 0)\n"+
" {\n"+
" alert(\""+Messages.getBodyJavascriptString(locale,"WikiConnector.PathMustStartWithACharacter")+"\");\n"+
" SelectTab(\""+Messages.getBodyJavascriptString(locale,"WikiConnector.Server")+"\");\n"+
" editconnection.serverpath.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.
*/
public void outputConfigurationBody(IThreadContext threadContext, IHTTPOutput out,
Locale locale, ConfigParams parameters, String tabName)
throws ManifoldCFException, IOException
{
String protocol = parameters.getParameter(WikiConfig.PARAM_PROTOCOL);
if (protocol == null)
protocol = "http";
String server = parameters.getParameter(WikiConfig.PARAM_SERVER);
if (server == null)
server = "";
String port = parameters.getParameter(WikiConfig.PARAM_PORT);
if (port == null)
port = "";
String path = parameters.getParameter(WikiConfig.PARAM_PATH);
if (path == null)
path = "/w";
String login = parameters.getParameter(WikiConfig.PARAM_LOGIN);
if (login == null) {
login = "";
}
String pass = parameters.getObfuscatedParameter(WikiConfig.PARAM_PASSWORD);
if (pass == null) {
pass = "";
}
String domain = parameters.getParameter(WikiConfig.PARAM_DOMAIN);
if (domain == null) {
domain = "";
}
if (tabName.equals(Messages.getString(locale,"WikiConnector.Server")))
{
out.print(
"<table class=\"displaytable\">\n"+
" <tr>\n"+
" <td class=\"description\"><nobr>" + Messages.getBodyString(locale,"WikiConnector.Protocol") + "</nobr></td>\n"+
" <td class=\"value\">\n"+
" <select name=\"serverprotocol\">\n"+
" <option value=\"http\""+(protocol.equals("http")?" selected=\"true\"":"")+">http</option>\n"+
" <option value=\"https\""+(protocol.equals("https")?" selected=\"true\"":"")+">https</option>\n"+
" </select>\n"+
" </td>\n"+
" </tr>\n"+
" <tr>\n"+
" <td class=\"description\"><nobr>" + Messages.getBodyString(locale,"WikiConnector.ServerName") + "</nobr></td>\n"+
" <td class=\"value\">\n"+
" <input name=\"servername\" type=\"text\" size=\"32\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(server)+"\"/>\n"+
" </td>\n"+
" </tr>\n"+
" <tr>\n"+
" <td class=\"description\"><nobr>" + Messages.getBodyString(locale,"WikiConnector.Port") + "</nobr></td>\n"+
" <td class=\"value\">\n"+
" <input name=\"serverport\" type=\"text\" size=\"5\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(port)+"\"/>\n"+
" </td>\n"+
" </tr>\n"+
" <tr>\n"+
" <td class=\"description\"><nobr>" + Messages.getBodyString(locale,"WikiConnector.PathName") + "</nobr></td>\n"+
" <td class=\"value\">\n"+
" <input name=\"serverpath\" type=\"text\" size=\"16\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(path)+"\"/>\n"+
" </td>\n"+
" </tr>\n"+
" <tr>\n"+
" <td class=\"description\"><nobr>" + Messages.getBodyString(locale, "WikiConnector.ServerLogin") + "</nobr></td>\n"+
" <td class=\"value\">\n"+
" <input name=\"serverlogin\" type=\"text\" size=\"16\" value=\"" + org.apache.manifoldcf.ui.util.Encoder.attributeEscape(login) + "\"/>\n"+
" </td>\n"+
" </tr>\n"+
" <tr>\n"+
" <td class=\"description\"><nobr>" + Messages.getBodyString(locale, "WikiConnector.ServerPassword") + "</nobr></td>\n"+
" <td class=\"value\">\n"+
" <input name=\"serverpass\" type=\"password\" size=\"16\" value=\"" + org.apache.manifoldcf.ui.util.Encoder.attributeEscape(pass) + "\"/>\n"+
" </td>\n"+
" </tr>\n"+
" <tr>\n"+
" <td class=\"description\"><nobr>" + Messages.getBodyString(locale, "WikiConnector.ServerDomain") + "</nobr></td>\n"+
" <td class=\"value\">\n"+
" <input name=\"serverdomain\" type=\"text\" size=\"16\" value=\"" + org.apache.manifoldcf.ui.util.Encoder.attributeEscape(domain) + "\"/>\n"+
" </td>\n"+
" </tr>\n"+
"</table>\n"
);
}
else
{
// Server tab hiddens
out.print(
"<input type=\"hidden\" name=\"serverprotocol\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(protocol)+"\"/>\n"+
"<input type=\"hidden\" name=\"servername\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(server)+"\"/>\n"+
"<input type=\"hidden\" name=\"serverport\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(port)+"\"/>\n"+
"<input type=\"hidden\" name=\"serverpath\" value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(path)+"\"/>\n"+
"<input type=\"hidden\" name=\"serverlogin\" value=\"" + org.apache.manifoldcf.ui.util.Encoder.attributeEscape(login) + "\"/>\n"+
"<input type=\"hidden\" name=\"serverpass\" value=\"" + org.apache.manifoldcf.ui.util.Encoder.attributeEscape(pass) + "\"/>\n"+
"<input type=\"hidden\" name=\"serverdomain\" value=\"" + org.apache.manifoldcf.ui.util.Encoder.attributeEscape(domain) + "\"/>\n"
);
}
}
/** Process a configuration post.
* This method is called at the start of the connector's configuration page, whenever there is a possibility that form data for a connection has been
* posted. Its purpose is to gather form information and modify the configuration parameters accordingly.
* The name of the posted form is "editconnection".
*@param threadContext is the local thread context.
*@param variableContext is the set of variables available from the post, including binary file post information.
*@param parameters are the configuration parameters, as they currently exist, for this connection being configured.
*@return null if all is well, or a string error message if there is an error that should prevent saving of the connection (and cause a redirection to an error page).
*/
@Override
public String processConfigurationPost(IThreadContext threadContext, IPostParameters variableContext,
Locale locale, ConfigParams parameters)
throws ManifoldCFException
{
String protocol = variableContext.getParameter("serverprotocol");
if (protocol != null)
parameters.setParameter(WikiConfig.PARAM_PROTOCOL,protocol);
String server = variableContext.getParameter("servername");
if (server != null)
parameters.setParameter(WikiConfig.PARAM_SERVER,server);
String port = variableContext.getParameter("serverport");
if (port != null)
parameters.setParameter(WikiConfig.PARAM_PORT,port);
String path = variableContext.getParameter("serverpath");
if (path != null)
parameters.setParameter(WikiConfig.PARAM_PATH,path);
String login = variableContext.getParameter("serverlogin");
if (login != null) {
parameters.setParameter(WikiConfig.PARAM_LOGIN, login);
}
String pass = variableContext.getParameter("serverpass");
if (pass != null) {
parameters.setObfuscatedParameter(WikiConfig.PARAM_PASSWORD, pass);
}
String domain = variableContext.getParameter("serverdomain");
if (domain != null) {
parameters.setParameter(WikiConfig.PARAM_DOMAIN, domain);
}
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,"WikiConnector.Parameters")+"</nobr></td>\n"+
" <td class=\"value\" colspan=\"3\">\n"
);
Iterator iter = parameters.listParameters();
while (iter.hasNext())
{
String param = (String)iter.next();
String value = parameters.getParameter(param);
if (param.length() >= "password".length() && param.substring(param.length()-"password".length()).equalsIgnoreCase("password"))
{
out.print(
" <nobr>"+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(param)+"=********</nobr><br/>\n"
);
}
else if (param.length() >="keystore".length() && param.substring(param.length()-"keystore".length()).equalsIgnoreCase("keystore"))
{
IKeystoreManager kmanager = KeystoreManagerFactory.make("",value);
out.print(
" <nobr>"+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(param)+"=&lt;"+Integer.toString(kmanager.getContents().length)+Messages.getBodyString(locale,"WikiConnector.certificates")+"&gt;</nobr><br/>\n"
);
}
else
{
out.print(
" <nobr>"+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(param)+"="+org.apache.manifoldcf.ui.util.Encoder.bodyEscape(value)+"</nobr><br/>\n"
);
}
}
out.print(
" </td>\n"+
" </tr>\n"+
"</table>\n"
);
}
/** Output the specification header section.
* This method is called in the head section of a job page which has selected a repository connection of the current type. Its purpose is to add the required tabs
* to the list, and to output any javascript methods that might be needed by the job editing HTML.
*@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,"WikiConnector.NamespaceAndTitles"));
tabsArray.add(Messages.getString(locale, "WikiConnector.Security"));
out.print(
"<script type=\"text/javascript\">\n"+
"<!--\n"+
"function checkSpecification()\n"+
"{\n"+
" // Does nothing right now.\n"+
" return true;\n"+
"}\n"+
"\n"+
"function NsDelete(k)\n"+
"{\n"+
" SpecOp(\"nsop_\"+k, \"Delete\", \"ns_\"+k);\n"+
"}\n"+
"\n"+
"function NsAdd(k)\n"+
"{\n"+
" SpecOp(\"nsop\", \"Add\", \"ns_\"+k);\n"+
"}\n"+
"\n"+
"function SpecAddToken(anchorvalue)\n"+
"{\n"+
" if (editjob.spectoken.value == \"\")\n"+
" {\n"+
" alert(\"" + Messages.getBodyJavascriptString(locale, "WikiConnector.TypeInAnAccessToken") + "\");\n"+
" editjob.spectoken.focus();\n"+
" return;\n"+
" }\n"+
" SpecOp(\"accessop\",\"Add\",anchorvalue);\n"+
"}\n"+
"\n"+
"function SpecOp(n, opValue, anchorvalue)\n"+
"{\n"+
" eval(\"editjob.\"+n+\".value = \\\"\"+opValue+\"\\\"\");\n"+
" postFormSetAnchor(anchorvalue);\n"+
"}\n"+
"\n"+
"//-->\n"+
"</script>\n"
);
}
/** Output the specification body section.
* This method is called in the body section of a job page which has selected a repository connection of the current type. Its purpose is to present the required form elements for editing.
* The coder can presume that the HTML that is output from this configuration will be within appropriate <html>, <body>, and <form> tags. The name of the
* form is "editjob".
*@param out is the output to which any HTML should be sent.
*@param ds is the current document specification for this job.
*@param tabName is the current tab name.
*/
@Override
public void outputSpecificationBody(IHTTPOutput out, Locale locale, DocumentSpecification ds, String tabName)
throws ManifoldCFException, IOException
{
if (tabName.equals(Messages.getString(locale,"WikiConnector.NamespaceAndTitles")))
{
boolean seenAny = false;
// Output table column headers
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,"WikiConnector.NamespaceAndTitles2") + "</nobr></td>\n"+
" <td class=\"boxcell\">\n"+
" <table class=\"formtable\">\n"+
" <tr class=\"formheaderrow\">\n"+
" <td class=\"formcolumnheader\"></td>\n"+
" <td class=\"formcolumnheader\"><nobr>" + Messages.getBodyString(locale,"WikiConnector.Namespace") + "</nobr></td>\n"+
" <td class=\"formcolumnheader\"><nobr>" + Messages.getBodyString(locale,"WikiConnector.TitlePrefix") + "</nobr></td>\n"+
" </tr>\n"
);
int k = 0;
for (int i = 0 ; i < ds.getChildCount() ; i++)
{
SpecificationNode sn = ds.getChild(i);
if (sn.getType().equals(WikiConfig.NODE_NAMESPACE_TITLE_PREFIX))
{
String namespace = sn.getAttributeValue(WikiConfig.ATTR_NAMESPACE);
String titlePrefix = sn.getAttributeValue(WikiConfig.ATTR_TITLEPREFIX);
String nsOpName = "nsop_"+k;
String nsNsName = "nsnsname_"+k;
String nsTitlePrefix = "nstitleprefix_"+k;
out.print(
" <tr class=\""+(((k % 2)==0)?"evenformrow":"oddformrow")+"\">\n"+
" <td class=\"formcolumncell\">\n"+
" <nobr>\n"+
" <a name=\""+"ns_"+Integer.toString(k)+"\"/>\n"+
" <input type=\"hidden\" name=\""+nsOpName+"\" value=\"\"/>\n"+
" <input type=\"hidden\" name=\""+nsNsName+"\" value=\""+((namespace==null)?"":org.apache.manifoldcf.ui.util.Encoder.attributeEscape(namespace))+"\"/>\n"+
" <input type=\"hidden\" name=\""+nsTitlePrefix+"\" value=\""+((titlePrefix==null)?"":org.apache.manifoldcf.ui.util.Encoder.attributeEscape(titlePrefix))+"\"/>\n"+
" <input type=\"button\" value=\"" + Messages.getAttributeString(locale,"WikiConnector.Delete") + "\" onClick='Javascript:NsDelete("+Integer.toString(k)+")' alt=\""+Messages.getAttributeString(locale,"WikiConnector.DeleteNamespaceTitle")+Integer.toString(k)+"\"/>\n"+
" </nobr>\n"+
" </td>\n"+
" <td class=\"formcolumncell\">\n"+
" <nobr>\n"+
" "+((namespace==null)?"(default)":org.apache.manifoldcf.ui.util.Encoder.bodyEscape(namespace))+"\n"+
" </nobr>\n"+
" </td>\n"+
" <td class=\"formcolumncell\">\n"+
" <nobr>\n"+
" "+((titlePrefix==null)?"(all titles)":org.apache.manifoldcf.ui.util.Encoder.bodyEscape(titlePrefix))+"\n"+
" </nobr>\n"+
" </td>\n"+
" </tr>\n"
);
k++;
}
}
if (k == 0)
{
out.print(
" <tr class=\"formrow\"><td colspan=\"3\" class=\"formmessage\">" + Messages.getBodyString(locale,"WikiConnector.NoSpecification") + "</td></tr>\n"
);
}
// Add area
out.print(
" <tr class=\"formrow\"><td colspan=\"4\" class=\"formseparator\"><hr/></td></tr>\n"
);
// Obtain the list of namespaces
Map<String,String> namespaces = new HashMap<String,String>();
try
{
getNamespaces(namespaces);
// Extract and sort the names we're going to present
String[] nameSpaceNames = new String[namespaces.size()];
Iterator<String> keyIter = namespaces.keySet().iterator();
int j = 0;
while (keyIter.hasNext())
{
nameSpaceNames[j++] = keyIter.next();
}
java.util.Arrays.sort(nameSpaceNames);
out.print(
" <tr class=\"formrow\">\n"+
" <td class=\"formcolumncell\">\n"+
" <nobr>\n"+
" <a name=\""+"ns_"+Integer.toString(k)+"\"/>\n"+
" <input type=\"hidden\" name=\"nsop\" value=\"\"/>\n"+
" <input type=\"hidden\" name=\"nscount\" value=\""+Integer.toString(k)+"\"/>\n"+
" <input type=\"button\" value=\"" + Messages.getAttributeString(locale,"WikiConnector.Add") + "\" onClick='Javascript:NsAdd("+Integer.toString(k)+")' alt=\"" + Messages.getAttributeString(locale,"WikiConnector.AddNamespacePrefix") + "\"/>\n"+
" </nobr>\n"+
" </td>\n"+
" <td class=\"formcolumncell\">\n"+
" <nobr>\n"+
" <select name=\"nsnsname\">\n"+
" <option value=\"\" selected=\"true\">-- " + Messages.getBodyString(locale,"WikiConnector.UseDefault") + " --</option>\n"
);
for (int l = 0 ; l < nameSpaceNames.length ; l++)
{
String prettyName = nameSpaceNames[l];
String canonicalName = namespaces.get(prettyName);
out.print(
" <option value=\""+org.apache.manifoldcf.ui.util.Encoder.attributeEscape(canonicalName)+"\">"+
org.apache.manifoldcf.ui.util.Encoder.bodyEscape(prettyName)+"</option>\n"
);
}
out.print(
" </select>\n"+
" </nobr>\n"+
" </td>\n"+
" <td class=\"formcolumncell\">\n"+
" <nobr>\n"+
" <input type=\"text\" name=\"nstitleprefix\" size=\"16\" value=\"\"/>\n"+
" </nobr>\n"+
" </td>\n"+
" </tr>\n"
);
}
catch (ServiceInterruption e)
{
out.print(
" <tr class=\"formrow\"><td colspan=\"3\" class=\"formmessage\">" + Messages.getBodyString(locale,"WikiConnector.TransientError") + org.apache.manifoldcf.ui.util.Encoder.bodyEscape(e.getMessage())+"</td></tr>\n"
);
}
out.print(
" </table>\n"+
" </td>\n"+
" </tr>\n"+
"</table>\n"
);
}
else
{
// Generate hiddens
int k = 0;
for (int i = 0 ; i < ds.getChildCount() ; i++)
{
SpecificationNode sn = ds.getChild(i);
if (sn.getType().equals(WikiConfig.NODE_NAMESPACE_TITLE_PREFIX))
{
String namespace = sn.getAttributeValue(WikiConfig.ATTR_NAMESPACE);
String titlePrefix = sn.getAttributeValue(WikiConfig.ATTR_TITLEPREFIX);
String nsNsName = "nsnsname_"+k;
String nsTitlePrefix = "nstitleprefix_"+k;
out.print(
"<input type=\"hidden\" name=\""+nsNsName+"\" value=\""+((namespace == null)?"":org.apache.manifoldcf.ui.util.Encoder.attributeEscape(namespace))+"\"/>\n"+
"<input type=\"hidden\" name=\""+nsTitlePrefix+"\" value=\""+((titlePrefix == null)?"":org.apache.manifoldcf.ui.util.Encoder.attributeEscape(titlePrefix))+"\"/>\n"
);
k++;
}
}
out.print(
"<input type=\"hidden\" name=\"nscount\" value=\""+new Integer(k)+"\"/>\n"
);
}
if (tabName.equals(Messages.getString(locale, "WikiConnector.Security")))
{
out.print(
"<table class=\"displaytable\">\n"+
" <tr><td class=\"separator\" colspan=\"2\"><hr/></td></tr>\n"
);
// Go through forced ACL
int i = 0;
int k = 0;
while (i < ds.getChildCount()) {
SpecificationNode sn = ds.getChild(i++);
if (sn.getType().equals("access")) {
String accessDescription = "_" + Integer.toString(k);
String accessOpName = "accessop" + accessDescription;
String token = sn.getAttributeValue("token");
out.print(
" <tr>\n"+
" <td class=\"description\">\n"+
" <input type=\"hidden\" name=\"" + accessOpName + "\" value=\"\"/>\n"+
" <input type=\"hidden\" name=\"" + "spectoken" + accessDescription + "\" value=\"" + org.apache.manifoldcf.ui.util.Encoder.attributeEscape(token) + "\"/>\n"+
" <a name=\"" + "token_" + Integer.toString(k) + "\">\n"+
" <input type=\"button\" value=\"" + Messages.getAttributeString(locale, "WikiConnector.Delete") + "\" onClick='Javascript:SpecOp(\"" + accessOpName + "\",\"Delete\",\"token_" + Integer.toString(k) + "\")' alt=\"" + Messages.getAttributeString(locale, "WikiConnector.Delete") + Integer.toString(k) + "\"/>\n"+
" </a>&nbsp;\n"+
" </td>\n"+
" <td class=\"value\">\n"+
" " + org.apache.manifoldcf.ui.util.Encoder.bodyEscape(token) + "\n"+
" </td>\n"+
" </tr>\n"
);
k++;
}
}
if (k == 0) {
out.print(
" <tr>\n"+
" <td class=\"message\" colspan=\"2\">" + Messages.getBodyString(locale, "WikiConnector.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=\"" + Messages.getAttributeString(locale, "WikiConnector.Add") + "\" onClick='Javascript:SpecAddToken(\"token_" + Integer.toString(k + 1) + "\")' alt=\"" + Messages.getAttributeString(locale, "WikiConnector.Add") + "\"/>\n"+
" </a>&nbsp;\n"+
" </td>\n"+
" <td class=\"value\">\n"+
" <input type=\"text\" size=\"30\" name=\"spectoken\" value=\"\"/>\n"+
" </td>\n"+
" </tr>\n"+
"</table>\n"
);
}
else
{
// Finally, go through forced ACL
int i = 0;
int k = 0;
while (i < ds.getChildCount()) {
SpecificationNode sn = ds.getChild(i++);
if (sn.getType().equals("access")) {
String accessDescription = "_" + Integer.toString(k);
String token = sn.getAttributeValue("token");
out.print(
"<input type=\"hidden\" name=\"" + "spectoken" + accessDescription + "\" value=\"" + org.apache.manifoldcf.ui.util.Encoder.attributeEscape(token) + "\"/>\n"
);
k++;
}
}
out.print(
"<input type=\"hidden\" name=\"tokencount\" value=\"" + Integer.toString(k) + "\"/>\n"
);
}
}
/** Process a specification post.
* This method is called at the start of job's edit or view page, whenever there is a possibility that form data for a connection has been
* posted. Its purpose is to gather form information and modify the document specification accordingly.
* The name of the posted form is "editjob".
*@param variableContext contains the post data, including binary file-upload information.
*@param ds is the current document specification for this job.
*@return null if all is well, or a string error message if there is an error that should prevent saving of the job (and cause a redirection to an error page).
*/
@Override
public String processSpecificationPost(IPostParameters variableContext, Locale locale, DocumentSpecification ds)
throws ManifoldCFException
{
String countString = variableContext.getParameter("nscount");
if (countString != null)
{
for (int i = 0 ; i < ds.getChildCount() ; i++)
{
SpecificationNode sn = ds.getChild(i);
if (sn.getType().equals(WikiConfig.NODE_NAMESPACE_TITLE_PREFIX))
ds.removeChild(i);
else
i++;
}
int nsCount = Integer.parseInt(countString);
for (int i = 0 ; i < nsCount ; i++)
{
String nsOpName = "nsop_"+i;
String nsNsName = "nsnsname_"+i;
String nsTitlePrefix = "nstitleprefix_"+i;
String nsOp = variableContext.getParameter(nsOpName);
if (nsOp == null || !nsOp.equals("Delete"))
{
String namespaceName = variableContext.getParameter(nsNsName);
if (namespaceName != null && namespaceName.length() == 0)
namespaceName = null;
String titlePrefix = variableContext.getParameter(nsTitlePrefix);
if (titlePrefix != null && titlePrefix.length() == 0)
titlePrefix = null;
SpecificationNode sn = new SpecificationNode(WikiConfig.NODE_NAMESPACE_TITLE_PREFIX);
if (namespaceName != null)
sn.setAttribute(WikiConfig.ATTR_NAMESPACE,namespaceName);
if (titlePrefix != null)
sn.setAttribute(WikiConfig.ATTR_TITLEPREFIX,titlePrefix);
ds.addChild(ds.getChildCount(),sn);
}
}
String newOp = variableContext.getParameter("nsop");
if (newOp != null && newOp.equals("Add"))
{
String namespaceName = variableContext.getParameter("nsnsname");
if (namespaceName != null && namespaceName.length() == 0)
namespaceName = null;
String titlePrefix = variableContext.getParameter("nstitleprefix");
if (titlePrefix != null && titlePrefix.length() == 0)
titlePrefix = null;
SpecificationNode sn = new SpecificationNode(WikiConfig.NODE_NAMESPACE_TITLE_PREFIX);
if (namespaceName != null)
sn.setAttribute(WikiConfig.ATTR_NAMESPACE,namespaceName);
if (titlePrefix != null)
sn.setAttribute(WikiConfig.ATTR_TITLEPREFIX,titlePrefix);
ds.addChild(ds.getChildCount(),sn);
}
}
String xc = variableContext.getParameter("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 = "accessop" + accessDescription;
xc = variableContext.getParameter(accessOpName);
if (xc != null && xc.equals("Delete")) {
// Next row
i++;
continue;
}
// Get the stuff we need
String accessSpec = variableContext.getParameter("spectoken" + accessDescription);
SpecificationNode node = new SpecificationNode("access");
node.setAttribute("token", accessSpec);
ds.addChild(ds.getChildCount(), node);
i++;
}
String op = variableContext.getParameter("accessop");
if (op != null && op.equals("Add")) {
String accessspec = variableContext.getParameter("spectoken");
SpecificationNode node = new SpecificationNode("access");
node.setAttribute("token", accessspec);
ds.addChild(ds.getChildCount(), node);
}
}
return null;
}
/** View specification.
* This method is called in the body section of a job's view page. Its purpose is to present the document specification information to the user.
* The coder can presume that the HTML that is output from this configuration will be within appropriate <html> and <body> tags.
*@param out is the output to which any HTML should be sent.
*@param ds is the current document specification for this job.
*/
@Override
public void viewSpecification(IHTTPOutput out, Locale locale, DocumentSpecification ds)
throws ManifoldCFException, IOException
{
out.print(
"<table class=\"displaytable\">\n"+
" <tr>\n"
);
out.print(
" <td class=\"description\"><nobr>" + Messages.getBodyString(locale,"WikiConnector.NamespaceAndTitles2") + "</nobr></td>\n"+
" <td class=\"boxcell\">\n"+
" <table class=\"formtable\">\n"+
" <tr class=\"formheaderrow\">\n"+
" <td class=\"formcolumnheader\"><nobr>" + Messages.getBodyString(locale,"WikiConnector.Namespace") + "</nobr></td>\n"+
" <td class=\"formcolumnheader\"><nobr>" + Messages.getBodyString(locale,"WikiConnector.TitlePrefix") + "</nobr></td>\n"+
" </tr>\n"
);
int k = 0;
for (int i = 0 ; i < ds.getChildCount() ; i++)
{
SpecificationNode sn = ds.getChild(i);
if (sn.getType().equals(WikiConfig.NODE_NAMESPACE_TITLE_PREFIX))
{
String namespace = sn.getAttributeValue(WikiConfig.ATTR_NAMESPACE);
String titlePrefix = sn.getAttributeValue(WikiConfig.ATTR_TITLEPREFIX);
out.print(
" <tr class=\""+(((k % 2)==0)?"evenformrow":"oddformrow")+"\">\n"+
" <td class=\"formcolumncell\">\n"+
" <nobr>\n"+
" "+((namespace==null)?"(default)":org.apache.manifoldcf.ui.util.Encoder.bodyEscape(namespace))+"\n"+
" </nobr>\n"+
" </td>\n"+
" <td class=\"formcolumncell\">\n"+
" <nobr>\n"+
" "+((titlePrefix==null)?"(all documents)":org.apache.manifoldcf.ui.util.Encoder.bodyEscape(titlePrefix))+"\n"+
" </nobr>\n"+
" </td>\n"+
" </tr>\n"
);
k++;
}
}
if (k == 0)
out.print(
" <tr class=\"formrow\"><td class=\"formmessage\" colspan=\"2\">" + Messages.getBodyString(locale,"WikiConnector.AllDefaultNamespaceDocumentsIncluded") + "</td></tr>\n"
);
out.print(
" </table>\n"+
" </td>\n"+
" </tr>\n"
);
// Go through looking for access tokens
boolean seenAny = false;
int i = 0;
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, "WikiConnector.AccessTokens") + "</nobr></td>\n"+
" <td class=\"value\">\n"
);
seenAny = true;
}
String token = sn.getAttributeValue("token");
out.print(
" " + org.apache.manifoldcf.ui.util.Encoder.bodyEscape(token) + "<br/>\n"
);
}
}
if (seenAny) {
out.print(
" </td>\n"+
" </tr>\n"
);
} else {
out.print(
" <tr><td class=\"message\" colspan=\"2\"><nobr>" + Messages.getBodyString(locale, "WikiConnector.NoAccessTokensSpecified") + "</nobr></td></tr>\n"
);
}
out.print(
"</table>\n"
);
}
// Protected static classes and methods
/** Create and initialize an HttpClient instance */
protected HttpClient getInitializedClient()
throws ServiceInterruption, ManifoldCFException
{
return httpClient;
}
/** Create and initialize an HttpMethodBase */
protected HttpMethodBase getInitializedMethod(String URL)
{
GetMethod method = new GetMethod(URL);
method.getParams().setParameter("http.socket.timeout", new Integer(300000));
return method;
}
/** Create an initialize a post method */
protected HttpMethodBase getInitializedPostMethod(String URL, Map<String,String> params)
{
PostMethod method = new PostMethod(URL);
for (String key : params.keySet()) {
method.setParameter(key, params.get(key));
}
method.getParams().setParameter("http.socket.timeout", new Integer(300000));
return method;
}
// -- Methods and classes to perform a "check" operation. --
/** Do the check operation. This throws an exception if anything is wrong.
*/
protected void performCheck()
throws ManifoldCFException, ServiceInterruption
{
getSession();
boolean loginAttempted = false;
while (true)
{
HttpClient client = getInitializedClient();
HttpMethodBase executeMethod = getInitializedMethod(getCheckURL());
try
{
ExecuteCheckThread t = new ExecuteCheckThread(client,executeMethod);
try
{
t.start();
t.join();
Throwable thr = t.getException();
if (thr != null)
{
if (thr instanceof ManifoldCFException)
{
if (((ManifoldCFException)thr).getErrorCode() == ManifoldCFException.INTERRUPTED)
throw new InterruptedException(thr.getMessage());
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
throw (Error)thr;
}
if (loginAttempted || !t.isLoginRequired())
return;
}
catch (ManifoldCFException e)
{
t.interrupt();
throw e;
}
catch (ServiceInterruption e)
{
t.interrupt();
throw e;
}
catch (IOException e)
{
t.interrupt();
throw e;
}
catch (InterruptedException e)
{
t.interrupt();
// We need the caller to abandon any connections left around, so rethrow in a way that forces them to process the event properly.
throw e;
}
}
catch (InterruptedException e)
{
// Drop the connection on the floor
executeMethod = null;
throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED);
}
catch (ManifoldCFException e)
{
if (e.getErrorCode() == ManifoldCFException.INTERRUPTED)
// Drop the connection on the floor
executeMethod = null;
throw e;
}
catch (java.net.SocketTimeoutException e)
{
long currentTime = System.currentTimeMillis();
throw new ServiceInterruption("Fetch test timed out reading from the Wiki server: "+e.getMessage(),e,currentTime+300000L,currentTime+12L * 60000L,-1,false);
}
catch (java.net.SocketException e)
{
long currentTime = System.currentTimeMillis();
throw new ServiceInterruption("Fetch test received a socket error reading from Wiki server: "+e.getMessage(),e,currentTime+300000L,currentTime+12L * 60000L,-1,false);
}
catch (org.apache.commons.httpclient.ConnectTimeoutException e)
{
long currentTime = System.currentTimeMillis();
throw new ServiceInterruption("Fetch test connection timed out reading from Wiki server: "+e.getMessage(),e,currentTime+300000L,currentTime+12L * 60000L,-1,false);
}
catch (InterruptedIOException e)
{
executeMethod = null;
throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED);
}
catch (IOException e)
{
throw new ManifoldCFException("Fetch test had an IO failure: "+e.getMessage(),e);
}
finally
{
if (executeMethod != null)
executeMethod.releaseConnection();
}
if (!loginToAPI())
break;
loginAttempted = true;
// Back around...
}
}
/** Get a URL for a check operation.
*/
protected String getCheckURL()
throws ManifoldCFException
{
return baseURL + "action=query&list=allpages&aplimit=1";
}
/** Thread to execute a "check" operation. This thread both executes the operation and parses the result. */
protected static class ExecuteCheckThread extends Thread
{
protected HttpClient client;
protected HttpMethodBase executeMethod;
protected Throwable exception = null;
protected boolean loginNeeded = false;
public ExecuteCheckThread(HttpClient client, HttpMethodBase executeMethod)
{
super();
setDaemon(true);
this.client = client;
this.executeMethod = executeMethod;
}
public void run()
{
try
{
// Call the execute method appropriately
int rval = client.executeMethod(executeMethod);
if (rval != 200)
throw new ManifoldCFException("Unexpected response code: "+rval);
// Read response and make sure it's valid
InputStream is = executeMethod.getResponseBodyAsStream();
try
{
loginNeeded = parseCheckResponse(is);
}
finally
{
try
{
is.close();
}
catch (IllegalStateException e)
{
// Ignore this error
}
}
}
catch (Throwable e)
{
this.exception = e;
}
}
public Throwable getException()
{
return exception;
}
public boolean isLoginRequired()
{
return loginNeeded;
}
}
/** Parse check response, e.g.:
* <api xmlns="http://www.mediawiki.org/xml/api/">
* <query>
* <allpages>
* <p pageid="19839654" ns="0" title="Kre&#039;fey" />
* </allpages>
* </query>
* <query-continue>
* <allpages apfrom="Krea" />
* </query-continue>
* </api>
*/
protected static boolean parseCheckResponse(InputStream is)
throws ManifoldCFException, ServiceInterruption
{
// Parse the document. This will cause various things to occur, within the instantiated XMLContext class.
XMLStream x = new XMLStream();
WikiCheckAPIContext c = new WikiCheckAPIContext(x);
x.setContext(c);
try
{
try
{
x.parse(is);
if (c.isLoginRequired())
return true;
if (!c.hasResponse())
throw new ManifoldCFException("Valid API response not detected");
return false;
}
catch (IOException e)
{
long time = System.currentTimeMillis();
throw new ServiceInterruption(e.getMessage(),e,time + 300000L,time + 12L * 60000L,-1,false);
}
}
finally
{
x.cleanup();
}
}
/** Class representing the "api" context of a "check" response */
protected static class WikiCheckAPIContext extends SingleLevelContext
{
boolean responseSeen = false;
boolean needLogin = false;
public WikiCheckAPIContext(XMLStream theStream)
{
super(theStream,"api");
}
protected BaseProcessingContext createChild(String namespaceURI, String localName, String qName, Attributes atts)
{
return new WikiCheckQueryContext(theStream,namespaceURI,localName,qName,atts);
}
protected void finishChild(BaseProcessingContext child)
throws ManifoldCFException
{
responseSeen |= ((WikiCheckQueryContext)child).hasResponse();
needLogin |= ((WikiCheckQueryContext)child).isLoginRequired();
}
public boolean hasResponse()
{
return responseSeen;
}
public boolean isLoginRequired()
{
return needLogin;
}
}
/** Class representing the "api/query" context of a "check" response */
protected static class WikiCheckQueryContext extends SingleLevelErrorContext
{
protected boolean responseSeen = false;
public WikiCheckQueryContext(XMLStream theStream, String namespaceURI, String localName, String qName, Attributes atts)
{
super(theStream,namespaceURI,localName,qName,atts,"query");
}
protected BaseProcessingContext createChild(String namespaceURI, String localName, String qName, Attributes atts)
{
return new WikiCheckAllPagesContext(theStream,namespaceURI,localName,qName,atts);
}
protected void finishChild(BaseProcessingContext child)
throws ManifoldCFException
{
responseSeen |= ((WikiCheckAllPagesContext)child).hasResponse();
}
public boolean hasResponse()
{
return responseSeen;
}
}
/** Class recognizing the "api/query/allpages" context of a "check" response */
protected static class WikiCheckAllPagesContext extends SingleLevelContext
{
protected boolean responseSeen = false;
public WikiCheckAllPagesContext(XMLStream theStream, String namespaceURI, String localName, String qName, Attributes atts)
{
super(theStream,namespaceURI,localName,qName,atts,"allpages");
}
protected BaseProcessingContext createChild(String namespaceURI, String localName, String qName, Attributes atts)
{
return new WikiCheckPContext(theStream,namespaceURI,localName,qName,atts);
}
protected void finishChild(BaseProcessingContext child)
throws ManifoldCFException
{
responseSeen |= true;
}
public boolean hasResponse()
{
return responseSeen;
}
}
/** Class representing the "api/query/allpages/p" context of a "check" response */
protected static class WikiCheckPContext extends BaseProcessingContext
{
public WikiCheckPContext(XMLStream theStream, String namespaceURI, String localName, String qName, Attributes atts)
{
super(theStream,namespaceURI,localName,qName,atts);
}
}
// -- Methods and classes to perform a "list pages" operation. --
/** Perform a series of listPages() operations, so that we fully obtain the documents we're looking for even though
* we're limited to 500 of them per request.
*/
protected void listAllPages(ISeedingActivity activities, String namespace, String prefix, long startTime, long endTime)
throws ManifoldCFException, ServiceInterruption
{
getSession();
String lastTitle = null;
while (true)
{
activities.checkJobStillActive();
// Start with the last title seen in the previous round.
String newLastTitle = executeListPagesViaThread(lastTitle,namespace,prefix,activities);
if (newLastTitle == null)
break;
lastTitle = newLastTitle;
}
}
/** Execute a listPages() operation via a thread. Returns the last page title. */
protected String executeListPagesViaThread(String startPageTitle, String namespace, String prefix, ISeedingActivity activities)
throws ManifoldCFException, ServiceInterruption
{
boolean loginAttempted = false;
while (true)
{
HttpClient client = getInitializedClient();
HttpMethodBase executeMethod = getInitializedMethod(getListPagesURL(startPageTitle,namespace,prefix));
try
{
PageBuffer pageBuffer = new PageBuffer();
ExecuteListPagesThread t = new ExecuteListPagesThread(client,executeMethod,pageBuffer,startPageTitle);
try
{
t.start();
// Pick up the pages, 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 pageID = pageBuffer.fetch();
if (pageID == null)
break;
// Add the pageID to the queue
activities.addSeedDocument(pageID);
}
t.join();
Throwable thr = t.getException();
if (thr != null)
{
if (thr instanceof ManifoldCFException)
{
if (((ManifoldCFException)thr).getErrorCode() == ManifoldCFException.INTERRUPTED)
throw new InterruptedException(thr.getMessage());
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
throw (Error)thr;
}
if (loginAttempted || !t.isLoginRequired())
return t.getLastPageTitle();
}
catch (ManifoldCFException e)
{
t.interrupt();
throw e;
}
catch (ServiceInterruption e)
{
t.interrupt();
throw e;
}
catch (IOException e)
{
t.interrupt();
throw e;
}
catch (InterruptedException e)
{
t.interrupt();
// We need the caller to abandon any connections left around, so rethrow in a way that forces them to process the event properly.
throw e;
}
finally
{
// Make SURE buffer is dead, otherwise child thread may well hang waiting on it
pageBuffer.abandon();
}
}
catch (InterruptedException e)
{
// Drop the connection on the floor
executeMethod = null;
throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED);
}
catch (ManifoldCFException e)
{
if (e.getErrorCode() == ManifoldCFException.INTERRUPTED)
// Drop the connection on the floor
executeMethod = null;
throw e;
}
catch (java.net.SocketTimeoutException e)
{
long currentTime = System.currentTimeMillis();
throw new ServiceInterruption("ListPages timed out reading from the Wiki server: "+e.getMessage(),e,currentTime+300000L,currentTime+12L * 60000L,-1,false);
}
catch (java.net.SocketException e)
{
long currentTime = System.currentTimeMillis();
throw new ServiceInterruption("ListPages received a socket error reading from Wiki server: "+e.getMessage(),e,currentTime+300000L,currentTime+12L * 60000L,-1,false);
}
catch (org.apache.commons.httpclient.ConnectTimeoutException e)
{
long currentTime = System.currentTimeMillis();
throw new ServiceInterruption("ListPages connection timed out reading from Wiki server: "+e.getMessage(),e,currentTime+300000L,currentTime+12L * 60000L,-1,false);
}
catch (InterruptedIOException e)
{
executeMethod = null;
throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED);
}
catch (IOException e)
{
throw new ManifoldCFException("ListPages had an IO failure: "+e.getMessage(),e);
}
finally
{
if (executeMethod != null)
executeMethod.releaseConnection();
}
if (!loginToAPI())
break;
loginAttempted = true;
// Back around...
}
return null;
}
/** Create a URL to obtain the next 500 pages.
*/
protected String getListPagesURL(String startingTitle, String namespace, String prefix)
throws ManifoldCFException
{
try
{
return baseURL + "action=query&list=allpages" +
((prefix != null)?"&apprefix="+URLEncoder.encode(prefix,"utf-8"):"") +
((namespace != null)?"&apnamespace="+URLEncoder.encode(namespace,"utf-8"):"") +
((startingTitle!=null)?"&apfrom="+URLEncoder.encode(startingTitle,"utf-8"):"") +
"&aplimit=500";
}
catch (UnsupportedEncodingException e)
{
throw new ManifoldCFException(e.getMessage(),e);
}
}
protected static class ReturnString
{
public String returnValue = null;
}
/** Thread to execute a list pages operation */
protected static class ExecuteListPagesThread extends Thread
{
protected HttpClient client;
protected HttpMethodBase executeMethod;
protected Throwable exception = null;
protected PageBuffer pageBuffer;
protected String lastPageTitle = null;
protected String startPageTitle;
protected boolean loginNeeded = false;
public ExecuteListPagesThread(HttpClient client, HttpMethodBase executeMethod, PageBuffer pageBuffer, String startPageTitle)
{
super();
setDaemon(true);
this.client = client;
this.executeMethod = executeMethod;
this.pageBuffer = pageBuffer;
this.startPageTitle = startPageTitle;
}
public void run()
{
try
{
// Call the execute method appropriately
int rval = client.executeMethod(executeMethod);
if (rval != 200)
throw new ManifoldCFException("Unexpected response code: "+rval);
// Read response and make sure it's valid
InputStream is = executeMethod.getResponseBodyAsStream();
try
{
ReturnString returnString = new ReturnString();
loginNeeded = parseListPagesResponse(is,pageBuffer,startPageTitle,returnString);
lastPageTitle = returnString.returnValue;
}
finally
{
try
{
is.close();
}
catch (IllegalStateException e)
{
// Ignore this error
}
}
}
catch (Throwable e)
{
this.exception = e;
}
finally
{
pageBuffer.signalDone();
}
}
public Throwable getException()
{
return exception;
}
public String getLastPageTitle()
{
return lastPageTitle;
}
public boolean isLoginRequired()
{
return loginNeeded;
}
}
/** Parse list output, e.g.:
* <api xmlns="http://www.mediawiki.org/xml/api/">
* <query>
* <allpages>
* <p pageid="19839654" ns="0" title="Kre&#039;fey" />
* <p pageid="30955295" ns="0" title="Kre-O" />
* <p pageid="14773725" ns="0" title="Kre8tiveworkz" />
* <p pageid="19219017" ns="0" title="Kre M&#039;Baye" />
* <p pageid="19319577" ns="0" title="Kre Mbaye" />
* </allpages>
* </query>
* <query-continue>
* <allpages apfrom="Krea" />
* </query-continue>
* </api>
*/
protected static boolean parseListPagesResponse(InputStream is, PageBuffer buffer, String startPageTitle, ReturnString lastTitle)
throws ManifoldCFException, ServiceInterruption
{
// Parse the document. This will cause various things to occur, within the instantiated XMLContext class.
XMLStream x = new XMLStream();
WikiListPagesAPIContext c = new WikiListPagesAPIContext(x,buffer,startPageTitle);
x.setContext(c);
try
{
try
{
x.parse(is);
String lastTitleString = c.getLastTitle();
lastTitle.returnValue = lastTitleString;
return c.isLoginRequired();
}
catch (IOException e)
{
long time = System.currentTimeMillis();
throw new ServiceInterruption(e.getMessage(),e,time + 300000L,time + 12L * 60000L,-1,false);
}
}
finally
{
x.cleanup();
}
}
/** Class representing the "api" context of a "list all pages" response */
protected static class WikiListPagesAPIContext extends SingleLevelContext
{
protected String lastTitle = null;
protected PageBuffer buffer;
protected String startPageTitle;
protected boolean loginNeeded = false;
public WikiListPagesAPIContext(XMLStream theStream, PageBuffer buffer, String startPageTitle)
{
super(theStream,"api");
this.buffer = buffer;
this.startPageTitle = startPageTitle;
}
protected BaseProcessingContext createChild(String namespaceURI, String localName, String qName, Attributes atts)
{
return new WikiListPagesQueryContext(theStream,namespaceURI,localName,qName,atts,buffer,startPageTitle);
}
protected void finishChild(BaseProcessingContext child)
throws ManifoldCFException
{
lastTitle = ((WikiListPagesQueryContext)child).getLastTitle();
loginNeeded |= ((WikiListPagesQueryContext)child).isLoginRequired();
}
public String getLastTitle()
{
return lastTitle;
}
public boolean isLoginRequired()
{
return loginNeeded;
}
}
/** Class representing the "api/query" context of a "list all pages" response */
protected static class WikiListPagesQueryContext extends SingleLevelErrorContext
{
protected String lastTitle = null;
protected PageBuffer buffer;
protected String startPageTitle;
public WikiListPagesQueryContext(XMLStream theStream, String namespaceURI, String localName, String qName, Attributes atts,
PageBuffer buffer, String startPageTitle)
{
super(theStream,namespaceURI,localName,qName,atts,"query");
this.buffer = buffer;
this.startPageTitle = startPageTitle;
}
protected BaseProcessingContext createChild(String namespaceURI, String localName, String qName, Attributes atts)
{
return new WikiListPagesAllPagesContext(theStream,namespaceURI,localName,qName,atts,buffer,startPageTitle);
}
protected void finishChild(BaseProcessingContext child)
throws ManifoldCFException
{
lastTitle = ((WikiListPagesAllPagesContext)child).getLastTitle();
}
public String getLastTitle()
{
return lastTitle;
}
}
/** Class recognizing the "api/query/allpages" context of a "list all pages" response */
protected static class WikiListPagesAllPagesContext extends SingleLevelContext
{
protected String lastTitle = null;
protected PageBuffer buffer;
protected String startPageTitle;
public WikiListPagesAllPagesContext(XMLStream theStream, String namespaceURI, String localName, String qName, Attributes atts,
PageBuffer buffer, String startPageTitle)
{
super(theStream,namespaceURI,localName,qName,atts,"allpages");
this.buffer = buffer;
this.startPageTitle = startPageTitle;
}
protected BaseProcessingContext createChild(String namespaceURI, String localName, String qName, Attributes atts)
{
// When we recognize allpages, we need to look for <p> records.
return new WikiListPagesPContext(theStream,namespaceURI,localName,qName,atts,buffer,startPageTitle);
}
protected void finishChild(BaseProcessingContext child)
throws ManifoldCFException
{
// Update the last title from all the <p> records we saw.
lastTitle = ((WikiListPagesPContext)child).getLastTitle();
}
public String getLastTitle()
{
return lastTitle;
}
}
/** Class representing the "api/query/allpages/p" context of a "list all pages" response */
protected static class WikiListPagesPContext extends BaseProcessingContext
{
protected String lastTitle = null;
protected PageBuffer buffer;
protected String startPageTitle;
public WikiListPagesPContext(XMLStream theStream, String namespaceURI, String localName, String qName, Attributes atts,
PageBuffer buffer, String startPageTitle)
{
super(theStream,namespaceURI,localName,qName,atts);
this.buffer = buffer;
this.startPageTitle = startPageTitle;
}
protected XMLContext beginTag(String namespaceURI, String localName, String qName, Attributes atts)
throws ManifoldCFException, ServiceInterruption
{
if (qName.equals("p"))
{
String currentTitle = atts.getValue("title");
// Skip the record that matches the start page title (just pretend it isn't there)
if (startPageTitle == null || !currentTitle.equals(startPageTitle))
{
lastTitle = currentTitle;
String pageID = atts.getValue("pageid");
// Add the discovered page id to the page buffer
try
{
buffer.add(pageID);
}
catch (InterruptedException e)
{
throw new ManifoldCFException(e.getMessage(),e,ManifoldCFException.INTERRUPTED);
}
}
}
return super.beginTag(namespaceURI,localName,qName,atts);
}
public String getLastTitle()
{
return lastTitle;
}
}
// -- Methods and classes to perform a "get doc urls" operation. --
protected void getDocURLs(String[] documentIdentifiers, Map<String,String> urls)
throws ManifoldCFException, ServiceInterruption
{
getSession();
boolean loginAttempted = false;
while (true)
{
HttpClient client = getInitializedClient();
HttpMethodBase executeMethod = getInitializedMethod(getGetDocURLsURL(documentIdentifiers));
try
{
ExecuteGetDocURLsThread t = new ExecuteGetDocURLsThread(client,executeMethod,urls);
try
{
t.start();
t.join();
Throwable thr = t.getException();
if (thr != null)
{
if (thr instanceof ManifoldCFException)
{
if (((ManifoldCFException)thr).getErrorCode() == ManifoldCFException.INTERRUPTED)
throw new InterruptedException(thr.getMessage());
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
throw (Error)thr;
}
if (loginAttempted || !t.isLoginRequired())
return;
}
catch (ManifoldCFException e)
{
t.interrupt();
throw e;
}
catch (ServiceInterruption e)
{
t.interrupt();
throw e;
}
catch (IOException e)
{
t.interrupt();
throw e;
}
catch (InterruptedException e)
{
t.interrupt();
// We need the caller to abandon any connections left around, so rethrow in a way that forces them to process the event properly.
throw e;
}
}
catch (InterruptedException e)
{
// Drop the connection on the floor
executeMethod = null;
throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED);
}
catch (ManifoldCFException e)
{
if (e.getErrorCode() == ManifoldCFException.INTERRUPTED)
// Drop the connection on the floor
executeMethod = null;
throw e;
}
catch (java.net.SocketTimeoutException e)
{
long currentTime = System.currentTimeMillis();
throw new ServiceInterruption("URL fetch timed out reading from the Wiki server: "+e.getMessage(),e,currentTime+300000L,currentTime+12L * 60000L,-1,false);
}
catch (java.net.SocketException e)
{
long currentTime = System.currentTimeMillis();
throw new ServiceInterruption("URL fetch received a socket error reading from Wiki server: "+e.getMessage(),e,currentTime+300000L,currentTime+12L * 60000L,-1,false);
}
catch (org.apache.commons.httpclient.ConnectTimeoutException e)
{
long currentTime = System.currentTimeMillis();
throw new ServiceInterruption("URL fetch connection timed out reading from Wiki server: "+e.getMessage(),e,currentTime+300000L,currentTime+12L * 60000L,-1,false);
}
catch (InterruptedIOException e)
{
executeMethod = null;
throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED);
}
catch (IOException e)
{
throw new ManifoldCFException("URL fetch had an IO failure: "+e.getMessage(),e);
}
finally
{
if (executeMethod != null)
executeMethod.releaseConnection();
}
if (!loginToAPI())
break;
loginAttempted = true;
}
}
/** Create a URL to obtain multiple page's urls, given the page IDs.
*/
protected String getGetDocURLsURL(String[] documentIdentifiers)
throws ManifoldCFException
{
StringBuilder sb = new StringBuilder();
for (int i = 0 ; i < documentIdentifiers.length ; i++)
{
if (i > 0)
sb.append("|");
sb.append(documentIdentifiers[i]);
}
try
{
return baseURL + "action=query&prop=info&pageids="+URLEncoder.encode(sb.toString(),"utf-8")+"&inprop=url";
}
catch (UnsupportedEncodingException e)
{
throw new ManifoldCFException(e.getMessage(),e);
}
}
/** Thread to execute a "get timestamp" operation. This thread both executes the operation and parses the result. */
protected static class ExecuteGetDocURLsThread extends Thread
{
protected HttpClient client;
protected HttpMethodBase executeMethod;
protected Throwable exception = null;
protected Map<String,String> urls;
protected boolean loginNeeded = false;
public ExecuteGetDocURLsThread(HttpClient client, HttpMethodBase executeMethod, Map<String,String> urls)
{
super();
setDaemon(true);
this.client = client;
this.executeMethod = executeMethod;
this.urls = urls;
}
public void run()
{
try
{
// Call the execute method appropriately
int rval = client.executeMethod(executeMethod);
if (rval != 200)
throw new ManifoldCFException("Unexpected response code: "+rval);
// Read response and make sure it's valid
InputStream is = executeMethod.getResponseBodyAsStream();
try
{
loginNeeded = parseGetDocURLsResponse(is,urls);
}
finally
{
try
{
is.close();
}
catch (IllegalStateException e)
{
// Ignore this error
}
}
}
catch (Throwable e)
{
this.exception = e;
}
}
public Throwable getException()
{
return exception;
}
public boolean isLoginRequired()
{
return loginNeeded;
}
}
/** This method parses a response like the following:
* <api>
* <query>
* <pages>
* <page pageid="27697087" ns="0" title="API" fullurl="..."/>
* </pages>
* </query>
* </api>
*/
protected static boolean parseGetDocURLsResponse(InputStream is, Map<String,String> urls)
throws ManifoldCFException, ServiceInterruption
{
// Parse the document. This will cause various things to occur, within the instantiated XMLContext class.
XMLStream x = new XMLStream();
WikiGetDocURLsAPIContext c = new WikiGetDocURLsAPIContext(x,urls);
x.setContext(c);
try
{
try
{
x.parse(is);
return c.isLoginRequired();
}
catch (IOException e)
{
long time = System.currentTimeMillis();
throw new ServiceInterruption(e.getMessage(),e,time + 300000L,time + 12L * 60000L,-1,false);
}
}
finally
{
x.cleanup();
}
}
/** Class representing the "api" context of a "get timestamp" response */
protected static class WikiGetDocURLsAPIContext extends SingleLevelContext
{
protected Map<String,String> urls;
protected boolean loginNeeded = false;
public WikiGetDocURLsAPIContext(XMLStream theStream, Map<String,String> urls)
{
super(theStream,"api");
this.urls = urls;
}
protected BaseProcessingContext createChild(String namespaceURI, String localName, String qName, Attributes atts)
{
return new WikiGetDocURLsQueryContext(theStream,namespaceURI,localName,qName,atts,urls);
}
protected void finishChild(BaseProcessingContext child)
throws ManifoldCFException
{
loginNeeded |= ((WikiGetDocURLsQueryContext)child).isLoginRequired();
}
public boolean isLoginRequired()
{
return loginNeeded;
}
}
/** Class representing the "api/query" context of a "get timestamp" response */
protected static class WikiGetDocURLsQueryContext extends SingleLevelErrorContext
{
protected Map<String,String> urls;
public WikiGetDocURLsQueryContext(XMLStream theStream, String namespaceURI, String localName, String qName, Attributes atts,
Map<String,String> urls)
{
super(theStream,namespaceURI,localName,qName,atts,"query");
this.urls = urls;
}
protected BaseProcessingContext createChild(String namespaceURI, String localName, String qName, Attributes atts)
{
return new WikiGetDocURLsPagesContext(theStream,namespaceURI,localName,qName,atts,urls);
}
protected void finishChild(BaseProcessingContext child)
throws ManifoldCFException
{
}
}
/** Class looking for the "api/query/pages" context of a "get timestamp" response */
protected static class WikiGetDocURLsPagesContext extends SingleLevelContext
{
protected Map<String,String> urls;
public WikiGetDocURLsPagesContext(XMLStream theStream, String namespaceURI, String localName, String qName, Attributes atts,
Map<String,String> urls)
{
super(theStream,namespaceURI,localName,qName,atts,"pages");
this.urls = urls;
}
protected BaseProcessingContext createChild(String namespaceURI, String localName, String qName, Attributes atts)
{
return new WikiGetDocURLsPageContext(theStream,namespaceURI,localName,qName,atts,urls);
}
protected void finishChild(BaseProcessingContext child)
throws ManifoldCFException
{
}
}
/** Class looking for the "api/query/pages/page" context of a "get timestamp" response */
protected static class WikiGetDocURLsPageContext extends BaseProcessingContext
{
protected Map<String,String> urls;
public WikiGetDocURLsPageContext(XMLStream theStream, String namespaceURI, String localName, String qName, Attributes atts,
Map<String,String> urls)
{
super(theStream,namespaceURI,localName,qName,atts);
this.urls = urls;
}
protected XMLContext beginTag(String namespaceURI, String localName, String qName, Attributes atts)
throws ManifoldCFException, ServiceInterruption
{
if (qName.equals("page"))
{
String pageID = atts.getValue("pageid");
String fullURL = atts.getValue("fullurl");
if (pageID != null && fullURL != null)
urls.put(pageID,fullURL);
}
return super.beginTag(namespaceURI,localName,qName,atts);
}
}
// -- Methods and classes to perform a "get Timestamp" operation. --
/** Obtain document versions for a set of documents.
*/
protected void getTimestamps(String[] documentIdentifiers, Map<String,String> versions, IVersionActivity activities)
throws ManifoldCFException, ServiceInterruption
{
getSession();
boolean loginAttempted = false;
while (true)
{
HttpClient client = getInitializedClient();
HttpMethodBase executeMethod = getInitializedMethod(getGetTimestampURL(documentIdentifiers));
try
{
ExecuteGetTimestampThread t = new ExecuteGetTimestampThread(client,executeMethod,versions);
try
{
t.start();
t.join();
Throwable thr = t.getException();
if (thr != null)
{
if (thr instanceof ManifoldCFException)
{
if (((ManifoldCFException)thr).getErrorCode() == ManifoldCFException.INTERRUPTED)
throw new InterruptedException(thr.getMessage());
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
throw (Error)thr;
}
if (loginAttempted || !t.isLoginRequired())
return;
}
catch (ManifoldCFException e)
{
t.interrupt();
throw e;
}
catch (ServiceInterruption e)
{
t.interrupt();
throw e;
}
catch (IOException e)
{
t.interrupt();
throw e;
}
catch (InterruptedException e)
{
t.interrupt();
// We need the caller to abandon any connections left around, so rethrow in a way that forces them to process the event properly.
throw e;
}
}
catch (InterruptedException e)
{
// Drop the connection on the floor
executeMethod = null;
throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED);
}
catch (ManifoldCFException e)
{
if (e.getErrorCode() == ManifoldCFException.INTERRUPTED)
// Drop the connection on the floor
executeMethod = null;
throw e;
}
catch (java.net.SocketTimeoutException e)
{
long currentTime = System.currentTimeMillis();
throw new ServiceInterruption("Version fetch timed out reading from the Wiki server: "+e.getMessage(),e,currentTime+300000L,currentTime+12L * 60000L,-1,false);
}
catch (java.net.SocketException e)
{
long currentTime = System.currentTimeMillis();
throw new ServiceInterruption("Version fetch received a socket error reading from Wiki server: "+e.getMessage(),e,currentTime+300000L,currentTime+12L * 60000L,-1,false);
}
catch (org.apache.commons.httpclient.ConnectTimeoutException e)
{
long currentTime = System.currentTimeMillis();
throw new ServiceInterruption("Version fetch connection timed out reading from Wiki server: "+e.getMessage(),e,currentTime+300000L,currentTime+12L * 60000L,-1,false);
}
catch (InterruptedIOException e)
{
executeMethod = null;
throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED);
}
catch (IOException e)
{
throw new ManifoldCFException("Version fetch had an IO failure: "+e.getMessage(),e);
}
finally
{
if (executeMethod != null)
executeMethod.releaseConnection();
}
if (!loginToAPI())
break;
loginAttempted = true;
}
}
/** Create a URL to obtain multiple page's timestamps, given the page IDs.
*/
protected String getGetTimestampURL(String[] documentIdentifiers)
throws ManifoldCFException
{
StringBuilder sb = new StringBuilder();
for (int i = 0 ; i < documentIdentifiers.length ; i++)
{
if (i > 0)
sb.append("|");
sb.append(documentIdentifiers[i]);
}
try
{
return baseURL + "action=query&prop=revisions&pageids="+URLEncoder.encode(sb.toString(),"utf-8")+"&rvprop=timestamp";
}
catch (UnsupportedEncodingException e)
{
throw new ManifoldCFException(e.getMessage(),e);
}
}
/** Thread to execute a "get timestamp" operation. This thread both executes the operation and parses the result. */
protected static class ExecuteGetTimestampThread extends Thread
{
protected HttpClient client;
protected HttpMethodBase executeMethod;
protected Throwable exception = null;
protected Map<String,String> versions;
protected boolean loginNeeded = false;
public ExecuteGetTimestampThread(HttpClient client, HttpMethodBase executeMethod, Map<String,String> versions)
{
super();
setDaemon(true);
this.client = client;
this.executeMethod = executeMethod;
this.versions = versions;
}
public void run()
{
try
{
// Call the execute method appropriately
int rval = client.executeMethod(executeMethod);
if (rval != 200)
throw new ManifoldCFException("Unexpected response code: "+rval);
// Read response and make sure it's valid
InputStream is = executeMethod.getResponseBodyAsStream();
try
{
loginNeeded = parseGetTimestampResponse(is,versions);
}
finally
{
try
{
is.close();
}
catch (IllegalStateException e)
{
// Ignore this error
}
}
}
catch (Throwable e)
{
this.exception = e;
}
}
public Throwable getException()
{
return exception;
}
public boolean isLoginRequired()
{
return loginNeeded;
}
}
/** This method parses a response like the following:
* <api>
* <query>
* <pages>
* <page pageid="27697087" ns="0" title="API">
* <revisions>
* <rev user="Graham87" timestamp="2010-06-13T08:41:17Z" />
* </revisions>
* </page>
* </pages>
* </query>
* </api>
*/
protected static boolean parseGetTimestampResponse(InputStream is, Map<String,String> versions)
throws ManifoldCFException, ServiceInterruption
{
// Parse the document. This will cause various things to occur, within the instantiated XMLContext class.
XMLStream x = new XMLStream();
WikiGetTimestampAPIContext c = new WikiGetTimestampAPIContext(x,versions);
x.setContext(c);
try
{
try
{
x.parse(is);
return c.isLoginRequired();
}
catch (IOException e)
{
long time = System.currentTimeMillis();
throw new ServiceInterruption(e.getMessage(),e,time + 300000L,time + 12L * 60000L,-1,false);
}
}
finally
{
x.cleanup();
}
}
/** Class representing the "api" context of a "get timestamp" response */
protected static class WikiGetTimestampAPIContext extends SingleLevelContext
{
protected Map<String,String> versions;
protected boolean loginNeeded = false;
public WikiGetTimestampAPIContext(XMLStream theStream, Map<String,String> versions)
{
super(theStream,"api");
this.versions = versions;
}
protected BaseProcessingContext createChild(String namespaceURI, String localName, String qName, Attributes atts)
{
return new WikiGetTimestampQueryContext(theStream,namespaceURI,localName,qName,atts,versions);
}
protected void finishChild(BaseProcessingContext child)
throws ManifoldCFException
{
loginNeeded |= ((WikiGetTimestampQueryContext)child).isLoginRequired();
}
public boolean isLoginRequired()
{
return loginNeeded;
}
}
/** Class representing the "api/query" context of a "get timestamp" response */
protected static class WikiGetTimestampQueryContext extends SingleLevelErrorContext
{
protected Map<String,String> versions;
public WikiGetTimestampQueryContext(XMLStream theStream, String namespaceURI, String localName, String qName, Attributes atts,
Map<String,String> versions)
{
super(theStream,namespaceURI,localName,qName,atts,"query");
this.versions = versions;
}
protected BaseProcessingContext createChild(String namespaceURI, String localName, String qName, Attributes atts)
{
return new WikiGetTimestampPagesContext(theStream,namespaceURI,localName,qName,atts,versions);
}
protected void finishChild(BaseProcessingContext child)
throws ManifoldCFException
{
}
}
/** Class looking for the "api/query/pages" context of a "get timestamp" response */
protected static class WikiGetTimestampPagesContext extends SingleLevelContext
{
protected Map<String,String> versions;
public WikiGetTimestampPagesContext(XMLStream theStream, String namespaceURI, String localName, String qName, Attributes atts,
Map<String,String> versions)
{
super(theStream,namespaceURI,localName,qName,atts,"pages");
this.versions = versions;
}
protected BaseProcessingContext createChild(String namespaceURI, String localName, String qName, Attributes atts)
{
return new WikiGetTimestampPageContext(theStream,namespaceURI,localName,qName,atts,versions);
}
protected void finishChild(BaseProcessingContext child)
throws ManifoldCFException
{
}
}
/** Class looking for the "api/query/pages/page" context of a "get timestamp" response */
protected static class WikiGetTimestampPageContext extends BaseProcessingContext
{
protected String pageID = null;
protected Map<String,String> versions;
public WikiGetTimestampPageContext(XMLStream theStream, String namespaceURI, String localName, String qName, Attributes atts,
Map<String,String> versions)
{
super(theStream,namespaceURI,localName,qName,atts);
this.versions = versions;
}
protected XMLContext beginTag(String namespaceURI, String localName, String qName, Attributes atts)
throws ManifoldCFException, ServiceInterruption
{
if (qName.equals("page"))
{
pageID = atts.getValue("pageid");
return new WikiGetTimestampRevisionsContext(theStream,namespaceURI,localName,qName,atts);
}
return super.beginTag(namespaceURI,localName,qName,atts);
}
protected void endTag()
throws ManifoldCFException, ServiceInterruption
{
XMLContext theContext = theStream.getContext();
String theTag = theContext.getQname();
if (theTag.equals("page"))
{
String lastRevEdit = ((WikiGetTimestampRevisionsContext)theContext).getTimestamp();
versions.put(pageID,lastRevEdit);
}
else
super.endTag();
}
}
/** Class looking for the "api/query/pages/page/revisions" context of a "get timestamp" response */
protected static class WikiGetTimestampRevisionsContext extends SingleLevelContext
{
protected String timestamp = null;
public WikiGetTimestampRevisionsContext(XMLStream theStream, String namespaceURI, String localName, String qName, Attributes atts)
{
super(theStream,namespaceURI,localName,qName,atts,"revisions");
}
protected BaseProcessingContext createChild(String namespaceURI, String localName, String qName, Attributes atts)
{
return new WikiGetTimestampRevContext(theStream,namespaceURI,localName,qName,atts);
}
protected void finishChild(BaseProcessingContext child)
throws ManifoldCFException
{
WikiGetTimestampRevContext rc = (WikiGetTimestampRevContext)child;
if (timestamp == null)
timestamp = rc.getTimestamp();
}
public String getTimestamp()
{
return timestamp;
}
}
/** Class looking for the "api/query/pages/page/revisions/rev" context of a "get timestamp" response */
protected static class WikiGetTimestampRevContext extends BaseProcessingContext
{
protected String timestamp = null;
public WikiGetTimestampRevContext(XMLStream theStream, String namespaceURI, String localName, String qName, Attributes atts)
{
super(theStream,namespaceURI,localName,qName,atts);
}
protected XMLContext beginTag(String namespaceURI, String localName, String qName, Attributes atts)
throws ManifoldCFException, ServiceInterruption
{
if (qName.equals("rev"))
timestamp = atts.getValue("timestamp");
return super.beginTag(namespaceURI,localName,qName,atts);
}
public String getTimestamp()
{
return timestamp;
}
}
// -- Methods and classes to perform a "get namespaces" operation. --
/** Obtain the set of namespaces, as a map keyed by the canonical namespace name
* where the value is the descriptive name.
*/
protected void getNamespaces(Map<String,String> namespaces)
throws ManifoldCFException, ServiceInterruption
{
getSession();
boolean loginAttempted = false;
while (true)
{
HttpClient client = getInitializedClient();
HttpMethodBase executeMethod = getInitializedMethod(getGetNamespacesURL());
try
{
ExecuteGetNamespacesThread t = new ExecuteGetNamespacesThread(client,executeMethod,namespaces);
try
{
t.start();
t.join();
Throwable thr = t.getException();
if (thr != null)
{
if (thr instanceof ManifoldCFException)
{
if (((ManifoldCFException)thr).getErrorCode() == ManifoldCFException.INTERRUPTED)
throw new InterruptedException(thr.getMessage());
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
throw (Error)thr;
}
if (loginAttempted || !t.isLoginRequired())
return;
}
catch (ManifoldCFException e)
{
t.interrupt();
throw e;
}
catch (ServiceInterruption e)
{
t.interrupt();
throw e;
}
catch (IOException e)
{
t.interrupt();
throw e;
}
catch (InterruptedException e)
{
t.interrupt();
// We need the caller to abandon any connections left around, so rethrow in a way that forces them to process the event properly.
throw e;
}
}
catch (InterruptedException e)
{
// Drop the connection on the floor
executeMethod = null;
throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED);
}
catch (ManifoldCFException e)
{
if (e.getErrorCode() == ManifoldCFException.INTERRUPTED)
// Drop the connection on the floor
executeMethod = null;
throw e;
}
catch (java.net.SocketTimeoutException e)
{
long currentTime = System.currentTimeMillis();
throw new ServiceInterruption("Get namespaces timed out reading from the Wiki server: "+e.getMessage(),e,currentTime+300000L,currentTime+12L * 60000L,-1,false);
}
catch (java.net.SocketException e)
{
long currentTime = System.currentTimeMillis();
throw new ServiceInterruption("Get namespaces received a socket error reading from Wiki server: "+e.getMessage(),e,currentTime+300000L,currentTime+12L * 60000L,-1,false);
}
catch (org.apache.commons.httpclient.ConnectTimeoutException e)
{
long currentTime = System.currentTimeMillis();
throw new ServiceInterruption("Get namespaces connection timed out reading from Wiki server: "+e.getMessage(),e,currentTime+300000L,currentTime+12L * 60000L,-1,false);
}
catch (InterruptedIOException e)
{
executeMethod = null;
throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED);
}
catch (IOException e)
{
throw new ManifoldCFException("Get namespaces had an IO failure: "+e.getMessage(),e);
}
finally
{
if (executeMethod != null)
executeMethod.releaseConnection();
}
if (!loginToAPI())
break;
loginAttempted = true;
}
}
/** Thread to execute a "get namespaces" operation. This thread both executes the operation and parses the result. */
protected static class ExecuteGetNamespacesThread extends Thread
{
protected HttpClient client;
protected HttpMethodBase executeMethod;
protected Throwable exception = null;
protected Map<String,String> namespaces;
protected boolean loginNeeded = false;
public ExecuteGetNamespacesThread(HttpClient client, HttpMethodBase executeMethod, Map<String,String> namespaces)
{
super();
setDaemon(true);
this.client = client;
this.executeMethod = executeMethod;
this.namespaces = namespaces;
}
public void run()
{
try
{
// Call the execute method appropriately
int rval = client.executeMethod(executeMethod);
if (rval != 200)
{
throw new ManifoldCFException("Unexpected response code "+rval+": "+executeMethod.getResponseBodyAsString());
}
// Read response and make sure it's valid
InputStream is = executeMethod.getResponseBodyAsStream();
try
{
// Parse the document. This will cause various things to occur, within the instantiated XMLContext class.
//<api>
// <query>
// <namespaces>
// <ns id="-2" case="first-letter" canonical="Media" xml:space="preserve">Media</ns>
// <ns id="-1" case="first-letter" canonical="Special" xml:space="preserve">Special</ns>
// <ns id="0" case="first-letter" subpages="" content="" xml:space="preserve" />
// <ns id="1" case="first-letter" subpages="" canonical="Talk" xml:space="preserve">Talk</ns>
// <ns id="2" case="first-letter" subpages="" canonical="User" xml:space="preserve">User</ns>
// <ns id="90" case="first-letter" canonical="Thread" xml:space="preserve">Thread</ns>
// <ns id="91" case="first-letter" canonical="Thread talk" xml:space="preserve">Thread talk</ns>
// </namespaces>
// </query>
//</api>
XMLStream x = new XMLStream();
WikiGetNamespacesAPIContext c = new WikiGetNamespacesAPIContext(x,namespaces);
x.setContext(c);
try
{
try
{
x.parse(is);
loginNeeded = c.isLoginRequired();
}
catch (IOException e)
{
long time = System.currentTimeMillis();
throw new ServiceInterruption(e.getMessage(),e,time + 300000L,time + 12L * 60000L,-1,false);
}
}
finally
{
x.cleanup();
}
}
finally
{
try
{
is.close();
}
catch (IllegalStateException e)
{
// Ignore this error
}
}
}
catch (Throwable e)
{
this.exception = e;
}
}
public Throwable getException()
{
return exception;
}
public boolean isLoginRequired()
{
return loginNeeded;
}
}
/** Create a URL to obtain the namespaces.
*/
protected String getGetNamespacesURL()
throws ManifoldCFException
{
return baseURL + "action=query&meta=siteinfo&siprop=namespaces";
}
/** Class representing the "api" context of a "get namespaces" response */
protected static class WikiGetNamespacesAPIContext extends SingleLevelContext
{
protected Map<String,String> namespaces;
protected boolean loginNeeded = false;
public WikiGetNamespacesAPIContext(XMLStream theStream, Map<String,String> namespaces)
{
super(theStream,"api");
this.namespaces = namespaces;
}
protected BaseProcessingContext createChild(String namespaceURI, String localName, String qName, Attributes atts)
{
return new WikiGetNamespacesQueryContext(theStream,namespaceURI,localName,qName,atts,namespaces);
}
protected void finishChild(BaseProcessingContext child)
throws ManifoldCFException
{
loginNeeded |= ((WikiGetNamespacesQueryContext)child).isLoginRequired();
}
public boolean isLoginRequired()
{
return loginNeeded;
}
}
/** Class representing the "api/query" context of a "get namespaces" response */
protected static class WikiGetNamespacesQueryContext extends SingleLevelErrorContext
{
protected Map<String,String> namespaces;
public WikiGetNamespacesQueryContext(XMLStream theStream, String namespaceURI, String localName, String qName, Attributes atts,
Map<String,String> namespaces)
{
super(theStream,namespaceURI,localName,qName,atts,"query");
this.namespaces = namespaces;
}
protected BaseProcessingContext createChild(String namespaceURI, String localName, String qName, Attributes atts)
{
return new WikiGetNamespacesNamespacesContext(theStream,namespaceURI,localName,qName,atts,namespaces);
}
protected void finishChild(BaseProcessingContext child)
throws ManifoldCFException
{
}
}
/** Class representing the "api/query/namespaces" context of a "get namespaces" response */
protected static class WikiGetNamespacesNamespacesContext extends SingleLevelContext
{
protected Map<String,String> namespaces;
public WikiGetNamespacesNamespacesContext(XMLStream theStream, String namespaceURI, String localName, String qName, Attributes atts,
Map<String,String> namespaces)
{
super(theStream,namespaceURI,localName,qName,atts,"namespaces");
this.namespaces = namespaces;
}
protected BaseProcessingContext createChild(String namespaceURI, String localName, String qName, Attributes atts)
{
return new WikiGetNamespacesNsContext(theStream,namespaceURI,localName,qName,atts,namespaces);
}
protected void finishChild(BaseProcessingContext child)
throws ManifoldCFException
{
}
}
/** Class representing the "api/query/pages/page" context of a "get doc info" response */
protected static class WikiGetNamespacesNsContext extends BaseProcessingContext
{
protected Map<String,String> namespaces;
protected String canonical = null;
protected String nsid = null;
public WikiGetNamespacesNsContext(XMLStream theStream, String namespaceURI, String localName, String qName, Attributes atts,
Map<String,String> namespaces)
{
super(theStream,namespaceURI,localName,qName,atts);
this.namespaces = namespaces;
}
protected XMLContext beginTag(String namespaceURI, String localName, String qName, Attributes atts)
throws ManifoldCFException, ServiceInterruption
{
if (qName.equals("ns"))
{
nsid = atts.getValue("id");
canonical = atts.getValue("canonical");
if (canonical != null && nsid != null)
return new XMLStringContext(theStream,namespaceURI,localName,qName,atts);
}
return super.beginTag(namespaceURI,localName,qName,atts);
}
protected void endTag()
throws ManifoldCFException, ServiceInterruption
{
XMLContext theContext = theStream.getContext();
String theTag = theContext.getQname();
if (theTag.equals("ns"))
{
if (canonical != null && nsid != null)
{
// Pull down the data
XMLStringContext sc = (XMLStringContext)theContext;
namespaces.put(sc.getValue(),nsid);
}
else
super.endTag();
}
else
super.endTag();
}
protected void tagCleanup()
throws ManifoldCFException
{
}
}
// -- Methods and classes to perform a "get Docinfo" operation. --
/** Get document info and index the document.
*/
protected void getDocInfo(String documentIdentifier, String documentVersion, String fullURL, IProcessActivity activities, String[] allowACL)
throws ManifoldCFException, ServiceInterruption
{
getSession();
boolean loginAttempted = false;
while (true)
{
HttpClient client = getInitializedClient();
HttpMethodBase executeMethod = getInitializedMethod(getGetDocInfoURL(documentIdentifier));
String statusCode = "UNKNOWN";
String errorMessage = null;
long startTime = System.currentTimeMillis();
long dataSize = 0L;
try
{
ExecuteGetDocInfoThread t = new ExecuteGetDocInfoThread(client,executeMethod,documentIdentifier);
try
{
t.start();
t.join();
statusCode = t.getStatusCode();
errorMessage = t.getErrorMessage();
Throwable thr = t.getException();
if (thr != null)
{
if (thr instanceof ManifoldCFException)
{
if (((ManifoldCFException)thr).getErrorCode() == ManifoldCFException.INTERRUPTED)
throw new InterruptedException(thr.getMessage());
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
throw (Error)thr;
}
// Fetch all the data we need from the thread, and do the indexing.
File contentFile = t.getContentFile();
if (contentFile != null)
{
statusCode = "OK";
try
{
String author = t.getAuthor();
String comment = t.getComment();
String title = t.getTitle();
String lastModified = t.getLastModified();
RepositoryDocument rd = new RepositoryDocument();
dataSize = contentFile.length();
InputStream is = new FileInputStream(contentFile);
try
{
rd.setBinary(is,dataSize);
if (comment != null)
rd.addField("comment",comment);
if (author != null)
rd.addField("author",author);
if (title != null)
rd.addField("title",title);
if (lastModified != null)
rd.addField("last-modified",lastModified);
if (allowACL != null && allowACL.length > 0) {
String[] denyACL = new String[]{
defaultAuthorityDenyToken
};
rd.setDenyACL(denyACL);
rd.setACL(allowACL);
}
activities.ingestDocument(documentIdentifier,documentVersion,fullURL,rd);
}
finally
{
is.close();
}
}
finally
{
contentFile.delete();
}
}
if (loginAttempted || !t.isLoginRequired())
return;
}
catch (ManifoldCFException e)
{
t.interrupt();
throw e;
}
catch (ServiceInterruption e)
{
t.interrupt();
throw e;
}
catch (IOException e)
{
t.interrupt();
throw e;
}
catch (InterruptedException e)
{
t.interrupt();
// We need the caller to abandon any connections left around, so rethrow in a way that forces them to process the event properly.
throw e;
}
finally
{
t.cleanup();
}
}
catch (InterruptedException e)
{
// Drop the connection on the floor
executeMethod = null;
statusCode = null;
throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED);
}
catch (ManifoldCFException e)
{
if (e.getErrorCode() == ManifoldCFException.INTERRUPTED)
{
// Drop the connection on the floor
executeMethod = null;
statusCode = null;
}
throw e;
}
catch (java.net.SocketTimeoutException e)
{
long currentTime = System.currentTimeMillis();
throw new ServiceInterruption("Get doc info timed out reading from the Wiki server: "+e.getMessage(),e,currentTime+300000L,currentTime+12L * 60000L,-1,false);
}
catch (java.net.SocketException e)
{
long currentTime = System.currentTimeMillis();
throw new ServiceInterruption("Get doc info received a socket error reading from Wiki server: "+e.getMessage(),e,currentTime+300000L,currentTime+12L * 60000L,-1,false);
}
catch (org.apache.commons.httpclient.ConnectTimeoutException e)
{
long currentTime = System.currentTimeMillis();
throw new ServiceInterruption("Get doc info connection timed out reading from Wiki server: "+e.getMessage(),e,currentTime+300000L,currentTime+12L * 60000L,-1,false);
}
catch (InterruptedIOException e)
{
executeMethod = null;
statusCode = null;
throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED);
}
catch (IOException e)
{
throw new ManifoldCFException("Get doc info had an IO failure: "+e.getMessage(),e);
}
finally
{
if (executeMethod != null)
executeMethod.releaseConnection();
if (statusCode != null)
activities.recordActivity(new Long(startTime),ACTIVITY_FETCH,new Long(dataSize),documentIdentifier,statusCode,errorMessage,null);
}
if (!loginToAPI())
break;
loginAttempted = true;
}
}
/** Thread to execute a "get doc info" operation. This thread both executes the operation and parses the result. */
protected static class ExecuteGetDocInfoThread extends Thread
{
protected HttpClient client;
protected HttpMethodBase executeMethod;
protected Throwable exception = null;
protected String documentIdentifier;
protected File contentFile = null;
protected String author = null;
protected String title = null;
protected String comment = null;
protected String lastModified = null;
protected String statusCode = null;
protected String errorMessage = null;
protected boolean loginNeeded = false;
public ExecuteGetDocInfoThread(HttpClient client, HttpMethodBase executeMethod, String documentIdentifier)
{
super();
setDaemon(true);
this.client = client;
this.executeMethod = executeMethod;
this.documentIdentifier = documentIdentifier;
}
public void run()
{
try
{
// Call the execute method appropriately
int rval = client.executeMethod(executeMethod);
if (rval != 200)
{
statusCode = "HTTP code "+rval;
throw new ManifoldCFException("Unexpected response code "+rval+": "+executeMethod.getResponseBodyAsString());
}
// Read response and make sure it's valid
InputStream is = executeMethod.getResponseBodyAsStream();
try
{
// Parse the document. This will cause various things to occur, within the instantiated XMLContext class.
// <api>
// <query>
// <pages>
// <page pageid="27697087" ns="0" title="API" touched="2011-09-27T07:00:55Z" lastrevid="367741756" counter="" length="70" redirect="" fullurl="http://en.wikipedia.org/wiki/API" editurl="http://en.wikipedia.org/w/index.php?title=API&amp;action=edit">
// <revisions>
// <rev user="Graham87" timestamp="2010-06-13T08:41:17Z" comment="Protected API: restore protection ([edit=sysop] (indefinite) [move=sysop] (indefinite))" xml:space="preserve">#REDIRECT [[Application programming interface]]{{R from abbreviation}}</rev>
// </revisions>
// </page>
// </pages>
// </query>
//</api>
XMLStream x = new XMLStream();
WikiGetDocInfoAPIContext c = new WikiGetDocInfoAPIContext(x);
x.setContext(c);
try
{
try
{
x.parse(is);
contentFile = c.getContentFile();
title = c.getTitle();
author = c.getAuthor();
comment = c.getComment();
lastModified = c.getLastModified();
statusCode = "OK";
loginNeeded = c.isLoginRequired();
}
catch (IOException e)
{
long time = System.currentTimeMillis();
throw new ServiceInterruption(e.getMessage(),e,time + 300000L,time + 12L * 60000L,-1,false);
}
}
finally
{
x.cleanup();
}
}
finally
{
try
{
is.close();
}
catch (IllegalStateException e)
{
// Ignore this error
}
}
}
catch (Throwable e)
{
statusCode = "Exception";
errorMessage = e.getMessage();
this.exception = e;
}
}
public Throwable getException()
{
return exception;
}
public String getStatusCode()
{
return statusCode;
}
public String getErrorMessage()
{
return errorMessage;
}
public File getContentFile()
{
File rval = contentFile;
contentFile = null;
return rval;
}
public String getAuthor()
{
return author;
}
public String getComment()
{
return comment;
}
public String getTitle()
{
return title;
}
public String getLastModified()
{
return lastModified;
}
public boolean isLoginRequired()
{
return loginNeeded;
}
public void cleanup()
{
if (contentFile != null)
{
contentFile.delete();
contentFile = null;
}
}
}
/** Create a URL to obtain a page's metadata and content, given the page ID.
* QUESTION: Can we do multiple document identifiers at a time??
*/
protected String getGetDocInfoURL(String documentIdentifier)
throws ManifoldCFException
{
return baseURL + "action=query&prop=revisions&pageids="+documentIdentifier+"&rvprop=user%7ccomment%7ccontent%7ctimestamp";
}
/** Class representing the "api" context of a "get doc info" response */
protected static class WikiGetDocInfoAPIContext extends SingleLevelContext
{
/** Title */
protected String title = null;
/** Content file */
protected File contentFile = null;
/** Author */
protected String author = null;
/** Comment */
protected String comment = null;
/** Last modified */
protected String lastModified = null;
protected boolean loginNeeded = false;
public WikiGetDocInfoAPIContext(XMLStream theStream)
{
super(theStream,"api");
}
protected BaseProcessingContext createChild(String namespaceURI, String localName, String qName, Attributes atts)
{
return new WikiGetDocInfoQueryContext(theStream,namespaceURI,localName,qName,atts);
}
protected void finishChild(BaseProcessingContext child)
throws ManifoldCFException
{
WikiGetDocInfoQueryContext pc = (WikiGetDocInfoQueryContext)child;
tagCleanup();
title = pc.getTitle();
contentFile = pc.getContentFile();
author = pc.getAuthor();
comment = pc.getComment();
lastModified = pc.getLastModified();
loginNeeded |= pc.isLoginRequired();
}
protected void tagCleanup()
throws ManifoldCFException
{
// Delete the contents file if it is there.
if (contentFile != null)
{
contentFile.delete();
contentFile = null;
}
}
public String getTitle()
{
return title;
}
public File getContentFile()
{
File rval = contentFile;
contentFile = null;
return rval;
}
public String getAuthor()
{
return author;
}
public String getLastModified()
{
return lastModified;
}
public String getComment()
{
return comment;
}
public boolean isLoginRequired()
{
return loginNeeded;
}
}
/** Class representing the "api/query" context of a "get doc info" response */
protected static class WikiGetDocInfoQueryContext extends SingleLevelErrorContext
{
/** Title */
protected String title = null;
/** Content file */
protected File contentFile = null;
/** Author */
protected String author = null;
/** Comment */
protected String comment = null;
/** Last modified */
protected String lastModified = null;
public WikiGetDocInfoQueryContext(XMLStream theStream, String namespaceURI, String localName, String qName, Attributes atts)
{
super(theStream,namespaceURI,localName,qName,atts,"query");
}
protected BaseProcessingContext createChild(String namespaceURI, String localName, String qName, Attributes atts)
{
return new WikiGetDocInfoPagesContext(theStream,namespaceURI,localName,qName,atts);
}
protected void finishChild(BaseProcessingContext child)
throws ManifoldCFException
{
WikiGetDocInfoPagesContext pc = (WikiGetDocInfoPagesContext)child;
tagCleanup();
title = pc.getTitle();
contentFile = pc.getContentFile();
author = pc.getAuthor();
comment = pc.getComment();
lastModified = pc.getLastModified();
}
protected void tagCleanup()
throws ManifoldCFException
{
// Delete the contents file if it is there.
if (contentFile != null)
{
contentFile.delete();
contentFile = null;
}
}
public String getTitle()
{
return title;
}
public File getContentFile()
{
File rval = contentFile;
contentFile = null;
return rval;
}
public String getAuthor()
{
return author;
}
public String getLastModified()
{
return lastModified;
}
public String getComment()
{
return comment;
}
}
/** Class representing the "api/query/pages" context of a "get doc info" response */
protected static class WikiGetDocInfoPagesContext extends SingleLevelContext
{
/** Title */
protected String title = null;
/** Content file */
protected File contentFile = null;
/** Author */
protected String author = null;
/** Comment */
protected String comment = null;
/** Last modified */
protected String lastModified = null;
public WikiGetDocInfoPagesContext(XMLStream theStream, String namespaceURI, String localName, String qName, Attributes atts)
{
super(theStream,namespaceURI,localName,qName,atts,"pages");
}
protected BaseProcessingContext createChild(String namespaceURI, String localName, String qName, Attributes atts)
{
return new WikiGetDocInfoPageContext(theStream,namespaceURI,localName,qName,atts);
}
protected void finishChild(BaseProcessingContext child)
throws ManifoldCFException
{
WikiGetDocInfoPageContext pc = (WikiGetDocInfoPageContext)child;
tagCleanup();
title = pc.getTitle();
contentFile = pc.getContentFile();
author = pc.getAuthor();
lastModified = pc.getLastModified();
comment = pc.getComment();
}
protected void tagCleanup()
throws ManifoldCFException
{
// Delete the contents file if it is there.
if (contentFile != null)
{
contentFile.delete();
contentFile = null;
}
}
public String getTitle()
{
return title;
}
public File getContentFile()
{
File rval = contentFile;
contentFile = null;
return rval;
}
public String getAuthor()
{
return author;
}
public String getLastModified()
{
return lastModified;
}
public String getComment()
{
return comment;
}
}
/** Class representing the "api/query/pages/page" context of a "get doc info" response */
protected static class WikiGetDocInfoPageContext extends BaseProcessingContext
{
/** Title */
protected String title = null;
/** Content file */
protected File contentFile = null;
/** Author */
protected String author = null;
/** Comment */
protected String comment = null;
/** Last modified */
protected String lastModified = null;
public WikiGetDocInfoPageContext(XMLStream theStream, String namespaceURI, String localName, String qName, Attributes atts)
{
super(theStream,namespaceURI,localName,qName,atts);
}
protected XMLContext beginTag(String namespaceURI, String localName, String qName, Attributes atts)
throws ManifoldCFException, ServiceInterruption
{
if (qName.equals("page"))
{
title = atts.getValue("title");
return new WikiGetDocInfoRevisionsContext(theStream,namespaceURI,localName,qName,atts);
}
return super.beginTag(namespaceURI,localName,qName,atts);
}
protected void endTag()
throws ManifoldCFException, ServiceInterruption
{
XMLContext theContext = theStream.getContext();
String theTag = theContext.getQname();
if (theTag.equals("page"))
{
// Pull down the data
WikiGetDocInfoRevisionsContext rc = (WikiGetDocInfoRevisionsContext)theContext;
tagCleanup();
contentFile = rc.getContentFile();
author = rc.getAuthor();
comment = rc.getComment();
lastModified = rc.getLastModified();
}
super.endTag();
}
protected void tagCleanup()
throws ManifoldCFException
{
// Delete the contents file if it is there.
if (contentFile != null)
{
contentFile.delete();
contentFile = null;
}
}
public String getTitle()
{
return title;
}
public File getContentFile()
{
File rval = contentFile;
contentFile = null;
return rval;
}
public String getAuthor()
{
return author;
}
public String getComment()
{
return comment;
}
public String getLastModified()
{
return lastModified;
}
}
/** Class representing the "api/query/pages/page/revisions" context of a "get doc info" response */
protected static class WikiGetDocInfoRevisionsContext extends SingleLevelContext
{
protected File contentFile = null;
protected String author = null;
protected String comment = null;
protected String lastModified = null;
public WikiGetDocInfoRevisionsContext(XMLStream theStream, String namespaceURI, String localName, String qName, Attributes atts)
{
super(theStream,namespaceURI,localName,qName,atts,"revisions");
}
protected BaseProcessingContext createChild(String namespaceURI, String localName, String qName, Attributes atts)
{
return new WikiGetDocInfoRevContext(theStream,namespaceURI,localName,qName,atts);
}
protected void finishChild(BaseProcessingContext child)
throws ManifoldCFException
{
WikiGetDocInfoRevContext rc = (WikiGetDocInfoRevContext)child;
tagCleanup();
contentFile = rc.getContentFile();
author = rc.getAuthor();
comment = rc.getComment();
lastModified = rc.getLastModified();
}
protected void tagCleanup()
throws ManifoldCFException
{
// Delete the contents file if it is there.
if (contentFile != null)
{
contentFile.delete();
contentFile = null;
}
}
public File getContentFile()
{
File rval = contentFile;
contentFile = null;
return rval;
}
public String getAuthor()
{
return author;
}
public String getComment()
{
return comment;
}
public String getLastModified()
{
return lastModified;
}
}
/** Class looking for the "api/query/pages/page/revisions/rev" context of a "get doc info" response */
protected static class WikiGetDocInfoRevContext extends BaseProcessingContext
{
protected String author = null;
protected String comment = null;
protected File contentFile = null;
protected String lastModified = null;
public WikiGetDocInfoRevContext(XMLStream theStream, String namespaceURI, String localName, String qName, Attributes atts)
{
super(theStream,namespaceURI,localName,qName,atts);
}
protected XMLContext beginTag(String namespaceURI, String localName, String qName, Attributes atts)
throws ManifoldCFException, ServiceInterruption
{
if (qName.equals("rev"))
{
author = atts.getValue("user");
comment = atts.getValue("comment");
lastModified = atts.getValue("timestamp");
try
{
File tempFile = File.createTempFile("_wikidata_","tmp");
return new XMLFileContext(theStream,namespaceURI,localName,qName,atts,tempFile);
}
catch (java.net.SocketTimeoutException e)
{
throw new ManifoldCFException("IO exception creating temp file: "+e.getMessage(),e);
}
catch (InterruptedIOException e)
{
throw new ManifoldCFException("Interrupted: "+e.getMessage(),e,ManifoldCFException.INTERRUPTED);
}
catch (IOException e)
{
throw new ManifoldCFException("IO exception creating temp file: "+e.getMessage(),e);
}
}
return super.beginTag(namespaceURI,localName,qName,atts);
}
protected void endTag()
throws ManifoldCFException, ServiceInterruption
{
XMLContext theContext = theStream.getContext();
String theTag = theContext.getQname();
if (theTag.equals("rev"))
{
// Pull down the data
XMLFileContext rc = (XMLFileContext)theContext;
tagCleanup();
contentFile = rc.getCompletedFile();
}
else
super.endTag();
}
protected void tagCleanup()
throws ManifoldCFException
{
// Delete the contents file if it is there.
if (contentFile != null)
{
contentFile.delete();
contentFile = null;
}
}
public String getAuthor()
{
return author;
}
public String getLastModified()
{
return lastModified;
}
public String getComment()
{
return comment;
}
public File getContentFile()
{
File rval = contentFile;
contentFile = null;
return rval;
}
}
}