CONNECTORS-1161:Committing GSoC Project Result
git-svn-id: https://svn.apache.org/repos/asf/manifoldcf/branches/CONNECTORS-1161@1695542 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/connectors/confluence/build.xml b/connectors/confluence/build.xml
new file mode 100644
index 0000000..44ce338
--- /dev/null
+++ b/connectors/confluence/build.xml
@@ -0,0 +1,44 @@
+<!--
+ 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.
+-->
+
+<project name="confluence" default="all">
+
+ <property environment="env"/>
+ <condition property="mcf-dist" value="${env.MCFDISTPATH}">
+ <isset property="env.MCFDISTPATH"/>
+ </condition>
+ <property name="abs-dist" location="../../dist"/>
+ <condition property="mcf-dist" value="${abs-dist}">
+ <not>
+ <isset property="env.MCFDISTPATH"/>
+ </not>
+ </condition>
+
+ <import file="${mcf-dist}/connector-build.xml"/>
+
+ <target name="deliver-connector" depends="mcf-connector-build.deliver-connector">
+ <antcall target="general-add-repository-connector">
+ <param name="connector-label" value="Confluence"/>
+ <param name="connector-class" value="org.apache.manifoldcf.crawler.connectors.confluence.ConfluenceRepositoryConnector"/>
+ </antcall>
+ <antcall target="general-add-authority-connector">
+ <param name="connector-label" value="Confluence"/>
+ <param name="connector-class" value="org.apache.manifoldcf.authorities.authorities.confluence.ConfluenceAuthorityConnector"/>
+ </antcall>
+ </target>
+
+</project>
diff --git a/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/authorities/authorities/confluence/ConfluenceAuthorityConnector.java b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/authorities/authorities/confluence/ConfluenceAuthorityConnector.java
new file mode 100644
index 0000000..66b4a53
--- /dev/null
+++ b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/authorities/authorities/confluence/ConfluenceAuthorityConnector.java
@@ -0,0 +1,438 @@
+package org.apache.manifoldcf.authorities.authorities.confluence;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.manifoldcf.agents.interfaces.ServiceInterruption;
+import org.apache.manifoldcf.authorities.authorities.BaseAuthorityConnector;
+import org.apache.manifoldcf.authorities.interfaces.AuthorizationResponse;
+import org.apache.manifoldcf.core.interfaces.ConfigParams;
+import org.apache.manifoldcf.core.interfaces.IHTTPOutput;
+import org.apache.manifoldcf.core.interfaces.IPasswordMapperActivity;
+import org.apache.manifoldcf.core.interfaces.IPostParameters;
+import org.apache.manifoldcf.core.interfaces.IThreadContext;
+import org.apache.manifoldcf.core.interfaces.ManifoldCFException;
+import org.apache.manifoldcf.crawler.connectors.confluence.ConfluenceConfiguration;
+import org.apache.manifoldcf.crawler.connectors.confluence.client.ConfluenceClient;
+import org.apache.manifoldcf.crawler.connectors.confluence.model.ConfluenceUser;
+import org.apache.manifoldcf.crawler.system.Logging;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * <p>
+ * Confluence Authority Connector class
+ * </p>
+ * <p>
+ * ManifoldCF Authority connector to deal with Confluence documents
+ * </p>
+ *
+ * @author Antonio David Perez Morales <adperezmorales@gmail.com>
+ *
+ */
+public class ConfluenceAuthorityConnector extends BaseAuthorityConnector {
+
+ /*
+ * Prefix for Confluence configuration and specification parameters
+ */
+ private static final String PARAMETER_PREFIX = "confluence_";
+
+ /* Configuration tabs */
+ private static final String CONF_SERVER_TAB_PROPERTY = "ConfluenceAuthorityConnector.Server";
+
+ // pages & js
+ // Template names for Confluence configuration
+ /**
+ * Forward to the javascript to check the configuration parameters
+ */
+ private static final String EDIT_CONFIG_HEADER_FORWARD = "editConfiguration_conf.js";
+ /**
+ * Server tab template
+ */
+ private static final String EDIT_CONFIG_FORWARD_SERVER = "editConfiguration_conf_server.html";
+
+ /**
+ * Forward to the HTML template to view the configuration parameters
+ */
+ private static final String VIEW_CONFIG_FORWARD = "viewConfiguration_conf.html";
+
+
+ private Logger logger = LoggerFactory
+ .getLogger(ConfluenceAuthorityConnector.class);
+
+ /* Confluence instance parameters */
+ protected String protocol = null;
+ protected String host = null;
+ protected String port = null;
+ protected String path = null;
+ protected String username = null;
+ protected String password = null;
+
+ protected ConfluenceClient confluenceClient = null;
+
+ /**
+ * <p>
+ * Default constructor
+ * </p>
+ */
+ public ConfluenceAuthorityConnector() {
+ super();
+ }
+
+ /**
+ * Used Mainly for testing
+ *
+ * @param client Injected Confluence Client
+ */
+ public void setConfluenceClient(ConfluenceClient client){
+ this.confluenceClient = client;
+ }
+
+
+
+ /**
+ * Close the connection. Call this before discarding the connection.
+ */
+ @Override
+ public void disconnect() throws ManifoldCFException {
+ if (confluenceClient != null) {
+ confluenceClient = null;
+ }
+
+ protocol = null;
+ host = null;
+ port = null;
+ path = null;
+ username = null;
+ password = null;
+
+ }
+
+ /**
+ * Makes connection to server
+ *
+ *
+ */
+ @Override
+ public void connect(ConfigParams configParams) {
+ super.connect(configParams);
+
+ protocol = params.getParameter(ConfluenceConfiguration.Server.PROTOCOL);
+ host = params.getParameter(ConfluenceConfiguration.Server.HOST);
+ port = params.getParameter(ConfluenceConfiguration.Server.PORT);
+ path = params.getParameter(ConfluenceConfiguration.Server.PATH);
+ username = params.getParameter(ConfluenceConfiguration.Server.USERNAME);
+ password = params
+ .getObfuscatedParameter(ConfluenceConfiguration.Server.PASSWORD);
+
+ try {
+ initConfluenceClient();
+ } catch (ManifoldCFException e) {
+ logger.debug(
+ "Not possible to initialize Confluence client. Reason: {}",
+ e.getMessage());
+ }
+ }
+
+ /**
+ * Checks if connection is available
+ */
+ @Override
+ public String check() throws ManifoldCFException {
+ try {
+ if (!isConnected()) {
+ initConfluenceClient();
+ }
+ Boolean result = confluenceClient.checkAuth();
+ if (result)
+ return super.check();
+ else
+ throw new ManifoldCFException(
+ "Confluence instance could not be reached");
+ } catch (ServiceInterruption e) {
+ return "Connection temporarily failed: " + e.getMessage();
+ } catch (ManifoldCFException e) {
+ return "Connection failed: " + e.getMessage();
+ } catch (Exception e) {
+ return "Connection failed: " + e.getMessage();
+ }
+ }
+
+ /**
+ * <p>
+ * Initialize Confluence client using the configured parameters
+ *
+ * @throws ManifoldCFException
+ */
+ protected void initConfluenceClient() throws ManifoldCFException {
+ if (confluenceClient == null) {
+
+ if (StringUtils.isEmpty(protocol)) {
+ throw new ManifoldCFException("Parameter "
+ + ConfluenceConfiguration.Server.PROTOCOL
+ + " required but not set");
+ }
+
+ if (Logging.connectors.isDebugEnabled()) {
+ Logging.connectors.debug("Confluence protocol = '" + protocol
+ + "'");
+ }
+
+ if (StringUtils.isEmpty(host)) {
+ throw new ManifoldCFException("Parameter "
+ + ConfluenceConfiguration.Server.HOST
+ + " required but not set");
+ }
+
+ if (Logging.connectors.isDebugEnabled()) {
+ Logging.connectors.debug("Confluence host = '" + host + "'");
+ }
+
+ if (Logging.connectors.isDebugEnabled()) {
+ Logging.connectors.debug("Confluence port = '" + port + "'");
+ }
+
+ if (StringUtils.isEmpty(path)) {
+ throw new ManifoldCFException("Parameter "
+ + ConfluenceConfiguration.Server.PATH
+ + " required but not set");
+ }
+
+ if (Logging.connectors.isDebugEnabled()) {
+ Logging.connectors.debug("Confluence path = '" + path + "'");
+ }
+
+ if (Logging.connectors.isDebugEnabled()) {
+ Logging.connectors.debug("Confluence username = '" + username
+ + "'");
+ }
+
+ if (Logging.connectors.isDebugEnabled()) {
+ Logging.connectors
+ .debug("Confluence password '" + password != null ? "set"
+ : "not set" + "'");
+ }
+
+ int portInt;
+ if (port != null && port.length() > 0) {
+ try {
+ portInt = Integer.parseInt(port);
+ } catch (NumberFormatException e) {
+ throw new ManifoldCFException("Bad number: "
+ + e.getMessage(), e);
+ }
+ } else {
+ if (protocol.toLowerCase(Locale.ROOT).equals("http"))
+ portInt = 80;
+ else
+ portInt = 443;
+ }
+
+ /* Generating a client to perform Confluence requests */
+ confluenceClient = new ConfluenceClient(protocol, host, portInt,
+ path, username, password);
+ }
+
+ }
+
+ /**
+ * This method is called to assess whether to count this connector instance
+ * should actually be counted as being connected.
+ *
+ * @return true if the connector instance is actually connected.
+ */
+ @Override
+ public boolean isConnected() {
+ return confluenceClient != null;
+ }
+
+
+ private void fillInServerConfigurationMap(Map<String, String> serverMap,
+ IPasswordMapperActivity mapper, ConfigParams parameters) {
+ String confluenceProtocol = parameters
+ .getParameter(ConfluenceConfiguration.Server.PROTOCOL);
+ String confluenceHost = parameters
+ .getParameter(ConfluenceConfiguration.Server.HOST);
+ String confluencePort = parameters
+ .getParameter(ConfluenceConfiguration.Server.PORT);
+ String confluencePath = parameters
+ .getParameter(ConfluenceConfiguration.Server.PATH);
+ String confluenceUsername = parameters
+ .getParameter(ConfluenceConfiguration.Server.USERNAME);
+ String confluencePassword = parameters
+ .getObfuscatedParameter(ConfluenceConfiguration.Server.PASSWORD);
+
+ if (confluenceProtocol == null)
+ confluenceProtocol = ConfluenceConfiguration.Server.PROTOCOL_DEFAULT_VALUE;
+ if (confluenceHost == null)
+ confluenceHost = ConfluenceConfiguration.Server.HOST_DEFAULT_VALUE;
+ if (confluencePort == null)
+ confluencePort = ConfluenceConfiguration.Server.PORT_DEFAULT_VALUE;
+ if (confluencePath == null)
+ confluencePath = ConfluenceConfiguration.Server.PATH_DEFAULT_VALUE;
+
+ if (confluenceUsername == null)
+ confluenceUsername = ConfluenceConfiguration.Server.USERNAME_DEFAULT_VALUE;
+ if (confluencePassword == null)
+ confluencePassword = ConfluenceConfiguration.Server.PASSWORD_DEFAULT_VALUE;
+ else
+ confluencePassword = mapper.mapPasswordToKey(confluencePassword);
+
+ serverMap.put(PARAMETER_PREFIX
+ + ConfluenceConfiguration.Server.PROTOCOL, confluenceProtocol);
+ serverMap.put(PARAMETER_PREFIX + ConfluenceConfiguration.Server.HOST,
+ confluenceHost);
+ serverMap.put(PARAMETER_PREFIX + ConfluenceConfiguration.Server.PORT,
+ confluencePort);
+ serverMap.put(PARAMETER_PREFIX + ConfluenceConfiguration.Server.PATH,
+ confluencePath);
+ serverMap.put(PARAMETER_PREFIX
+ + ConfluenceConfiguration.Server.USERNAME, confluenceUsername);
+ serverMap.put(PARAMETER_PREFIX
+ + ConfluenceConfiguration.Server.PASSWORD, confluencePassword);
+ }
+
+ @Override
+ public void viewConfiguration(IThreadContext threadContext,
+ IHTTPOutput out, Locale locale, ConfigParams parameters)
+ throws ManifoldCFException, IOException {
+ Map<String, String> paramMap = new HashMap<String, String>();
+
+ /* Fill server configuration parameters */
+ fillInServerConfigurationMap(paramMap, out, parameters);
+
+ Messages.outputResourceWithVelocity(out, locale, VIEW_CONFIG_FORWARD,
+ paramMap, true);
+ }
+
+ @Override
+ public void outputConfigurationHeader(IThreadContext threadContext,
+ IHTTPOutput out, Locale locale, ConfigParams parameters,
+ List<String> tabsArray) throws ManifoldCFException, IOException {
+ // Add the Server tab
+ tabsArray.add(Messages.getString(locale, CONF_SERVER_TAB_PROPERTY));
+ // Map the parameters
+ Map<String, String> paramMap = new HashMap<String, String>();
+
+ /* Fill server configuration parameters */
+ fillInServerConfigurationMap(paramMap, out, parameters);
+
+ // Output the Javascript - only one Velocity template for all tabs
+ Messages.outputResourceWithVelocity(out, locale,
+ EDIT_CONFIG_HEADER_FORWARD, paramMap, true);
+ }
+
+ @Override
+ public void outputConfigurationBody(IThreadContext threadContext,
+ IHTTPOutput out, Locale locale, ConfigParams parameters,
+ String tabName) throws ManifoldCFException, IOException {
+
+ // Call the Velocity templates for each tab
+ Map<String, String> paramMap = new HashMap<String, String>();
+ // Set the tab name
+ paramMap.put("TabName", tabName);
+
+ // Fill in the parameters
+ fillInServerConfigurationMap(paramMap, out, parameters);
+
+ // Server tab
+ Messages.outputResourceWithVelocity(out, locale,
+ EDIT_CONFIG_FORWARD_SERVER, paramMap, true);
+
+ }
+
+ /*
+ * Repository specification post handle, (server and proxy & client secret
+ * etc)
+ *
+ * @see
+ * org.apache.manifoldcf.core.connector.BaseConnector#processConfigurationPost
+ * (org.apache.manifoldcf.core.interfaces.IThreadContext,
+ * org.apache.manifoldcf.core.interfaces.IPostParameters,
+ * org.apache.manifoldcf.core.interfaces.ConfigParams)
+ */
+ @Override
+ public String processConfigurationPost(IThreadContext threadContext,
+ IPostParameters variableContext, ConfigParams parameters)
+ throws ManifoldCFException {
+
+ String confluenceProtocol = variableContext
+ .getParameter(PARAMETER_PREFIX
+ + ConfluenceConfiguration.Server.PROTOCOL);
+ if (confluenceProtocol != null)
+ parameters.setParameter(ConfluenceConfiguration.Server.PROTOCOL,
+ confluenceProtocol);
+
+ String confluenceHost = variableContext.getParameter(PARAMETER_PREFIX
+ + ConfluenceConfiguration.Server.HOST);
+ if (confluenceHost != null)
+ parameters.setParameter(ConfluenceConfiguration.Server.HOST,
+ confluenceHost);
+
+ String confluencePort = variableContext.getParameter(PARAMETER_PREFIX
+ + ConfluenceConfiguration.Server.PORT);
+ if (confluencePort != null)
+ parameters.setParameter(ConfluenceConfiguration.Server.PORT,
+ confluencePort);
+
+ String confluencePath = variableContext.getParameter(PARAMETER_PREFIX
+ + ConfluenceConfiguration.Server.PATH);
+ if (confluencePath != null)
+ parameters.setParameter(ConfluenceConfiguration.Server.PATH,
+ confluencePath);
+
+ String confluenceUsername = variableContext
+ .getParameter(PARAMETER_PREFIX
+ + ConfluenceConfiguration.Server.USERNAME);
+ if (confluenceUsername != null)
+ parameters.setParameter(ConfluenceConfiguration.Server.USERNAME,
+ confluenceUsername);
+
+ String confluencePassword = variableContext
+ .getParameter(PARAMETER_PREFIX
+ + ConfluenceConfiguration.Server.PASSWORD);
+ if (confluencePassword != null)
+ parameters.setObfuscatedParameter(
+ ConfluenceConfiguration.Server.PASSWORD,
+ variableContext.mapKeyToPassword(confluencePassword));
+
+ /* null means process configuration has been successful */
+ return null;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.apache.manifoldcf.authorities.authorities.BaseAuthorityConnector#getDefaultAuthorizationResponse(java.lang.String)
+ */
+ @Override
+ public AuthorizationResponse getDefaultAuthorizationResponse(String userName) {
+ return RESPONSE_UNREACHABLE;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.apache.manifoldcf.authorities.authorities.BaseAuthorityConnector#getAuthorizationResponse(java.lang.String)
+ */
+ @Override
+ public AuthorizationResponse getAuthorizationResponse(String userName)
+ throws ManifoldCFException {
+ try {
+ ConfluenceUser confluenceUser = confluenceClient.getUserAuthorities(userName);
+ if (confluenceUser.getUsername() == null
+ || confluenceUser.getUsername().isEmpty()
+ || confluenceUser.getAuthorities().isEmpty())
+ return RESPONSE_USERNOTFOUND;
+ else
+ return new AuthorizationResponse(
+ confluenceUser.getAuthorities().toArray(new String[confluenceUser.getAuthorities().size()]),
+ AuthorizationResponse.RESPONSE_OK);
+ } catch (Exception e) {
+ return RESPONSE_UNREACHABLE;
+ }
+ }
+
+}
diff --git a/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/authorities/authorities/confluence/Messages.java b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/authorities/authorities/confluence/Messages.java
new file mode 100644
index 0000000..2b305f5
--- /dev/null
+++ b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/authorities/authorities/confluence/Messages.java
@@ -0,0 +1,149 @@
+/**
+ * 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.authorities.authorities.confluence;
+
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.manifoldcf.core.interfaces.IHTTPOutput;
+import org.apache.manifoldcf.core.interfaces.ManifoldCFException;
+
+/**
+ * <p>Messages class</p>
+ * <p>Class used to render templates along with specific values</p>
+ * <p>Also used to get messages for specific Locales</p>
+ *
+ * @author Antonio David Perez Morales <adperezmorales@gmail.com>
+ *
+ */
+public class Messages extends org.apache.manifoldcf.ui.i18n.Messages
+{
+ public static final String DEFAULT_BUNDLE_NAME="org.apache.manifoldcf.authorities.authorities.confluence.common";
+ public static final String DEFAULT_PATH_NAME="org.apache.manifoldcf.authorities.authorities.confluence";
+
+ /** Constructor - do no instantiate
+ */
+ protected Messages()
+ {
+ }
+
+ public static String getString(Locale locale, String messageKey)
+ {
+ return getString(DEFAULT_BUNDLE_NAME, locale, messageKey, null);
+ }
+
+ public static String getAttributeString(Locale locale, String messageKey)
+ {
+ return getAttributeString(DEFAULT_BUNDLE_NAME, locale, messageKey, null);
+ }
+
+ public static String getBodyString(Locale locale, String messageKey)
+ {
+ return getBodyString(DEFAULT_BUNDLE_NAME, locale, messageKey, null);
+ }
+
+ public static String getAttributeJavascriptString(Locale locale, String messageKey)
+ {
+ return getAttributeJavascriptString(DEFAULT_BUNDLE_NAME, locale, messageKey, null);
+ }
+
+ public static String getBodyJavascriptString(Locale locale, String messageKey)
+ {
+ return getBodyJavascriptString(DEFAULT_BUNDLE_NAME, locale, messageKey, null);
+ }
+
+ public static String getString(Locale locale, String messageKey, Object[] args)
+ {
+ return getString(DEFAULT_BUNDLE_NAME, locale, messageKey, args);
+ }
+
+ public static String getAttributeString(Locale locale, String messageKey, Object[] args)
+ {
+ return getAttributeString(DEFAULT_BUNDLE_NAME, locale, messageKey, args);
+ }
+
+ public static String getBodyString(Locale locale, String messageKey, Object[] args)
+ {
+ return getBodyString(DEFAULT_BUNDLE_NAME, locale, messageKey, args);
+ }
+
+ public static String getAttributeJavascriptString(Locale locale, String messageKey, Object[] args)
+ {
+ return getAttributeJavascriptString(DEFAULT_BUNDLE_NAME, locale, messageKey, args);
+ }
+
+ public static String getBodyJavascriptString(Locale locale, String messageKey, Object[] args)
+ {
+ return getBodyJavascriptString(DEFAULT_BUNDLE_NAME, locale, messageKey, args);
+ }
+
+ // More general methods which allow bundlenames and class loaders to be specified.
+
+ public static String getString(String bundleName, Locale locale, String messageKey, Object[] args)
+ {
+ return getString(Messages.class, bundleName, locale, messageKey, args);
+ }
+
+ public static String getAttributeString(String bundleName, Locale locale, String messageKey, Object[] args)
+ {
+ return getAttributeString(Messages.class, bundleName, locale, messageKey, args);
+ }
+
+ public static String getBodyString(String bundleName, Locale locale, String messageKey, Object[] args)
+ {
+ return getBodyString(Messages.class, bundleName, locale, messageKey, args);
+ }
+
+ public static String getAttributeJavascriptString(String bundleName, Locale locale, String messageKey, Object[] args)
+ {
+ return getAttributeJavascriptString(Messages.class, bundleName, locale, messageKey, args);
+ }
+
+ public static String getBodyJavascriptString(String bundleName, Locale locale, String messageKey, Object[] args)
+ {
+ return getBodyJavascriptString(Messages.class, bundleName, locale, messageKey, args);
+ }
+
+ // Resource output
+
+ public static void outputResource(IHTTPOutput output, Locale locale, String resourceKey,
+ Map<String,String> substitutionParameters, boolean mapToUpperCase)
+ throws ManifoldCFException
+ {
+ outputResource(output,Messages.class,DEFAULT_PATH_NAME,locale,resourceKey,
+ substitutionParameters,mapToUpperCase);
+ }
+
+ public static void outputResourceWithVelocity(IHTTPOutput output, Locale locale, String resourceKey,
+ Map<String,String> substitutionParameters, boolean mapToUpperCase)
+ throws ManifoldCFException
+ {
+ outputResourceWithVelocity(output,Messages.class,DEFAULT_BUNDLE_NAME,DEFAULT_PATH_NAME,locale,resourceKey,
+ substitutionParameters,mapToUpperCase);
+ }
+
+ public static void outputResourceWithVelocity(IHTTPOutput output, Locale locale, String resourceKey,
+ Map<String,Object> contextObjects)
+ throws ManifoldCFException
+ {
+ outputResourceWithVelocity(output,Messages.class,DEFAULT_BUNDLE_NAME,DEFAULT_PATH_NAME,locale,resourceKey,
+ contextObjects);
+ }
+
+}
+
diff --git a/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/ConfluenceConfiguration.java b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/ConfluenceConfiguration.java
new file mode 100644
index 0000000..1fe8009
--- /dev/null
+++ b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/ConfluenceConfiguration.java
@@ -0,0 +1,41 @@
+package org.apache.manifoldcf.crawler.connectors.confluence;
+
+/**
+ * <p>
+ * ConfluenceConfiguration class
+ * </p>
+ * <p>
+ * Class used to keep configuration parameters for Confluence repository connection
+ * </p>
+ *
+ * @author Antonio David Perez Morales <adperezmorales@gmail.com>
+ *
+ */
+public class ConfluenceConfiguration {
+
+ public static interface Server {
+ public static final String USERNAME = "username";
+ public static final String PASSWORD = "password";
+ public static final String PROTOCOL = "protocol";
+ public static final String HOST = "host";
+ public static final String PORT = "port";
+ public static final String PATH = "path";
+
+ public static final String PROTOCOL_DEFAULT_VALUE = "http";
+ public static final String HOST_DEFAULT_VALUE = "";
+ public static final String PORT_DEFAULT_VALUE = "8090";
+ public static final String PATH_DEFAULT_VALUE = "/confluence";
+ public static final String USERNAME_DEFAULT_VALUE = "";
+ public static final String PASSWORD_DEFAULT_VALUE = "";
+ }
+
+ public static interface Specification {
+ public static final String SPACES = "spaces";
+ public static final String SPACE = "space";
+ public static final String SPACE_KEY_ATTRIBUTE = "key";
+ public static final String PAGES = "pages";
+ public static final String PROCESS_ATTACHMENTS_ATTRIBUTE_KEY = "process_attachments";
+
+ }
+
+}
diff --git a/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/ConfluenceRepositoryConnector.java b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/ConfluenceRepositoryConnector.java
new file mode 100644
index 0000000..4187de1
--- /dev/null
+++ b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/ConfluenceRepositoryConnector.java
@@ -0,0 +1,1253 @@
+package org.apache.manifoldcf.crawler.connectors.confluence;
+
+import java.io.IOException;
+import java.io.InterruptedIOException;
+import java.text.DateFormat;
+import java.text.MessageFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.manifoldcf.agents.interfaces.RepositoryDocument;
+import org.apache.manifoldcf.agents.interfaces.ServiceInterruption;
+import org.apache.manifoldcf.core.interfaces.ConfigParams;
+import org.apache.manifoldcf.core.interfaces.IHTTPOutput;
+import org.apache.manifoldcf.core.interfaces.IPasswordMapperActivity;
+import org.apache.manifoldcf.core.interfaces.IPostParameters;
+import org.apache.manifoldcf.core.interfaces.IThreadContext;
+import org.apache.manifoldcf.core.interfaces.ManifoldCFException;
+import org.apache.manifoldcf.core.interfaces.Specification;
+import org.apache.manifoldcf.core.interfaces.SpecificationNode;
+import org.apache.manifoldcf.crawler.connectors.BaseRepositoryConnector;
+import org.apache.manifoldcf.crawler.connectors.confluence.client.ConfluenceClient;
+import org.apache.manifoldcf.crawler.connectors.confluence.model.Attachment;
+import org.apache.manifoldcf.crawler.connectors.confluence.model.ConfluenceResponse;
+import org.apache.manifoldcf.crawler.connectors.confluence.model.Page;
+import org.apache.manifoldcf.crawler.connectors.confluence.util.ConfluenceUtil;
+import org.apache.manifoldcf.crawler.interfaces.IExistingVersions;
+import org.apache.manifoldcf.crawler.interfaces.IProcessActivity;
+import org.apache.manifoldcf.crawler.interfaces.ISeedingActivity;
+import org.apache.manifoldcf.crawler.system.Logging;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+/**
+ * <p>
+ * Confluence Repository Connector class
+ * </p>
+ * <p>
+ * ManifoldCF Repository connector to deal with Confluence documents
+ * </p>
+ *
+ * @author Antonio David Perez Morales <adperezmorales@gmail.com>
+ *
+ */
+public class ConfluenceRepositoryConnector extends BaseRepositoryConnector {
+
+ protected final static String ACTIVITY_READ = "read document";
+
+ /** Deny access token for default authority */
+ private final static String defaultAuthorityDenyToken = GLOBAL_DENY_TOKEN;
+
+ /*
+ * Prefix for Confluence configuration and specification parameters
+ */
+ private static final String PARAMETER_PREFIX = "confluence_";
+
+ /* Configuration tabs */
+ private static final String CONF_SERVER_TAB_PROPERTY = "ConfluenceRepositoryConnector.Server";
+
+ /* Specification tabs */
+ private static final String CONF_SPACES_TAB_PROPERTY = "ConfluenceRepositoryConnector.Spaces";
+ private static final String CONF_PAGES_TAB_PROPERTY = "ConfluenceRepositoryConnector.Pages";
+
+ // pages & js
+ // Template names for Confluence configuration
+ /**
+ * Forward to the javascript to check the configuration parameters
+ */
+ private static final String EDIT_CONFIG_HEADER_FORWARD = "editConfiguration_conf.js";
+ /**
+ * Server tab template
+ */
+ private static final String EDIT_CONFIG_FORWARD_SERVER = "editConfiguration_conf_server.html";
+
+ /**
+ * Forward to the HTML template to view the configuration parameters
+ */
+ private static final String VIEW_CONFIG_FORWARD = "viewConfiguration_conf.html";
+
+ // Template names for Confluence job specification
+ /**
+ * Forward to the javascript to check the specification parameters for the
+ * job
+ */
+ private static final String EDIT_SPEC_HEADER_FORWARD = "editSpecification_conf.js";
+ /**
+ * Forward to the template to edit the spaces for the job
+ */
+ private static final String EDIT_SPEC_FORWARD_SPACES = "editSpecification_confSpaces.html";
+
+ /**
+ * Forward to the template to edit the pages configuration for the job
+ */
+ private static final String EDIT_SPEC_FORWARD_CONF_PAGES = "editSpecification_confPages.html";
+
+ /**
+ * Forward to the template to view the specification parameters for the job
+ */
+ private static final String VIEW_SPEC_FORWARD = "viewSpecification_conf.html";
+
+ protected long lastSessionFetch = -1L;
+ protected static final long timeToRelease = 300000L;
+
+ protected final static long interruptionRetryTime = 5L * 60L * 1000L;
+
+ private Logger logger = LoggerFactory
+ .getLogger(ConfluenceRepositoryConnector.class);
+
+ /* Confluence instance parameters */
+ protected String protocol = null;
+ protected String host = null;
+ protected String port = null;
+ protected String path = null;
+ protected String username = null;
+ protected String password = null;
+
+ protected ConfluenceClient confluenceClient = null;
+
+ /**
+ * <p>
+ * Default constructor
+ * </p>
+ */
+ public ConfluenceRepositoryConnector() {
+ super();
+ }
+
+ /**
+ * Set Confluence Client (Mainly for Testing)
+ *
+ * @param confluenceClient
+ */
+ public void setConfluenceClient(ConfluenceClient confluenceClient){
+ this.confluenceClient = confluenceClient;
+ }
+
+ @Override
+ public String[] getActivitiesList() {
+ return new String[] { ACTIVITY_READ };
+ }
+
+ @Override
+ public String[] getBinNames(String documentIdentifier) {
+ return new String[] { host };
+ }
+
+ /**
+ * Close the connection. Call this before discarding the connection.
+ */
+ @Override
+ public void disconnect() throws ManifoldCFException {
+ if (confluenceClient != null) {
+ confluenceClient = null;
+ }
+
+ protocol = null;
+ host = null;
+ port = null;
+ path = null;
+ username = null;
+ password = null;
+
+ }
+
+ /**
+ * Makes connection to server
+ *
+ *
+ */
+ @Override
+ public void connect(ConfigParams configParams) {
+ super.connect(configParams);
+
+ protocol = params.getParameter(ConfluenceConfiguration.Server.PROTOCOL);
+ host = params.getParameter(ConfluenceConfiguration.Server.HOST);
+ port = params.getParameter(ConfluenceConfiguration.Server.PORT);
+ path = params.getParameter(ConfluenceConfiguration.Server.PATH);
+ username = params.getParameter(ConfluenceConfiguration.Server.USERNAME);
+ password = params
+ .getObfuscatedParameter(ConfluenceConfiguration.Server.PASSWORD);
+
+ try {
+ initConfluenceClient();
+ } catch (ManifoldCFException e) {
+ logger.debug(
+ "Not possible to initialize Confluence client. Reason: {}",
+ e.getMessage());
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Checks if connection is available
+ */
+ @Override
+ public String check() throws ManifoldCFException {
+ try {
+ if (!isConnected()) {
+ initConfluenceClient();
+ }
+ Boolean result = confluenceClient.check();
+ if (result)
+ return super.check();
+ else
+ throw new ManifoldCFException(
+ "Confluence instance could not be reached");
+ } catch (ServiceInterruption e) {
+ return "Connection temporarily failed: " + e.getMessage();
+ } catch (ManifoldCFException e) {
+ return "Connection failed: " + e.getMessage();
+ } catch (Exception e) {
+ return "Connection failed: " + e.getMessage();
+ }
+ }
+
+ /**
+ * <p>
+ * Initialize Confluence client using the configured parameters
+ *
+ * @throws ManifoldCFException
+ */
+ protected void initConfluenceClient() throws ManifoldCFException {
+ if (confluenceClient == null) {
+
+ if (StringUtils.isEmpty(protocol)) {
+ throw new ManifoldCFException("Parameter "
+ + ConfluenceConfiguration.Server.PROTOCOL
+ + " required but not set");
+ }
+
+ if (Logging.connectors.isDebugEnabled()) {
+ Logging.connectors.debug("Confluence protocol = '" + protocol
+ + "'");
+ }
+
+ if (StringUtils.isEmpty(host)) {
+ throw new ManifoldCFException("Parameter "
+ + ConfluenceConfiguration.Server.HOST
+ + " required but not set");
+ }
+
+ if (Logging.connectors.isDebugEnabled()) {
+ Logging.connectors.debug("Confluence host = '" + host + "'");
+ }
+
+ if (Logging.connectors.isDebugEnabled()) {
+ Logging.connectors.debug("Confluence port = '" + port + "'");
+ }
+
+// if (StringUtils.isEmpty(path)) {
+// throw new ManifoldCFException("Parameter "
+// + ConfluenceConfiguration.Server.PATH
+// + " required but not set");
+// }
+
+ if (Logging.connectors.isDebugEnabled()) {
+ Logging.connectors.debug("Confluence path = '" + path + "'");
+ }
+
+ if (Logging.connectors.isDebugEnabled()) {
+ Logging.connectors.debug("Confluence username = '" + username
+ + "'");
+ }
+
+ if (Logging.connectors.isDebugEnabled()) {
+ Logging.connectors
+ .debug("Confluence password '" + password != null ? "set"
+ : "not set" + "'");
+ }
+
+ int portInt;
+ if (port != null && port.length() > 0) {
+ try {
+ portInt = Integer.parseInt(port);
+ } catch (NumberFormatException e) {
+ throw new ManifoldCFException("Bad number: "
+ + e.getMessage(), e);
+ }
+ } else {
+ if (protocol.toLowerCase(Locale.ROOT).equals("http"))
+ portInt = 80;
+ else
+ portInt = 443;
+ }
+
+ /* Generating a client to perform Confluence requests */
+ confluenceClient = new ConfluenceClient(protocol, host, portInt,
+ path, username, password);
+ lastSessionFetch = System.currentTimeMillis();
+ }
+
+ }
+
+ /**
+ * This method is called to assess whether to count this connector instance
+ * should actually be counted as being connected.
+ *
+ * @return true if the connector instance is actually connected.
+ */
+ @Override
+ public boolean isConnected() {
+ return confluenceClient != null;
+ }
+
+ @Override
+ public void poll() throws ManifoldCFException {
+ if (lastSessionFetch == -1L) {
+ return;
+ }
+
+ long currentTime = System.currentTimeMillis();
+ if (currentTime >= lastSessionFetch + timeToRelease) {
+ confluenceClient.close();
+ confluenceClient = null;
+ lastSessionFetch = -1L;
+ }
+ }
+
+ @Override
+ public int getMaxDocumentRequest() {
+ return super.getMaxDocumentRequest();
+ }
+
+ /**
+ * Return the list of relationship types that this connector recognizes.
+ *
+ * @return the list.
+ */
+ @Override
+ public String[] getRelationshipTypes() {
+ return new String[] {};
+ }
+
+ private void fillInServerConfigurationMap(Map<String, String> serverMap,
+ IPasswordMapperActivity mapper, ConfigParams parameters) {
+ String confluenceProtocol = parameters
+ .getParameter(ConfluenceConfiguration.Server.PROTOCOL);
+ String confluenceHost = parameters
+ .getParameter(ConfluenceConfiguration.Server.HOST);
+ String confluencePort = parameters
+ .getParameter(ConfluenceConfiguration.Server.PORT);
+ String confluencePath = parameters
+ .getParameter(ConfluenceConfiguration.Server.PATH);
+ String confluenceUsername = parameters
+ .getParameter(ConfluenceConfiguration.Server.USERNAME);
+ String confluencePassword = parameters
+ .getObfuscatedParameter(ConfluenceConfiguration.Server.PASSWORD);
+
+ if (confluenceProtocol == null)
+ confluenceProtocol = ConfluenceConfiguration.Server.PROTOCOL_DEFAULT_VALUE;
+ if (confluenceHost == null)
+ confluenceHost = ConfluenceConfiguration.Server.HOST_DEFAULT_VALUE;
+ if (confluencePort == null)
+ confluencePort = ConfluenceConfiguration.Server.PORT_DEFAULT_VALUE;
+ if (confluencePath == null)
+ confluencePath = ConfluenceConfiguration.Server.PATH_DEFAULT_VALUE;
+
+ if (confluenceUsername == null)
+ confluenceUsername = ConfluenceConfiguration.Server.USERNAME_DEFAULT_VALUE;
+ if (confluencePassword == null)
+ confluencePassword = ConfluenceConfiguration.Server.PASSWORD_DEFAULT_VALUE;
+ else
+ confluencePassword = mapper.mapPasswordToKey(confluencePassword);
+
+ serverMap.put(PARAMETER_PREFIX
+ + ConfluenceConfiguration.Server.PROTOCOL, confluenceProtocol);
+ serverMap.put(PARAMETER_PREFIX + ConfluenceConfiguration.Server.HOST,
+ confluenceHost);
+ serverMap.put(PARAMETER_PREFIX + ConfluenceConfiguration.Server.PORT,
+ confluencePort);
+ serverMap.put(PARAMETER_PREFIX + ConfluenceConfiguration.Server.PATH,
+ confluencePath);
+ serverMap.put(PARAMETER_PREFIX
+ + ConfluenceConfiguration.Server.USERNAME, confluenceUsername);
+ serverMap.put(PARAMETER_PREFIX
+ + ConfluenceConfiguration.Server.PASSWORD, confluencePassword);
+ }
+
+ @Override
+ public void viewConfiguration(IThreadContext threadContext,
+ IHTTPOutput out, Locale locale, ConfigParams parameters)
+ throws ManifoldCFException, IOException {
+ Map<String, String> paramMap = new HashMap<String, String>();
+
+ /* Fill server configuration parameters */
+ fillInServerConfigurationMap(paramMap, out, parameters);
+
+ Messages.outputResourceWithVelocity(out, locale, VIEW_CONFIG_FORWARD,
+ paramMap, true);
+ }
+
+ @Override
+ public void outputConfigurationHeader(IThreadContext threadContext,
+ IHTTPOutput out, Locale locale, ConfigParams parameters,
+ List<String> tabsArray) throws ManifoldCFException, IOException {
+ // Add the Server tab
+ tabsArray.add(Messages.getString(locale, CONF_SERVER_TAB_PROPERTY));
+ // Map the parameters
+ Map<String, String> paramMap = new HashMap<String, String>();
+
+ /* Fill server configuration parameters */
+ fillInServerConfigurationMap(paramMap, out, parameters);
+
+ // Output the Javascript - only one Velocity template for all tabs
+ Messages.outputResourceWithVelocity(out, locale,
+ EDIT_CONFIG_HEADER_FORWARD, paramMap, true);
+ }
+
+ @Override
+ public void outputConfigurationBody(IThreadContext threadContext,
+ IHTTPOutput out, Locale locale, ConfigParams parameters,
+ String tabName) throws ManifoldCFException, IOException {
+
+ // Call the Velocity templates for each tab
+ Map<String, String> paramMap = new HashMap<String, String>();
+ // Set the tab name
+ paramMap.put("TabName", tabName);
+
+ // Fill in the parameters
+ fillInServerConfigurationMap(paramMap, out, parameters);
+
+ // Server tab
+ Messages.outputResourceWithVelocity(out, locale,
+ EDIT_CONFIG_FORWARD_SERVER, paramMap, true);
+
+ }
+
+ /*
+ * Repository specification post handle, (server and proxy & client secret
+ * etc)
+ *
+ * @see
+ * org.apache.manifoldcf.core.connector.BaseConnector#processConfigurationPost
+ * (org.apache.manifoldcf.core.interfaces.IThreadContext,
+ * org.apache.manifoldcf.core.interfaces.IPostParameters,
+ * org.apache.manifoldcf.core.interfaces.ConfigParams)
+ */
+ @Override
+ public String processConfigurationPost(IThreadContext threadContext,
+ IPostParameters variableContext, ConfigParams parameters)
+ throws ManifoldCFException {
+
+ String confluenceProtocol = variableContext
+ .getParameter(PARAMETER_PREFIX
+ + ConfluenceConfiguration.Server.PROTOCOL);
+ if (confluenceProtocol != null)
+ parameters.setParameter(ConfluenceConfiguration.Server.PROTOCOL,
+ confluenceProtocol);
+
+ String confluenceHost = variableContext.getParameter(PARAMETER_PREFIX
+ + ConfluenceConfiguration.Server.HOST);
+ if (confluenceHost != null)
+ parameters.setParameter(ConfluenceConfiguration.Server.HOST,
+ confluenceHost);
+
+ String confluencePort = variableContext.getParameter(PARAMETER_PREFIX
+ + ConfluenceConfiguration.Server.PORT);
+ if (confluencePort != null)
+ parameters.setParameter(ConfluenceConfiguration.Server.PORT,
+ confluencePort);
+
+ String confluencePath = variableContext.getParameter(PARAMETER_PREFIX
+ + ConfluenceConfiguration.Server.PATH);
+ if (confluencePath != null)
+ parameters.setParameter(ConfluenceConfiguration.Server.PATH,
+ confluencePath);
+
+ String confluenceUsername = variableContext
+ .getParameter(PARAMETER_PREFIX
+ + ConfluenceConfiguration.Server.USERNAME);
+ if (confluenceUsername != null)
+ parameters.setParameter(ConfluenceConfiguration.Server.USERNAME,
+ confluenceUsername);
+
+ String confluencePassword = variableContext
+ .getParameter(PARAMETER_PREFIX
+ + ConfluenceConfiguration.Server.PASSWORD);
+ if (confluencePassword != null)
+ parameters.setObfuscatedParameter(
+ ConfluenceConfiguration.Server.PASSWORD,
+ variableContext.mapKeyToPassword(confluencePassword));
+
+ /* null means process configuration has been successful */
+ return null;
+ }
+
+ /**
+ * <p>
+ * Fill the configured spaces into the map
+ * </p>
+ *
+ * @param newMap
+ * @param ds
+ */
+ private void fillInConfSpacesSpecificationMap(Map<String, Object> newMap,
+ ConfluenceSpecification cs) {
+
+ newMap.put(ConfluenceConfiguration.Specification.SPACES.toUpperCase(),
+ cs.getSpaces());
+ }
+
+ /**
+ * <p>
+ * Fill the pages configuration into the map
+ * </p>
+ *
+ * @param newMap
+ * @param ds
+ */
+ private void fillInConfPagesSpecificationMap(Map<String, Object> newMap,
+ ConfluenceSpecification cs) {
+
+ newMap.put(
+ ConfluenceConfiguration.Specification.PROCESS_ATTACHMENTS_ATTRIBUTE_KEY
+ .toUpperCase(), cs.isProcessAttachments());
+ return;
+
+ }
+
+ @Override
+ public void viewSpecification(IHTTPOutput out, Locale locale,
+ Specification ds, int connectionSequenceNumber)
+ throws ManifoldCFException, IOException {
+
+ Map<String, Object> paramMap = new HashMap<String, Object>();
+ paramMap.put("SeqNum", Integer.toString(connectionSequenceNumber));
+
+ ConfluenceSpecification cs = ConfluenceSpecification.from(ds);
+
+ fillInConfSpacesSpecificationMap(paramMap, cs);
+ fillInConfPagesSpecificationMap(paramMap, cs);
+
+ Messages.outputResourceWithVelocity(out, locale, VIEW_SPEC_FORWARD,
+ paramMap);
+ }
+
+ /*
+ * Handle job specification post
+ *
+ * @see org.apache.manifoldcf.crawler.connectors.BaseRepositoryConnector#
+ * processSpecificationPost
+ * (org.apache.manifoldcf.core.interfaces.IPostParameters,
+ * org.apache.manifoldcf.crawler.interfaces.DocumentSpecification)
+ */
+
+ @Override
+ public String processSpecificationPost(IPostParameters variableContext,
+ Locale locale, Specification ds, int connectionSequenceNumber)
+ throws ManifoldCFException {
+
+ String seqPrefix = "s" + connectionSequenceNumber + "_";
+
+ String xc = variableContext.getParameter(seqPrefix + "spacescount");
+ if (xc != null) {
+ // Delete all preconfigured spaces
+ int i = 0;
+ while (i < ds.getChildCount()) {
+ SpecificationNode sn = ds.getChild(i);
+ if (sn.getType().equals(
+ ConfluenceConfiguration.Specification.SPACES))
+ ds.removeChild(i);
+ else
+ i++;
+ }
+
+ SpecificationNode spaces = new SpecificationNode(
+ ConfluenceConfiguration.Specification.SPACES);
+ ds.addChild(ds.getChildCount(), spaces);
+ int spacesCount = Integer.parseInt(xc);
+ i = 0;
+ while (i < spacesCount) {
+ String spaceDescription = "_" + Integer.toString(i);
+ String spaceOpName = seqPrefix + "spaceop" + spaceDescription;
+ xc = variableContext.getParameter(spaceOpName);
+ if (xc != null && xc.equals("Delete")) {
+ // Next row
+ i++;
+ continue;
+ }
+ // Get the stuff we need
+ String spaceKey = variableContext.getParameter(seqPrefix
+ + "space" + spaceDescription);
+ SpecificationNode node = new SpecificationNode(
+ ConfluenceConfiguration.Specification.SPACE);
+ node.setAttribute(
+ ConfluenceConfiguration.Specification.SPACE_KEY_ATTRIBUTE,
+ spaceKey);
+ spaces.addChild(spaces.getChildCount(), node);
+ i++;
+ }
+
+ String op = variableContext.getParameter(seqPrefix + "spaceop");
+ if (op != null && op.equals("Add")) {
+ String spaceSpec = variableContext.getParameter(seqPrefix
+ + "space");
+ SpecificationNode node = new SpecificationNode(
+ ConfluenceConfiguration.Specification.SPACE);
+ node.setAttribute(
+ ConfluenceConfiguration.Specification.SPACE_KEY_ATTRIBUTE,
+ spaceSpec);
+ spaces.addChild(spaces.getChildCount(), node);
+ }
+ }
+
+ /* Delete pages configuration */
+ int i = 0;
+ while (i < ds.getChildCount()) {
+ SpecificationNode sn = ds.getChild(i);
+ if (sn.getType()
+ .equals(ConfluenceConfiguration.Specification.PAGES))
+ ds.removeChild(i);
+ else
+ i++;
+ }
+
+ SpecificationNode pages = new SpecificationNode(
+ ConfluenceConfiguration.Specification.PAGES);
+ ds.addChild(ds.getChildCount(), pages);
+
+ String procAttachments = variableContext
+ .getParameter(seqPrefix
+ + ConfluenceConfiguration.Specification.PROCESS_ATTACHMENTS_ATTRIBUTE_KEY);
+ if (procAttachments != null && !procAttachments.isEmpty()) {
+ pages.setAttribute(
+ ConfluenceConfiguration.Specification.PROCESS_ATTACHMENTS_ATTRIBUTE_KEY,
+ String.valueOf(procAttachments));
+ }
+
+ return null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * @see org.apache.manifoldcf.crawler.connectors.BaseRepositoryConnector#
+ * outputSpecificationBody
+ * (org.apache.manifoldcf.core.interfaces.IHTTPOutput, java.util.Locale,
+ * org.apache.manifoldcf.crawler.interfaces.DocumentSpecification,
+ * java.lang.String)
+ */
+ @Override
+ public void outputSpecificationBody(IHTTPOutput out, Locale locale,
+ Specification ds, int connectionSequenceNumber,
+ int actualSequenceNumber, String tabName)
+ throws ManifoldCFException, IOException {
+
+ // Output JIRAQuery tab
+ Map<String, Object> paramMap = new HashMap<String, Object>();
+ paramMap.put("TabName", tabName);
+ paramMap.put("SeqNum", Integer.toString(connectionSequenceNumber));
+ paramMap.put("SelectedNum", Integer.toString(actualSequenceNumber));
+
+ ConfluenceSpecification cs = ConfluenceSpecification.from(ds);
+
+ fillInConfSpacesSpecificationMap(paramMap, cs);
+ fillInConfPagesSpecificationMap(paramMap, cs);
+ Messages.outputResourceWithVelocity(out, locale,
+ EDIT_SPEC_FORWARD_SPACES, paramMap);
+
+ Messages.outputResourceWithVelocity(out, locale,
+ EDIT_SPEC_FORWARD_CONF_PAGES, paramMap);
+ }
+
+ /*
+ * Header for the specification
+ *
+ * @see org.apache.manifoldcf.crawler.connectors.BaseRepositoryConnector#
+ * outputSpecificationHeader
+ * (org.apache.manifoldcf.core.interfaces.IHTTPOutput, java.util.Locale,
+ * org.apache.manifoldcf.crawler.interfaces.DocumentSpecification,
+ * java.util.List)
+ */
+ @Override
+ public void outputSpecificationHeader(IHTTPOutput out, Locale locale,
+ Specification ds, int connectionSequenceNumber,
+ List<String> tabsArray) throws ManifoldCFException, IOException {
+
+ tabsArray.add(Messages.getString(locale, CONF_SPACES_TAB_PROPERTY));
+ tabsArray.add(Messages.getString(locale, CONF_PAGES_TAB_PROPERTY));
+
+ Map<String, Object> paramMap = new HashMap<String, Object>();
+ paramMap.put("SeqNum", Integer.toString(connectionSequenceNumber));
+
+ Messages.outputResourceWithVelocity(out, locale,
+ EDIT_SPEC_HEADER_FORWARD, paramMap);
+ }
+
+ /*
+ * Adding seed documents
+ *
+ * @see org.apache.manifoldcf.crawler.connectors.BaseRepositoryConnector#
+ * addSeedDocuments
+ * (org.apache.manifoldcf.crawler.interfaces.ISeedingActivity,
+ * org.apache.manifoldcf.crawler.interfaces.DocumentSpecification, long,
+ * long, int)
+ */
+ public String addSeedDocuments(ISeedingActivity activities,
+ Specification spec, String lastSeedVersion, long seedTime,
+ int jobMode) throws ManifoldCFException, ServiceInterruption {
+
+ if (!isConnected()) {
+ initConfluenceClient();
+ }
+
+ try {
+
+ /*
+ * Not uses delta seeding because Confluence can't be queried using
+ * dates or in a ordered way, only start and limit which can cause
+ * problems if an already indexed document is deleted, because we
+ * will miss some to-be indexed docs due to the last start parameter
+ * stored in the last execution
+ */
+ // if(lastSeedVersion != null && !lastSeedVersion.isEmpty()) {
+ // StringTokenizer tokenizer = new
+ // StringTokenizer(lastSeedVersion,"|");
+ //
+ // lastStart = new Long(lastSeedVersion);
+ // }
+
+ ConfluenceSpecification confluenceSpecification = ConfluenceSpecification
+ .from(spec);
+ List<String> spaceKeys = confluenceSpecification.getSpaces();
+
+ if (spaceKeys.isEmpty()) {
+ logger.info("No spaces configured. Processing all spaces");
+ addSeedDocumentsForSpace(Optional.<String> absent(),
+ activities, confluenceSpecification, lastSeedVersion,
+ seedTime, jobMode);
+ } else {
+ for (String space : spaceKeys) {
+ logger.info("Processing configured space {}", space);
+ addSeedDocumentsForSpace(Optional.<String> of(space),
+ activities, confluenceSpecification,
+ lastSeedVersion, seedTime, jobMode);
+ }
+ }
+
+ return "";
+ } catch (Exception e) {
+ handleConfluenceDownException(e, "seeding");
+ return null;
+ }
+ }
+
+ /**
+ * <p>
+ * Add seed documents for a given optional space
+ * </p>
+ *
+ * @throws ServiceInterruption
+ * @throws ManifoldCFException
+ */
+ private void addSeedDocumentsForSpace(Optional<String> space,
+ ISeedingActivity activities,
+ ConfluenceSpecification confluenceSpec, String lastSeedVersion,
+ long seedTime, int jobMode) throws ManifoldCFException,
+ ServiceInterruption {
+
+ long lastStart = 0;
+ long defaultSize = 50;
+
+ if (Logging.connectors != null && Logging.connectors.isDebugEnabled()) {
+ String spaceDesc = space.isPresent() ? "space with key "
+ + space.get() : "all the spaces";
+ Logging.connectors.debug(MessageFormat.format(
+ "Starting from {0} and size {1} for {2}", new Object[] {
+ lastStart, defaultSize, spaceDesc }));
+ }
+
+ try {
+ Boolean isLast = true;
+ do {
+ final ConfluenceResponse<Page> response = confluenceClient.getPages(
+ (int) lastStart, (int) defaultSize, space);
+
+ int count = 0;
+ for (Page page : response.getResults()) {
+
+ activities.addSeedDocument(page.getId());
+ if (confluenceSpec.isProcessAttachments()) {
+ processSeedAttachments(page, activities);
+ }
+ count++;
+ }
+ if (Logging.connectors != null
+ && Logging.connectors.isDebugEnabled())
+ Logging.connectors.debug(MessageFormat.format(
+ "Fetched and added {0} seed documents",
+ new Object[] { new Integer(count) }));
+
+ lastStart += count;
+ isLast = response.isLast();
+ if (Logging.connectors != null
+ && Logging.connectors.isDebugEnabled())
+ Logging.connectors.debug(MessageFormat.format(
+ "New start {0} and size {1}", new Object[] {
+ lastStart, defaultSize }));
+ } while (!isLast);
+
+ } catch (Exception e) {
+ handleConfluenceDownException(e, "seeding");
+ }
+
+ }
+
+ /**
+ * <p>
+ * Process seed attachments for the given page
+ * </p>
+ *
+ * @param page
+ * @param activities
+ */
+ private void processSeedAttachments(Page page, ISeedingActivity activities)
+ throws ManifoldCFException, ServiceInterruption {
+ long lastStart = 0;
+ long defaultSize = 50;
+
+ if (Logging.connectors != null && Logging.connectors.isDebugEnabled()) {
+ Logging.connectors
+ .debug(MessageFormat
+ .format("Processing page {} attachments starting from {} and size {}",
+ new Object[] { page.getId(), lastStart,
+ defaultSize }));
+ }
+
+ try {
+ Boolean isLast = true;
+ do {
+ final ConfluenceResponse<Attachment> response = confluenceClient
+ .getPageAttachments(page.getId(), (int) lastStart,
+ (int) defaultSize);
+
+ int count = 0;
+ for (Page resultPage : response.getResults()) {
+ activities.addSeedDocument(ConfluenceUtil
+ .generateRepositoryDocumentIdentifier(
+ resultPage.getId(), page.getId()));
+ count++;
+ }
+
+ if (Logging.connectors != null
+ && Logging.connectors.isDebugEnabled())
+ Logging.connectors
+ .debug(MessageFormat
+ .format("Fetched and added {} seed document attachments for page {}",
+ new Object[] { new Integer(count),
+ page.getId() }));
+
+ lastStart += count;
+ isLast = response.isLast();
+ if (Logging.connectors != null
+ && Logging.connectors.isDebugEnabled())
+ Logging.connectors.debug(MessageFormat.format(
+ "New start {0} and size {1}", new Object[] {
+ lastStart, defaultSize }));
+ } while (!isLast);
+
+ } catch (Exception e) {
+ handleConfluenceDownException(e, "seeding");
+ }
+ }
+
+ protected static void handleConfluenceDownException(Exception e,
+ String context) throws ManifoldCFException, ServiceInterruption {
+ long currentTime = System.currentTimeMillis();
+
+ // Server doesn't appear to by up. Try for a brief time then give up.
+ String message = "Server appears down during " + context + ": "
+ + e.getMessage();
+ Logging.connectors.warn(message, e);
+ throw new ServiceInterruption(message, e, currentTime
+ + interruptionRetryTime, -1L, 3, true);
+ }
+
+ /*
+ * Process documents
+ *
+ * @see org.apache.manifoldcf.crawler.connectors.BaseRepositoryConnector#
+ * processDocuments(java.lang.String[], java.lang.String[],
+ * org.apache.manifoldcf.crawler.interfaces.IProcessActivity,
+ * org.apache.manifoldcf.crawler.interfaces.DocumentSpecification,
+ * boolean[])
+ */
+ @Override
+ public void processDocuments(String[] documentIdentifiers,
+ IExistingVersions statuses, Specification spec,
+ IProcessActivity activities, int jobMode,
+ boolean usesDefaultAuthority) throws ManifoldCFException,
+ ServiceInterruption {
+
+ if(Logging.connectors != null && Logging.connectors.isDebugEnabled())
+ Logging.connectors
+ .debug("Process Confluence documents: Inside processDocuments");
+
+ for (int i = 0; i < documentIdentifiers.length; i++) {
+ String pageId = documentIdentifiers[i];
+ String version = statuses.getIndexedVersionString(pageId);
+
+ long startTime = System.currentTimeMillis();
+ String errorCode = "OK";
+ String errorDesc = StringUtils.EMPTY;
+ ProcessResult pResult = null;
+ boolean doLog = true;
+
+ try {
+ if (Logging.connectors != null && Logging.connectors.isDebugEnabled()) {
+ Logging.connectors
+ .debug("Confluence: Processing document identifier '"
+ + pageId + "'");
+ }
+
+ /* Ensure Confluence client is connected */
+ if (!isConnected()) {
+ initConfluenceClient();
+ }
+
+ if (ConfluenceUtil.isAttachment(pageId)) {
+ pResult = processPageAsAttachment(pageId, version,
+ activities, doLog);
+ }
+ else {
+ pResult = processPage(pageId, version, activities, doLog,
+ Maps.<String, String> newHashMap());
+ }
+ } catch (IOException ioe) {
+ handleIOException(ioe);
+ } catch (Exception e) {
+ handleException(e);
+ }
+
+ finally {
+ if (doLog){
+ if(pResult.errorCode != null && !pResult.errorCode.isEmpty()){
+ activities.recordActivity(new Long(startTime),
+ ACTIVITY_READ, pResult.fileSize, pageId, pResult.errorCode,
+ pResult.errorDescription, null);
+ }else{
+ activities.recordActivity(new Long(startTime),
+ ACTIVITY_READ, pResult.fileSize, pageId, errorCode,
+ errorDesc, null);
+ }
+ }
+ }
+
+ }
+ }
+
+ /**
+ * <p>
+ * Process the specific page
+ * </p>
+ *
+ * @param pageId
+ * The pageId being an attachment
+ * @param version
+ * The version of the page
+ * @param activities
+ * @param doLog
+ * @throws ManifoldCFException
+ * @throws IOException
+ * @throws ServiceInterruption
+ */
+ private ProcessResult processPage(String pageId, String version,
+ IProcessActivity activities, boolean doLog,
+ Map<String, String> extraProperties) throws ManifoldCFException,
+ ServiceInterruption, IOException {
+ Page page = confluenceClient.getPage(pageId);
+ return processPageInternal(page, pageId, version, activities, doLog,
+ extraProperties);
+ }
+
+ /**
+ * <p>
+ * Process the specific attachment
+ * </p>
+ *
+ * @param pageId
+ * The pageId being an attachment
+ * @param version
+ * The version of the page
+ * @param activities
+ * @param doLog
+ * @throws IOException
+ * @throws ServiceInterruption
+ */
+ private ProcessResult processPageAsAttachment(String pageId, String version,
+ IProcessActivity activities, boolean doLog)
+ throws ManifoldCFException, ServiceInterruption, IOException {
+
+ String[] ids = ConfluenceUtil.getAttachmentAndPageId(pageId);
+ Attachment attachment = confluenceClient.getAttachment(ids[0]);
+ Map<String, String> extraProperties = Maps.newHashMap();
+ extraProperties.put("attachedBy", ids[1]);
+ return processPageInternal(attachment, pageId, version, activities, doLog,
+ extraProperties);
+ }
+
+ /**
+ * <p>
+ * Process the specific page
+ * </p>
+ *
+ * @param pageId
+ * The pageId being an attachment
+ * @param manifoldDocumentIdentifier
+ * @param version
+ * The version of the page
+ * @param activities
+ * @param doLog
+ * @throws ManifoldCFException
+ * @throws IOException
+ * @throws ServiceInterruption
+ */
+ private ProcessResult processPageInternal(Page page,
+ String manifoldDocumentIdentifier, String version,
+ IProcessActivity activities, boolean doLog,
+ Map<String, String> extraProperties) throws ManifoldCFException,
+ ServiceInterruption, IOException {
+
+
+ /* Remove page if it has no content */
+ /*
+ * Page does not have content if there was an error trying to get the
+ * page
+ */
+ if (!page.hasContent()) {
+ activities.deleteDocument(manifoldDocumentIdentifier);
+ return new ProcessResult(page.getLength(), "DELETED", "");
+ }
+ if (Logging.connectors != null && Logging.connectors.isDebugEnabled()) {
+ Logging.connectors.debug("Confluence: This content exists: "
+ + page.getId());
+ }
+
+ RepositoryDocument rd = new RepositoryDocument();
+ Date createdDate = page.getCreatedDate();
+ Date lastModified = page.getLastModifiedDate();
+ DateFormat df = DateFormat.getDateTimeInstance();
+
+ /*
+ * Retain page in Manifold because it has not changed from last time
+ * This is needed to keep the identifier in Manifold data, because by
+ * default if a document is not retained nor ingested, it will be
+ * deleted by the framework
+ */
+ String lastVersion = df.format(lastModified);
+
+ if (!activities.checkDocumentNeedsReindexing(manifoldDocumentIdentifier, lastVersion)) {
+ return new ProcessResult(page.getLength(), "RETAINED", "");
+ }
+
+ if (!activities.checkLengthIndexable(page.getLength())){
+ activities.noDocument(page.getId(), lastVersion);
+ String errorCode = IProcessActivity.EXCLUDED_LENGTH;
+ String errorDesc = "Excluding document because of length ("+page.getLength()+")";
+ return new ProcessResult(page.getLength(), errorCode, errorDesc);
+ }
+
+ if (!activities.checkMimeTypeIndexable(page.getMediaType())) {
+ activities.noDocument(page.getId(), lastVersion);
+ String errorCode = IProcessActivity.EXCLUDED_MIMETYPE;
+ String errorDesc = "Excluding document because of mime type ("+page.getMediaType()+")";
+ return new ProcessResult(page.getLength(), errorCode, errorDesc);
+ }
+
+ if (!activities.checkDateIndexable(lastModified)) {
+ activities.noDocument(page.getId(), lastVersion);
+ String errorCode = IProcessActivity.EXCLUDED_DATE;
+ String errorDesc = "Excluding document because of date ("+lastModified+")";
+ return new ProcessResult(page.getLength(), errorCode, errorDesc);
+ }
+
+ if (!activities.checkURLIndexable(page.getWebUrl())) {
+ activities.noDocument(page.getId(), lastVersion);
+ String errorCode = IProcessActivity.EXCLUDED_URL;
+ String errorDesc = "Excluding document because of URL ('"+page.getWebUrl()+"')";
+ return new ProcessResult(page.getLength(), errorCode, errorDesc);
+ }
+
+ /* Add repository document information */
+ rd.setMimeType(page.getMediaType());
+ if (createdDate != null)
+ rd.setCreatedDate(createdDate);
+ if (lastModified != null)
+ rd.setModifiedDate(lastModified);
+ rd.setIndexingDate(new Date());
+
+ /* Adding Page Metadata */
+ Map<String, Object> pageMetadata = page.getMetadataAsMap();
+ for (Entry<String, Object> entry : pageMetadata.entrySet()) {
+ if(entry.getValue() instanceof List) {
+ List<?> list = (List<?>)entry.getValue();
+ rd.addField(entry.getKey(), list.toArray(new String[list.size()]));
+ }
+ else {
+ rd.addField(entry.getKey(), entry.getValue().toString());
+ }
+ }
+
+ /* Adding extra properties */
+ for (Entry<String, String> entry : extraProperties.entrySet()) {
+ rd.addField(entry.getKey(), entry.getValue());
+ }
+
+ String documentURI = page.getWebUrl();
+
+ /* Set repository document ACLs */
+ rd.setSecurityACL(RepositoryDocument.SECURITY_TYPE_DOCUMENT,
+ new String[] { page.getSpace() });
+ rd.setSecurityDenyACL(RepositoryDocument.SECURITY_TYPE_DOCUMENT,
+ new String[] { defaultAuthorityDenyToken });
+
+ rd.setBinary(page.getContentStream(), page.getLength());
+ rd.addField("size", String.valueOf(page.getLength()));
+
+ /* Ingest document */
+ activities.ingestDocumentWithException(manifoldDocumentIdentifier,
+ lastVersion, documentURI, rd);
+
+ return new ProcessResult(page.getLength(), null, null);
+ }
+
+ /**
+ * <p>
+ * Handles IO Exception to manage whether the exception is an interruption
+ * so that the process needs to be executed again later on
+ * </p>
+ *
+ * @param e
+ * The Exception
+ * @throws ManifoldCFException
+ * @throws ServiceInterruption
+ */
+ private static void handleIOException(IOException e)
+ throws ManifoldCFException, ServiceInterruption {
+ if (!(e instanceof java.net.SocketTimeoutException)
+ && (e instanceof InterruptedIOException)) {
+ throw new ManifoldCFException("Interrupted: " + e.getMessage(), e,
+ ManifoldCFException.INTERRUPTED);
+ }
+ Logging.connectors.warn("IO exception: " + e.getMessage(), e);
+ long currentTime = System.currentTimeMillis();
+ throw new ServiceInterruption("IO exception: " + e.getMessage(), e,
+ currentTime + 300000L, currentTime + 3 * 60 * 60000L, -1, false);
+ }
+
+ /**
+ * <p>
+ * Handles general exceptions
+ * </p>
+ *
+ * @param e
+ * The Exception
+ * @throws ManifoldCFException
+ */
+ private static void handleException(Exception e) throws ManifoldCFException {
+ Logging.connectors.warn("Exception: " + e.getMessage(), e);
+ throw new ManifoldCFException("Exception: " + e.getMessage(), e,
+ ManifoldCFException.REPOSITORY_CONNECTION_ERROR);
+
+ }
+
+ private class ProcessResult{
+ private long fileSize;
+ private String errorCode;
+ private String errorDescription;
+
+ private ProcessResult(long fileSize, String errorCode, String errorDescription){
+ this.fileSize = fileSize;
+ this.errorCode = errorCode;
+ this.errorDescription = errorDescription;
+ }
+ }
+
+ /**
+ * <p>
+ * Internal private class used to parse and keep the specification
+ * configuration in object format
+ * </p>
+ *
+ * @author Antonio David Perez Morales <adperezmorales@gmail.com>
+ *
+ */
+ private static class ConfluenceSpecification {
+ private List<String> spaces;
+ private Boolean processAttachments = false;
+
+ /**
+ * <p>
+ * Returns if attachments should be processed
+ * </p>
+ *
+ * @return a {@code Boolean} indicating if the attachments should be
+ * processed or not
+ */
+ public Boolean isProcessAttachments() {
+ return this.processAttachments;
+ }
+
+ /**
+ * <p>
+ * Returns the list of configured spaces or an empty list meaning that
+ * all spaces should be processed
+ * </p>
+ *
+ * @return a {@code List<String>} of configured spaces
+ */
+ public List<String> getSpaces() {
+ return this.spaces;
+ }
+
+ public static ConfluenceSpecification from(Specification spec) {
+ ConfluenceSpecification cs = new ConfluenceSpecification();
+ cs.spaces = Lists.newArrayList();
+ for (int i = 0, len = spec.getChildCount(); i < len; i++) {
+ SpecificationNode sn = spec.getChild(i);
+ if (sn.getType().equals(
+ ConfluenceConfiguration.Specification.SPACES)) {
+ for (int j = 0, sLen = sn.getChildCount(); j < sLen; j++) {
+ SpecificationNode specNode = sn.getChild(j);
+ if (specNode.getType().equals(
+ ConfluenceConfiguration.Specification.SPACE)) {
+ cs.spaces
+ .add(specNode
+ .getAttributeValue(ConfluenceConfiguration.Specification.SPACE_KEY_ATTRIBUTE));
+
+ }
+ }
+
+ } else if (sn.getType().equals(
+ ConfluenceConfiguration.Specification.PAGES)) {
+ String s = sn
+ .getAttributeValue(ConfluenceConfiguration.Specification.PROCESS_ATTACHMENTS_ATTRIBUTE_KEY);
+ cs.processAttachments = Boolean.valueOf(s);
+ }
+ }
+
+ return cs;
+
+ }
+ }
+
+}
diff --git a/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/Messages.java b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/Messages.java
new file mode 100644
index 0000000..1b65bda
--- /dev/null
+++ b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/Messages.java
@@ -0,0 +1,149 @@
+/**
+ * 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.confluence;
+
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.manifoldcf.core.interfaces.IHTTPOutput;
+import org.apache.manifoldcf.core.interfaces.ManifoldCFException;
+
+/**
+ * <p>Messages class</p>
+ * <p>Class used to render templates along with specific values</p>
+ * <p>Also used to get messages for specific Locales</p>
+ *
+ * @author Antonio David Perez Morales <adperezmorales@gmail.com>
+ *
+ */
+public class Messages extends org.apache.manifoldcf.ui.i18n.Messages
+{
+ public static final String DEFAULT_BUNDLE_NAME="org.apache.manifoldcf.crawler.connectors.confluence.common";
+ public static final String DEFAULT_PATH_NAME="org.apache.manifoldcf.crawler.connectors.confluence";
+
+ /** Constructor - do no instantiate
+ */
+ protected Messages()
+ {
+ }
+
+ public static String getString(Locale locale, String messageKey)
+ {
+ return getString(DEFAULT_BUNDLE_NAME, locale, messageKey, null);
+ }
+
+ public static String getAttributeString(Locale locale, String messageKey)
+ {
+ return getAttributeString(DEFAULT_BUNDLE_NAME, locale, messageKey, null);
+ }
+
+ public static String getBodyString(Locale locale, String messageKey)
+ {
+ return getBodyString(DEFAULT_BUNDLE_NAME, locale, messageKey, null);
+ }
+
+ public static String getAttributeJavascriptString(Locale locale, String messageKey)
+ {
+ return getAttributeJavascriptString(DEFAULT_BUNDLE_NAME, locale, messageKey, null);
+ }
+
+ public static String getBodyJavascriptString(Locale locale, String messageKey)
+ {
+ return getBodyJavascriptString(DEFAULT_BUNDLE_NAME, locale, messageKey, null);
+ }
+
+ public static String getString(Locale locale, String messageKey, Object[] args)
+ {
+ return getString(DEFAULT_BUNDLE_NAME, locale, messageKey, args);
+ }
+
+ public static String getAttributeString(Locale locale, String messageKey, Object[] args)
+ {
+ return getAttributeString(DEFAULT_BUNDLE_NAME, locale, messageKey, args);
+ }
+
+ public static String getBodyString(Locale locale, String messageKey, Object[] args)
+ {
+ return getBodyString(DEFAULT_BUNDLE_NAME, locale, messageKey, args);
+ }
+
+ public static String getAttributeJavascriptString(Locale locale, String messageKey, Object[] args)
+ {
+ return getAttributeJavascriptString(DEFAULT_BUNDLE_NAME, locale, messageKey, args);
+ }
+
+ public static String getBodyJavascriptString(Locale locale, String messageKey, Object[] args)
+ {
+ return getBodyJavascriptString(DEFAULT_BUNDLE_NAME, locale, messageKey, args);
+ }
+
+ // More general methods which allow bundlenames and class loaders to be specified.
+
+ public static String getString(String bundleName, Locale locale, String messageKey, Object[] args)
+ {
+ return getString(Messages.class, bundleName, locale, messageKey, args);
+ }
+
+ public static String getAttributeString(String bundleName, Locale locale, String messageKey, Object[] args)
+ {
+ return getAttributeString(Messages.class, bundleName, locale, messageKey, args);
+ }
+
+ public static String getBodyString(String bundleName, Locale locale, String messageKey, Object[] args)
+ {
+ return getBodyString(Messages.class, bundleName, locale, messageKey, args);
+ }
+
+ public static String getAttributeJavascriptString(String bundleName, Locale locale, String messageKey, Object[] args)
+ {
+ return getAttributeJavascriptString(Messages.class, bundleName, locale, messageKey, args);
+ }
+
+ public static String getBodyJavascriptString(String bundleName, Locale locale, String messageKey, Object[] args)
+ {
+ return getBodyJavascriptString(Messages.class, bundleName, locale, messageKey, args);
+ }
+
+ // Resource output
+
+ public static void outputResource(IHTTPOutput output, Locale locale, String resourceKey,
+ Map<String,String> substitutionParameters, boolean mapToUpperCase)
+ throws ManifoldCFException
+ {
+ outputResource(output,Messages.class,DEFAULT_PATH_NAME,locale,resourceKey,
+ substitutionParameters,mapToUpperCase);
+ }
+
+ public static void outputResourceWithVelocity(IHTTPOutput output, Locale locale, String resourceKey,
+ Map<String,String> substitutionParameters, boolean mapToUpperCase)
+ throws ManifoldCFException
+ {
+ outputResourceWithVelocity(output,Messages.class,DEFAULT_BUNDLE_NAME,DEFAULT_PATH_NAME,locale,resourceKey,
+ substitutionParameters,mapToUpperCase);
+ }
+
+ public static void outputResourceWithVelocity(IHTTPOutput output, Locale locale, String resourceKey,
+ Map<String,Object> contextObjects)
+ throws ManifoldCFException
+ {
+ outputResourceWithVelocity(output,Messages.class,DEFAULT_BUNDLE_NAME,DEFAULT_PATH_NAME,locale,resourceKey,
+ contextObjects);
+ }
+
+}
+
diff --git a/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/client/ConfluenceClient.java b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/client/ConfluenceClient.java
new file mode 100644
index 0000000..2da15e3
--- /dev/null
+++ b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/client/ConfluenceClient.java
@@ -0,0 +1,727 @@
+package org.apache.manifoldcf.crawler.connectors.confluence.client;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.charset.Charset;
+import java.util.List;
+
+import org.apache.commons.codec.binary.Base64;
+import org.apache.commons.io.IOUtils;
+import org.apache.http.HttpEntity;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.config.RequestConfig;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.client.protocol.HttpClientContext;
+import org.apache.http.config.SocketConfig;
+import org.apache.http.conn.HttpClientConnectionManager;
+import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
+import org.apache.http.entity.StringEntity;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.DefaultRedirectStrategy;
+import org.apache.http.impl.client.HttpClients;
+import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
+import org.apache.http.protocol.HttpRequestExecutor;
+import org.apache.http.util.EntityUtils;
+import org.apache.manifoldcf.connectorcommon.common.InterruptibleSocketFactory;
+import org.apache.manifoldcf.connectorcommon.interfaces.KeystoreManagerFactory;
+import org.apache.manifoldcf.core.interfaces.ManifoldCFException;
+import org.apache.manifoldcf.crawler.connectors.confluence.exception.ConfluenceException;
+import org.apache.manifoldcf.crawler.connectors.confluence.model.Attachment;
+import org.apache.manifoldcf.crawler.connectors.confluence.model.ConfluenceResource;
+import org.apache.manifoldcf.crawler.connectors.confluence.model.ConfluenceResponse;
+import org.apache.manifoldcf.crawler.connectors.confluence.model.ConfluenceUser;
+import org.apache.manifoldcf.crawler.connectors.confluence.model.Label;
+import org.apache.manifoldcf.crawler.connectors.confluence.model.MutableAttachment;
+import org.apache.manifoldcf.crawler.connectors.confluence.model.MutablePage;
+import org.apache.manifoldcf.crawler.connectors.confluence.model.Page;
+import org.apache.manifoldcf.crawler.connectors.confluence.model.Space;
+import org.apache.manifoldcf.crawler.connectors.confluence.model.Spaces;
+import org.apache.manifoldcf.crawler.connectors.confluence.model.builder.ConfluenceResourceBuilder;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Optional;
+import com.google.common.collect.Lists;
+
+/**
+ * <p>
+ * ConfluenceClient class
+ * </p>
+ * <p>
+ * This class is intended to be used to interact with Confluence REST API
+ * </p>
+ * <p>
+ * There are some methods that make use of the Confluence JSON-RPC 2.0 API, but
+ * until all the methods are ported to the new REST API, we will have to use
+ * them to leverage all the features provided by Confluence
+ * </p>
+ *
+ * @author Antonio David Perez Morales <adperezmorales@gmail.com>
+ *
+ */
+public class ConfluenceClient {
+
+ private static final String VIEW_PERMISSION = "view";
+
+ private static final String CONTENT_PATH = "/rest/api/content";
+ private static final String AUTHORITY_PATH = "/rpc/json-rpc/confluenceservice-v2/";
+ private static final String EXPANDABLE_PARAMETERS = "expand=body.view,metadata.labels,space,history,version";
+ private static final String CHILD_ATTACHMENTS_PATH = "/child/attachment/";
+ private static final String LABEL_PATH = "/label";
+
+ private Logger logger = LoggerFactory.getLogger(ConfluenceClient.class);
+
+ private String protocol;
+ private Integer port;
+ private String host;
+ private String path;
+ private String username;
+ private String password;
+
+ private CloseableHttpClient httpClient;
+ private HttpClientContext httpContext;
+
+ /**
+ * <p>Creates a new client instance using the given parameters</p>
+ * @param protocol the protocol
+ * @param host the host
+ * @param port the port
+ * @param path the path to Confluence instance
+ * @param username the username used to make the requests. Null or empty to use anonymous user
+ * @param password the password
+ * @throws ManifoldCFException
+ */
+ public ConfluenceClient(String protocol, String host, Integer port,
+ String path, String username, String password) throws ManifoldCFException {
+ this.protocol = protocol;
+ this.host = host;
+ this.port = port;
+ this.path = path;
+ this.username = username;
+ this.password = password;
+
+ connect();
+ }
+
+ /**
+ * <p>Connect methods used to initialize the underlying client</p>
+ * @throws ManifoldCFException
+ */
+ private void connect() throws ManifoldCFException {
+
+ int socketTimeout = 900000;
+ int connectionTimeout = 60000;
+
+ javax.net.ssl.SSLSocketFactory httpsSocketFactory = KeystoreManagerFactory.getTrustingSecureSocketFactory();
+ SSLConnectionSocketFactory myFactory = new SSLConnectionSocketFactory(new InterruptibleSocketFactory(httpsSocketFactory,connectionTimeout),
+ SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
+
+ HttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
+
+ // If authentication needed, set that
+ // Preemptive authentication not working
+ /*if (username != null)
+ {
+ CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
+ credentialsProvider.setCredentials(
+ AuthScope.ANY,
+ new UsernamePasswordCredentials(username,password));
+
+ AuthCache authCache = new BasicAuthCache();
+ authCache.put(new HttpHost(host, port), new BasicScheme());
+ httpContext = HttpClientContext.create();
+ httpContext.setCredentialsProvider(credentialsProvider);
+ httpContext.setAuthCache(authCache);
+ }*/
+
+ RequestConfig.Builder requestBuilder = RequestConfig.custom()
+ .setCircularRedirectsAllowed(true)
+ .setSocketTimeout(socketTimeout)
+ .setStaleConnectionCheckEnabled(true)
+ .setExpectContinueEnabled(true)
+ .setConnectTimeout(connectionTimeout)
+ .setConnectionRequestTimeout(socketTimeout);
+
+
+ httpClient = HttpClients.custom()
+ .setConnectionManager(connectionManager)
+ .setMaxConnTotal(1)
+ .disableAutomaticRetries()
+ .setDefaultRequestConfig(requestBuilder.build())
+ .setDefaultSocketConfig(SocketConfig.custom()
+ .setTcpNoDelay(true)
+ .setSoTimeout(socketTimeout)
+ .build())
+ .setSSLSocketFactory(myFactory)
+ .setRequestExecutor(new HttpRequestExecutor(socketTimeout))
+ .setRedirectStrategy(new DefaultRedirectStrategy())
+ .build();
+
+ }
+
+ /**
+ * <p>Close the client. No further requests can be done</p>
+ */
+ public void close() {
+ if (httpClient != null) {
+ try {
+ httpClient.close();
+ } catch (IOException e) {
+ logger.debug("Error closing http connection. Reason: {}",
+ e.getMessage());
+ e.printStackTrace();
+ }
+ }
+ }
+
+ /**
+ * <p>Check method used to test if Confluence instance is up and running</p>
+ *
+ * @return a {@code Boolean} indicating whether the Confluence instance is alive or not
+ *
+ * @throws Exception
+ */
+ public boolean check() throws Exception {
+ HttpResponse response;
+ try {
+ if (httpClient == null) {
+ connect();
+ }
+
+ String url = String.format("%s://%s:%s/%s/%s?limit=1", protocol, host,
+ port, path, CONTENT_PATH);
+ logger.debug(
+ "[Processing] Hitting url: {} for confluence status check fetching : ",
+ "Confluence URL", sanitizeUrl(url));
+ HttpGet httpGet = createGetRequest(url);
+ response = httpClient.execute(httpGet);
+ int statusCode = response.getStatusLine().getStatusCode();
+ if (statusCode != 200)
+ throw new Exception(
+ "[Checking connection] Confluence server appears to be down");
+ else
+ return true;
+ } catch (IOException e) {
+ logger.warn(
+ "[Checking connection] Confluence server appears to be down",
+ e);
+ throw new Exception("Confluence appears to be down", e);
+ }
+ }
+
+ /**
+ * <p>Check method used to test if Confluence instance is up and running when using Authority connector (JSON-RPC API)</p>
+ * <p>This method will be deleted when all JSON-RPC methods are available through the REST API
+ *
+ * @return a {@code Boolean} indicating whether the Confluence instance is alive or not
+ *
+ * @throws Exception
+ */
+ public boolean checkAuth() throws Exception {
+ try {
+ if (httpClient == null) {
+ connect();
+ }
+ getSpaces();
+ return true;
+ } catch (Exception e) {
+ logger.warn(
+ "[Checking connection] Confluence server appears to be down",
+ e);
+ throw e;
+ }
+ }
+ /**
+ * <p>
+ * Create a get request for the given url
+ * </p>
+ *
+ * @param url
+ * the url
+ * @return the created {@code HttpGet} instance
+ */
+ private HttpGet createGetRequest(String url) {
+ String finalUrl = useBasicAuthentication() ? url + "&os_authType=basic": url;
+ String sanitizedUrl = sanitizeUrl(finalUrl);
+ HttpGet httpGet = new HttpGet(sanitizedUrl);
+ httpGet.addHeader("Accept", "application/json");
+ if (useBasicAuthentication()) {
+ httpGet.addHeader(
+ "Authorization",
+ "Basic "
+ + Base64.encodeBase64String(String.format("%s:%s",
+ this.username, this.password).getBytes(
+ Charset.forName("UTF-8"))));
+ }
+ return httpGet;
+ }
+
+ /**
+ * <p>
+ * Get a list of Confluence pages
+ * </p>
+ *
+ * @return a {@code ConfluenceResponse} containing the result pages and
+ * some pagination values
+ * @throws Exception
+ */
+ public ConfluenceResponse<Page> getPages() throws Exception {
+ return getPages(0, 50, Optional.<String> absent());
+ }
+
+ /**
+ * <p>
+ * Get a list of Confluence pages using pagination
+ * </p>
+ *
+ * @param start The start value to get pages from
+ * @param limit The number of pages to get from start
+ * @return a {@code ConfluenceResponse} containing the result pages and
+ * some pagination values
+ * @throws Exception
+ */
+ @SuppressWarnings("unchecked")
+ public ConfluenceResponse<Page> getPages(int start, int limit,
+ Optional<String> space) throws Exception {
+ String url = String.format("%s://%s:%s/%s/%s?limit=%s&start=%s", protocol,
+ host, port, path, CONTENT_PATH, limit, start);
+ if (space.isPresent()) {
+ url = String.format("%s&spaceKey=%s", url, space.get());
+ }
+ return (ConfluenceResponse<Page>) getConfluenceResources(url, Page.builder());
+ }
+
+ /**
+ * <p>Get the {@code ConfluenceResources} from the given url</p>
+ * @param url The url identifying the REST resource to get the documents
+ * @param builder The builder used to build the resources contained in the response
+ * @return a {@code ConfluenceResponse} containing the page results
+ * @throws Exception
+ */
+ private ConfluenceResponse<? extends ConfluenceResource> getConfluenceResources(String url, ConfluenceResourceBuilder<? extends ConfluenceResource> builder) throws Exception {
+ logger.debug("[Processing] Hitting url for get confluence resources: {}", sanitizeUrl(url));
+
+ try {
+ HttpGet httpGet = createGetRequest(url);
+ HttpResponse response = executeRequest(httpGet);
+ ConfluenceResponse<? extends ConfluenceResource> confluenceResponse = responseFromHttpEntity(response
+ .getEntity(), builder);
+ EntityUtils.consume(response.getEntity());
+ return confluenceResponse;
+ } catch (IOException e) {
+ logger.error("[Processing] Failed to get page(s)", e);
+ throw new Exception("Confluence appears to be down", e);
+ }
+ }
+
+ /**
+ * <p>Creates a ConfluenceResponse from the entity returned in the HttpResponse</p>
+ * @param entity the {@code HttpEntity} to extract the response from
+ * @return a {@code ConfluenceResponse} with the requested information
+ * @throws Exception
+ */
+ private <T extends ConfluenceResource> ConfluenceResponse<T> responseFromHttpEntity(HttpEntity entity, ConfluenceResourceBuilder<T> builder)
+ throws Exception {
+ String stringEntity = EntityUtils.toString(entity);
+
+ JSONObject responseObject;
+ try {
+ responseObject = new JSONObject(stringEntity);
+ ConfluenceResponse<T> response = ConfluenceResponse
+ .fromJson(responseObject, builder);
+ if (response.getResults().size() == 0) {
+ logger.debug("[Processing] No {} found in the Confluence response", builder.getType().getSimpleName());
+ }
+
+ return response;
+ } catch (JSONException e) {
+ logger.debug("Error parsing JSON response");
+ throw new Exception();
+ }
+
+ }
+
+ /**
+ * <p>Get the attachments of the given page</p>
+ * @param pageId the page id
+ * @return a {@code ConfluenceResponse} instance containing the attachment results and some pagination values</p>
+ * @throws Exception
+ */
+ public ConfluenceResponse<Attachment> getPageAttachments(String pageId)
+ throws Exception {
+ return getPageAttachments(pageId, 0, 50);
+ }
+
+ /**
+ * <p>Get the attachments of the given page using pagination</p>
+ * @param pageId the page id
+ * @param start The start value to get attachments from
+ * @param limit The number of attachments to get from start
+ * @return a {@code ConfluenceResponse} instance containing the attachment results and some pagination values</p>
+ * @throws Exception
+ */
+ public ConfluenceResponse<Attachment> getPageAttachments(String pageId, int start,
+ int limit) throws Exception {
+ String url = String.format("%s://%s:%s/%s/%s/%s%s?limit=%s&start=%s",
+ protocol, host, port, path, CONTENT_PATH, pageId, CHILD_ATTACHMENTS_PATH,
+ limit, start);
+ @SuppressWarnings("unchecked")
+ ConfluenceResponse<Attachment> confluenceResources = (ConfluenceResponse<Attachment>) getConfluenceResources(url, Attachment.builder());
+ return confluenceResources;
+ }
+
+ /**
+ * <p>
+ * Gets a specific attachment contained in the specific page
+ * </p>
+ *
+ * @param attachmentId
+ * @param pageId
+ * @return the {@code Attachment} instance
+ */
+ public Attachment getAttachment(String attachmentId) {
+ String url = String
+ .format("%s://%s:%s/%s/%s/%s?%s",
+ protocol, host, port, path, CONTENT_PATH, attachmentId, EXPANDABLE_PARAMETERS);
+ logger.debug(
+ "[Processing] Hitting url for getting document content : {}",
+ sanitizeUrl(url));
+ try {
+ HttpGet httpGet = createGetRequest(url);
+ HttpResponse response = executeRequest(httpGet);
+ HttpEntity entity = response.getEntity();
+ MutableAttachment attachment = attachmentFromHttpEntity(entity);
+ EntityUtils.consume(entity);
+ retrieveAndSetAttachmentContent(attachment);
+ return attachment;
+ } catch (Exception e) {
+ logger.error("[Processing] Failed to get attachment {}. Error: {}",
+ url, e.getMessage());
+ }
+
+ return new Attachment();
+ }
+
+ /**
+ * <p>
+ * Downloads and retrieves the attachment content, setting it in the given
+ * {@code Attachment} instance
+ * </p>
+ *
+ * @param attachment
+ * the {@code Attachment} instance to download and set the
+ * content
+ * @throws Exception
+ */
+ private void retrieveAndSetAttachmentContent(MutableAttachment attachment)
+ throws Exception {
+ StringBuilder sb = new StringBuilder();
+ sb.append(attachment.getBaseUrl()).append(attachment.getUrlContext())
+ .append(attachment.getDownloadUrl());
+ String url = sanitizeUrl(sb.toString());
+ logger.debug(
+ "[Processing] Hitting url for getting attachment content : {}",
+ url);
+ try {
+ HttpGet httpGet = createGetRequest(url);
+ HttpResponse response = executeRequest(httpGet);
+ attachment.setLength(response.getEntity().getContentLength());
+ byte[] byteContent = IOUtils.toByteArray(response.getEntity()
+ .getContent());
+ EntityUtils.consumeQuietly(response.getEntity());
+ attachment.setContentStream(new ByteArrayInputStream(byteContent));
+ } catch (Exception e) {
+
+ logger.error(
+ "[Processing] Failed to get attachment content from {}. Error: {}",
+ url, e.getMessage());
+ throw e;
+ }
+
+ }
+
+
+ /**
+ * <p>Get a Confluence page identified by its id</p>
+ * @param pageId the page id
+ * @return the Confluence page
+ */
+ public Page getPage(String pageId) {
+ String url = String
+ .format("%s://%s:%s/%s/%s/%s?%s",
+ protocol, host, port, path, CONTENT_PATH, pageId, EXPANDABLE_PARAMETERS);
+ url = sanitizeUrl(url);
+ logger.debug(
+ "[Processing] Hitting url for getting document content : {}",
+ url);
+ try {
+ HttpGet httpGet = createGetRequest(url);
+ HttpResponse response = executeRequest(httpGet);
+ HttpEntity entity = response.getEntity();
+ MutablePage page = pageFromHttpEntity(entity);
+ EntityUtils.consume(entity);
+ List<Label> labels = getLabels(pageId);
+ page.setLabels(labels);
+ return page;
+ } catch (Exception e) {
+ logger.error("[Processing] Failed to get page {0}. Error: {1}",
+ url, e.getMessage());
+ }
+
+ return new Page();
+ }
+
+ /**
+ * <p>Get the labels of a specific page</p>
+ * @param pageId The pageId to get the labels
+ * @return a {@code List<Label>} of labels
+ */
+ public List<Label> getLabels(String pageId) {
+
+ List<Label> labels = Lists.newArrayList();
+ int lastStart = 0;
+ int limit = 50;
+ boolean isLast = false;
+ do {
+ String url = String
+ .format("%s://%s:%s/%s/%s/%s/%s?start=%s&limit=%s",
+ protocol, host, port, path, CONTENT_PATH, pageId, LABEL_PATH, lastStart, limit);
+ url = sanitizeUrl(url);
+ logger.debug(
+ "[Processing] Hitting url for getting page labels : {}",
+ url);
+ try {
+ @SuppressWarnings("unchecked")
+ ConfluenceResponse<Label> response = (ConfluenceResponse<Label>) getConfluenceResources(url, Label.builder());
+ labels.addAll(response.getResults());
+ lastStart += response.getResults().size();
+ isLast = response.isLast();
+ } catch (Exception e) {
+ logger.debug("Error getting labels for page {}. Reason: {}", pageId, e.getMessage());
+ }
+ }
+ while(!isLast);
+
+ return labels;
+ }
+
+ /**
+ *
+ * @param username
+ * @return
+ * @throws Exception
+ */
+ public ConfluenceUser getUserAuthorities(String username) throws Exception {
+ List<String> authorities = Lists.<String>newArrayList();
+ Spaces spaces = getSpaces();
+ for(Space space: spaces) {
+ List<String> permissions = getSpacePermissionsForUser(space, username);
+ if(permissions.contains(VIEW_PERMISSION)) {
+ authorities.add(space.getKey());
+ }
+ }
+
+ return new ConfluenceUser(username, authorities);
+
+ }
+
+ private HttpPost createPostRequest(String url) {
+ HttpPost httpPost = new HttpPost(url);
+ httpPost.addHeader("Accept", "application/json");
+ httpPost.addHeader("Content-Type", "application/json");
+ if (useBasicAuthentication()) {
+ httpPost.addHeader(
+ "Authorization",
+ "Basic "
+ + Base64.encodeBase64String(String.format("%s:%s",
+ this.username, this.password).getBytes(
+ Charset.forName("UTF-8"))));
+ }
+ return httpPost;
+ }
+
+ /**
+ * <p>Execute the given {@code HttpUriRequest} using the configured client</p>
+ * @param request the {@code HttpUriRequest} to be executed
+ * @return the {@code HttpResponse} object returned from the server
+ * @throws Exception
+ */
+ private HttpResponse executeRequest(HttpUriRequest request)
+ throws Exception {
+ String url = request.getURI().toString();
+ logger.debug(
+ "[Processing] Hitting url for getting document content : {}",
+ url);
+
+ try {
+ HttpResponse response = httpClient.execute(request, httpContext);
+ if (response.getStatusLine().getStatusCode() != 200) {
+ throw new Exception("Confluence error. "
+ + response.getStatusLine().getStatusCode() + " "
+ + response.getStatusLine().getReasonPhrase());
+ }
+ return response;
+ } catch (Exception e) {
+ logger.error("[Processing] Failed to get page {}. Error: {}",
+ url, e.getMessage());
+ throw e;
+ }
+ }
+
+ /**
+ * <p>Creates a Confluence page object from the given entity returned by the server</p>
+ * @param entity the {@code HttpEntity} to create the {@code MutablePage} from
+ * @return the Confluence page instance
+ * @throws Exception
+ */
+ private MutablePage pageFromHttpEntity(HttpEntity entity) throws Exception {
+ String stringEntity = EntityUtils.toString(entity);
+
+ JSONObject responseObject;
+ try {
+ responseObject = new JSONObject(stringEntity);
+ @SuppressWarnings("unchecked")
+ MutablePage response = ((ConfluenceResourceBuilder<MutablePage>)MutablePage.builder()).fromJson(responseObject, new MutablePage());
+ return response;
+ } catch (JSONException e) {
+ logger.debug("Error parsing JSON page response data");
+ throw new Exception("Error parsing JSON page response data");
+ }
+ }
+
+ /**
+ * <p>Creates a {@code MutableAttachment} object from the given entity returned by the server</p>
+ * @param entity the {@code HttpEntity} to create the {@code MutableAttachment} from
+ * @return the Confluence MutableAttachment instance
+ * @throws Exception
+ */
+ private MutableAttachment attachmentFromHttpEntity(HttpEntity entity)
+ throws Exception {
+ String stringEntity = EntityUtils.toString(entity);
+ JSONObject responseObject;
+ try {
+ responseObject = new JSONObject(stringEntity);
+ MutableAttachment response = (MutableAttachment) Attachment
+ .builder()
+ .fromJson(responseObject, new MutableAttachment());
+ return response;
+ } catch (JSONException e) {
+ logger.debug("Error parsing JSON page response data");
+ throw new Exception("Error parsing JSON page response data");
+ }
+ }
+
+ /**
+ * <p>Method to check if basic authentication must be used</p>
+ * @return {@code Boolean} indicating whether basic authentication must be used or not
+ */
+ private boolean useBasicAuthentication() {
+ return this.username != null && !"".equals(username)
+ && this.password != null;
+ }
+
+ /**
+ * <p>
+ * Sanitize the given url replacing the appearance of more than one slash by
+ * only one slash
+ * </p>
+ *
+ * @param url
+ * The url to sanitize
+ * @return the sanitized url
+ */
+ private String sanitizeUrl(String url) {
+ int colonIndex = url.indexOf(":");
+ String urlWithoutProtocol = url.startsWith("http") ? url.substring(colonIndex+3) : url;
+ String sanitizedUrl = urlWithoutProtocol.replaceAll("\\/+", "/");
+ return url.substring(0,colonIndex) + "://" + sanitizedUrl;
+ }
+
+ private Spaces getSpaces() throws Exception {
+ String url = String.format("%s://%s:%s%s%sgetSpaces", protocol, host,
+ port, path, AUTHORITY_PATH);
+
+ logger.debug(
+ "[Processing] Hitting url for getting Confluence spaces : {}",
+ url);
+
+ HttpPost httpPost = createPostRequest(url);
+ httpPost.setEntity(new StringEntity("[]"));
+ HttpResponse response = httpClient.execute(httpPost);
+ if (response.getStatusLine().getStatusCode() != 200) {
+ throw new ConfluenceException("Confluence error. "
+ + response.getStatusLine().getStatusCode() + " "
+ + response.getStatusLine().getReasonPhrase());
+ }
+ HttpEntity entity = response.getEntity();
+ Spaces spaces = spacesFromHttpEntity(entity);
+ EntityUtils.consume(entity);
+ return spaces;
+ }
+
+ private List<String> getSpacePermissionsForUser(Space space, String username) throws Exception {
+ String url = String.format("%s://%s:%s%s%sgetPermissionsForUser", protocol, host,
+ port, path, AUTHORITY_PATH);
+
+ logger.debug(
+ "[Processing] Hitting url {} for getting Confluence permissions for user {} in space {}",
+ url, username, space.getKey());
+
+ HttpPost httpPost = createPostRequest(url);
+ JSONArray jsonArray = new JSONArray();
+ jsonArray.put(space.getKey());
+ jsonArray.put(username);
+ StringEntity stringEntity = new StringEntity(jsonArray.toString());
+ httpPost.setEntity(stringEntity);
+ HttpResponse response = httpClient.execute(httpPost);
+ if (response.getStatusLine().getStatusCode() != 200) {
+ throw new ConfluenceException("Confluence error. "
+ + response.getStatusLine().getStatusCode() + " "
+ + response.getStatusLine().getReasonPhrase());
+ }
+ HttpEntity entity = response.getEntity();
+ List<String> permissions = permissionsFromHttpEntity(entity);
+ EntityUtils.consume(entity);
+ return permissions;
+ }
+
+ private Spaces spacesFromHttpEntity(HttpEntity entity) throws Exception {
+ String stringEntity = EntityUtils.toString(entity);
+
+ JSONArray responseObject;
+ try {
+ responseObject = new JSONArray(stringEntity);
+ Spaces response = Spaces.fromJson(responseObject);
+
+ return response;
+ } catch (JSONException e) {
+ logger.debug("Error parsing JSON spaces response data");
+ throw new Exception("Error parsing JSON spaces response data");
+ }
+
+ }
+
+ private List<String> permissionsFromHttpEntity(HttpEntity entity) throws Exception {
+ String stringEntity = EntityUtils.toString(entity);
+
+ JSONArray responseObject;
+ List<String> permissions = Lists.newArrayList();
+ try {
+ responseObject = new JSONArray(stringEntity);
+ for(int i=0,len=responseObject.length();i<len;i++) {
+ permissions.add(responseObject.getString(i));
+ }
+
+ return permissions;
+ } catch (JSONException e) {
+ logger.debug("Error parsing JSON space permissions response data");
+ throw new Exception("Error parsing JSON space permissions respnse data");
+ }
+
+ }
+}
diff --git a/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/exception/ConfluenceException.java b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/exception/ConfluenceException.java
new file mode 100644
index 0000000..29ce4af
--- /dev/null
+++ b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/exception/ConfluenceException.java
@@ -0,0 +1,17 @@
+package org.apache.manifoldcf.crawler.connectors.confluence.exception;
+
+public class ConfluenceException extends Exception {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 5903550079897330304L;
+
+ public ConfluenceException(String message) {
+ super(message);
+ }
+
+ public ConfluenceException(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/exception/PageNotFoundException.java b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/exception/PageNotFoundException.java
new file mode 100644
index 0000000..d20742c
--- /dev/null
+++ b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/exception/PageNotFoundException.java
@@ -0,0 +1,14 @@
+package org.apache.manifoldcf.crawler.connectors.confluence.exception;
+
+/**
+ *
+ * @author Antonio David Perez Morales <adperezmorales@gmail.com>
+ *
+ */
+public class PageNotFoundException extends Exception {
+
+ /**
+ *
+ */
+ private static final long serialVersionUID = 1L;
+}
\ No newline at end of file
diff --git a/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/Attachment.java b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/Attachment.java
new file mode 100644
index 0000000..c05a81c
--- /dev/null
+++ b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/Attachment.java
@@ -0,0 +1,113 @@
+package org.apache.manifoldcf.crawler.connectors.confluence.model;
+
+import java.io.InputStream;
+import java.util.Map;
+
+import org.apache.manifoldcf.crawler.connectors.confluence.model.builder.ConfluenceResourceBuilder;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+/**
+ * <p>
+ * Attachment class
+ * </p>
+ * <p>
+ * Represents a Confluence Attachment
+ * </p>
+ *
+ * @author Antonio David Perez Morales <adperezmorales@gmail.com>
+ */
+public class Attachment extends Page {
+
+ protected static final String KEY_DOWNLOAD = "download";
+ protected static final String KEY_EXTENSIONS = "extensions";
+ protected String downloadUrl;
+ protected InputStream contentStream;
+
+ public static ConfluenceResourceBuilder<Attachment> builder() {
+ return new AttachmentBuilder();
+ }
+
+ public String getDownloadUrl() {
+ return this.downloadUrl;
+ }
+
+ @Override
+ public boolean hasContent() {
+ return (this.length > 0 && this.hasContentStream()) || (this.downloadUrl != null && !this.downloadUrl.isEmpty());
+ }
+
+ public Boolean hasContentStream() {
+ return this.contentStream != null;
+ }
+
+ @Override
+ public InputStream getContentStream() {
+ if(hasContentStream()) {
+ return this.contentStream;
+ }
+ return super.getContentStream();
+ }
+
+ @Override
+ protected void refineMetadata(Map<String, Object> metadata) {
+ super.refineMetadata(metadata);
+ metadata.put("downloadUrl", this.getBaseUrl() + this.getUrlContext()
+ + downloadUrl);
+ }
+
+ /**
+ * <p>
+ * AttachmentBuilder internal class
+ * </p>
+ * <p>
+ * Used to build Attachments
+ * </p>
+ *
+ * @author Antonio David Perez Morales <adperezmorales@gmail.com>
+ *
+ */
+ public static class AttachmentBuilder implements ConfluenceResourceBuilder<Attachment>{
+
+ @Override
+ public Attachment fromJson(JSONObject jsonPage) {
+ return fromJson(jsonPage, new Attachment());
+ }
+
+ @SuppressWarnings("unchecked")
+ public Attachment fromJson(JSONObject jsonPage, Attachment attachment) {
+ ((ConfluenceResourceBuilder<Page>) Page.builder()).fromJson(jsonPage, attachment);
+
+ try {
+ /*
+ * Download URL
+ */
+
+ JSONObject links = (JSONObject) jsonPage.get(Page.KEY_LINKS);
+ if (links != null) {
+ attachment.downloadUrl = links.optString(KEY_DOWNLOAD, "");
+ }
+
+ /*
+ * Extensions
+ */
+ JSONObject extensions = (JSONObject) jsonPage
+ .get(KEY_EXTENSIONS);
+ if (extensions != null) {
+ attachment.mediaType = extensions.optString(
+ Page.KEY_MEDIATYPE, "");
+ }
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+
+ return attachment;
+ }
+
+ @Override
+ public Class<Attachment> getType() {
+ return Attachment.class;
+ }
+
+ }
+}
diff --git a/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/ConfluenceResource.java b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/ConfluenceResource.java
new file mode 100644
index 0000000..27af73d
--- /dev/null
+++ b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/ConfluenceResource.java
@@ -0,0 +1,12 @@
+package org.apache.manifoldcf.crawler.connectors.confluence.model;
+
+/**
+ * <p>ConfluenceResource class</p>
+ * <p>Used as base class for other classes like Page and Attachments</p>
+ *
+ * @author Antonio David Perez Morales <adperezmorales@gmail.com>
+ *
+ */
+public class ConfluenceResource {
+
+}
diff --git a/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/ConfluenceResponse.java b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/ConfluenceResponse.java
new file mode 100644
index 0000000..ca1a3c9
--- /dev/null
+++ b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/ConfluenceResponse.java
@@ -0,0 +1,68 @@
+package org.apache.manifoldcf.crawler.connectors.confluence.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.manifoldcf.crawler.connectors.confluence.model.builder.ConfluenceResourceBuilder;
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+public class ConfluenceResponse<T extends ConfluenceResource> {
+
+ private List<T> results;
+ private int start;
+ private int limit;
+ private Boolean isLast;
+
+ public ConfluenceResponse(List<T> results, int start, int limit, Boolean isLast) {
+ this.results = results;
+ this.start = start;
+ this.limit = limit;
+ this.isLast = isLast;
+ }
+
+ public List<T> getResults() {
+ return this.results;
+ }
+
+ public int getStart() {
+ return this.start;
+ }
+
+ public int getLimit() {
+ return this.limit;
+ }
+
+ public Boolean isLast() {
+ return isLast;
+ }
+
+ public static <T extends ConfluenceResource> ConfluenceResponse<T> fromJson(JSONObject response, ConfluenceResourceBuilder<T> builder) {
+ List<T> resources = new ArrayList<T>();
+ try {
+ JSONArray jsonArray = response.getJSONArray("results");
+ for(int i=0,size=jsonArray.length(); i<size;i++) {
+ JSONObject jsonPage = jsonArray.getJSONObject(i);
+ T resource = (T) builder.fromJson(jsonPage);
+ resources.add(resource);
+ }
+
+ int limit = response.getInt("limit");
+ int start = response.getInt("start");
+ Boolean isLast = false;
+ JSONObject links = response.getJSONObject("_links");
+ if(links != null) {
+ isLast = links.optString("next", "undefined").equalsIgnoreCase("undefined");
+ }
+
+ return new ConfluenceResponse<T>(resources, start, limit, isLast);
+
+ } catch (JSONException e) {
+ // TODO Auto-generated catch block
+ e.printStackTrace();
+ }
+
+ return new ConfluenceResponse<T>(new ArrayList<T>(), 0,0,false);
+ }
+}
diff --git a/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/ConfluenceUser.java b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/ConfluenceUser.java
new file mode 100644
index 0000000..6683dff
--- /dev/null
+++ b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/ConfluenceUser.java
@@ -0,0 +1,28 @@
+package org.apache.manifoldcf.crawler.connectors.confluence.model;
+
+import java.util.List;
+
+/**
+ * <p>ConfluenceUser class</p>
+ * <p>Represents a Confluence user</p>
+ *
+ * @author Antonio David Perez Morales <adperezmorales@gmail.com>
+ *
+ */
+public class ConfluenceUser {
+ private final String username;
+ private final List<String> authorities;
+
+ public ConfluenceUser(String username, List<String> authorities) {
+ this.username = username;
+ this.authorities = authorities;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public List<String> getAuthorities() {
+ return authorities;
+ }
+ }
diff --git a/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/Label.java b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/Label.java
new file mode 100644
index 0000000..de0c7cd
--- /dev/null
+++ b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/Label.java
@@ -0,0 +1,86 @@
+package org.apache.manifoldcf.crawler.connectors.confluence.model;
+
+import org.apache.manifoldcf.crawler.connectors.confluence.model.builder.ConfluenceResourceBuilder;
+import org.json.JSONObject;
+
+/**
+ * <p>
+ * Label class
+ * </p>
+ * <p>
+ * Represents a Confluence Label
+ * </p>
+ *
+ * @author Antonio David Perez Morales <adperezmorales@gmail.com>
+ */
+public class Label extends ConfluenceResource{
+
+ protected static final String KEY_LINKS = "_links";
+ protected static final String KEY_ID = "id";
+ protected static final String KEY_SELF = "self";
+ protected static final String KEY_PREFIX = "prefix";
+ protected static final String KEY_NAME = "name";
+
+ protected String id;
+ protected String prefix;
+ protected String name;
+
+ @SuppressWarnings("unused")
+ private JSONObject delegated;
+
+ public Label() {
+
+ }
+
+ public String getId() {
+ return this.id;
+ }
+
+ public String getPrefix() {
+ return this.prefix;
+ }
+
+ public String getName() {
+ return this.name;
+ }
+
+ public static LabelBuilder builder() {
+ return new LabelBuilder();
+ }
+
+ /**
+ * <p>
+ * LabelBuilder internal class
+ * </p>
+ * <p>
+ * Used to build Labels
+ * </p>
+ *
+ * @author Antonio David Perez Morales <adperezmorales@gmail.com>
+ *
+ */
+ public static class LabelBuilder implements ConfluenceResourceBuilder<Label>{
+
+ public Label fromJson(JSONObject jsonLabel) {
+ return fromJson(jsonLabel, new Label());
+ }
+
+ public Label fromJson(JSONObject jsonPage, Label label) {
+
+ label.id = jsonPage.optString(KEY_ID, "");
+ label.prefix = jsonPage.optString(KEY_PREFIX, "");
+ label.name = jsonPage.optString(KEY_NAME, "");
+
+ label.delegated = jsonPage;
+
+ return label;
+
+ }
+
+ @Override
+ public Class<Label> getType() {
+ return Label.class;
+ }
+
+ }
+}
diff --git a/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/MutableAttachment.java b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/MutableAttachment.java
new file mode 100644
index 0000000..e30aaef
--- /dev/null
+++ b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/MutableAttachment.java
@@ -0,0 +1,94 @@
+package org.apache.manifoldcf.crawler.connectors.confluence.model;
+
+import java.io.InputStream;
+import java.util.Date;
+
+/**
+ * <p>
+ * Mutable Attachment class
+ * </p>
+ * <p>
+ * Represents a Confluence Attachment which can be mutated
+ * </p>
+ *
+ * @author Antonio David Perez Morales <adperezmorales@gmail.com>
+ */
+public class MutableAttachment extends Attachment {
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public void setSpace(String space) {
+ this.space = space;
+ }
+
+ public void setBaseUrl(String baseUrl) {
+ this.baseUrl = baseUrl;
+ }
+
+ public void setUrlContext(String urlContext) {
+ this.urlContext = urlContext;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ public void setWebUrl(String webUrl) {
+ this.webUrl = webUrl;
+ }
+
+ public void setCreatedDate(Date createdDate) {
+ this.createdDate = createdDate;
+ }
+
+ public void setLastModified(Date lastModified) {
+ this.lastModified = lastModified;
+ }
+
+ public void setType(PageType type) {
+ this.type = type;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public void setVersion(int version) {
+ this.version = version;
+ }
+
+ public void setCreator(String creator) {
+ this.creator = creator;
+ }
+
+ public void setCreatorUsername(String creatorUsername) {
+ this.creatorUsername = creatorUsername;
+ }
+
+ public void setLastModifier(String lastModifier) {
+ this.lastModifier = lastModifier;
+ }
+
+ public void setLastModifierUsername(String lastModifierUsername) {
+ this.lastModifierUsername = lastModifierUsername;
+ }
+
+ public void setMediaType(String mediaType) {
+ this.mediaType = mediaType;
+ }
+
+ public void setLength(long length) {
+ this.length = length;
+ }
+
+ public void setDownloadUrl(String downloadUrl) {
+ this.downloadUrl = downloadUrl;
+ }
+
+ public void setContentStream(InputStream contentStream) {
+ this.contentStream = contentStream;
+ }
+
+}
diff --git a/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/MutablePage.java b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/MutablePage.java
new file mode 100644
index 0000000..78c8e6a
--- /dev/null
+++ b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/MutablePage.java
@@ -0,0 +1,99 @@
+package org.apache.manifoldcf.crawler.connectors.confluence.model;
+
+import java.util.Date;
+import java.util.List;
+
+/**
+ * <p>
+ * MutablePage class
+ * </p>
+ * <p>
+ * Represents a Confluence Page which is mutable unlike {@code Page} class which can be also initialized using the PageBuilder obtained from
+ * <code>Page.builder()</code> method
+ * </p>
+ *
+ * @author Antonio David Perez Morales <adperezmorales@gmail.com>
+ */
+public class MutablePage extends Page {
+
+ public MutablePage() {
+
+ }
+
+ public void setId(String id) {
+ this.id = id;
+ }
+
+ public void setSpace(String space) {
+ this.space = space;
+ }
+
+ public void setBaseUrl(String baseUrl) {
+ this.baseUrl = baseUrl;
+ }
+
+ public void setUrlContext(String urlContext) {
+ this.urlContext = urlContext;
+ }
+
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ public void setWebUrl(String webUrl) {
+ this.webUrl = webUrl;
+ }
+
+ public void setCreatedDate(Date createdDate) {
+ this.createdDate = createdDate;
+ }
+
+ public void setLastModified(Date lastModified) {
+ this.lastModified = lastModified;
+ }
+
+ public void setType(PageType type) {
+ this.type = type;
+ }
+
+ public void setTitle(String title) {
+ this.title = title;
+ }
+
+ public void setVersion(int version) {
+ this.version = version;
+ }
+
+ public void setCreator(String creator) {
+ this.creator = creator;
+ }
+
+ public void setCreatorUsername(String creatorUsername) {
+ this.creatorUsername = creatorUsername;
+ }
+
+ public void setLastModifier(String lastModifier) {
+ this.lastModifier = lastModifier;
+ }
+
+ public void setLastModifierUsername(String lastModifierUsername) {
+ this.lastModifierUsername = lastModifierUsername;
+ }
+
+ public void setMediaType(String mediaType) {
+ this.mediaType = mediaType;
+ }
+
+ public void setLength(long length) {
+ this.length = length;
+ }
+
+ public void setContent(String content) {
+ this.content = content;
+ }
+
+ public void setLabels(List<Label> labels) {
+ this.labels = labels;
+ }
+
+}
diff --git a/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/Page.java b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/Page.java
new file mode 100644
index 0000000..8039805
--- /dev/null
+++ b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/Page.java
@@ -0,0 +1,357 @@
+package org.apache.manifoldcf.crawler.connectors.confluence.model;
+
+import java.io.ByteArrayInputStream;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.Date;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.manifoldcf.core.common.DateParser;
+import org.apache.manifoldcf.crawler.connectors.confluence.model.builder.ConfluenceResourceBuilder;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import com.google.common.base.Function;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Maps;
+
+/**
+ * <p>
+ * Page class
+ * </p>
+ * <p>
+ * Represents a Confluence Page
+ * </p>
+ *
+ * @author Antonio David Perez Morales <adperezmorales@gmail.com>
+ */
+public class Page extends ConfluenceResource{
+
+ protected static final String KEY_LINKS = "_links";
+ protected static final String KEY_ID = "id";
+ protected static final String KEY_SELF = "self";
+ protected static final String KEY_WEBUI = "webui";
+ protected static final String KEY_BASE = "base";
+ protected static final String KEY_CONTEXT = "context";
+ protected static final String KEY_KEY = "key";
+ protected static final String KEY_TITLE = "title";
+ protected static final String KEY_BODY = "body";
+ protected static final String KEY_VIEW = "view";
+ protected static final String KEY_VALUE = "value";
+ protected static final String KEY_SPACE = "space";
+ protected static final String KEY_HISTORY = "history";
+ protected static final String KEY_CREATED_DATE = "createdDate";
+ protected static final String KEY_CREATED_BY = "createdBy";
+ protected static final String KEY_BY = "by";
+ protected static final String KEY_TYPE = "type";
+ protected static final String KEY_DISPLAY_NAME = "displayName";
+ protected static final String KEY_USER_NAME = "username";
+ protected static final String KEY_VERSION = "version";
+ protected static final String KEY_WHEN = "when";
+ protected static final String KEY_MEDIATYPE = "mediaType";
+
+ private static final String PAGE_ID = "confluenceId";
+ private static final String PAGE_URL = "url";
+ private static final String PAGE_WEBURL = "webUrl";
+ private static final String PAGE_LAST_MODIFIED = "lastModified";
+ private static final String PAGE_CREATOR = "creator";
+ private static final String PAGE_CREATOR_USERNAME = "creatorUsername";
+ private static final String PAGE_LAST_MODIFIER = "lastModifier";
+ private static final String PAGE_LAST_MODIFIER_USERNAME = "lastModifierUsername";
+ private static final String PAGE_SIZE = "size";
+ private static final String PAGE_LABEL = "label";
+
+ protected String id;
+ protected String space;
+ protected String baseUrl;
+ protected String urlContext;
+ protected String url;
+ protected String webUrl;
+ protected Date createdDate;
+ protected Date lastModified;
+ protected PageType type;
+ protected String title;
+ protected int version;
+ protected String creator;
+ protected String creatorUsername;
+ protected String lastModifier;
+ protected String lastModifierUsername;
+ protected String mediaType = "text/html";
+ protected long length;
+ protected String content;
+ protected List<Label> labels = Lists.newArrayList();
+
+ @SuppressWarnings("unused")
+ private JSONObject delegated;
+
+ public Page() {
+
+ }
+
+ public String getContent() {
+ return this.content;
+ }
+
+ public String getId() {
+ return this.id;
+ }
+
+ public PageType getType() {
+ return this.type;
+ }
+
+ public String getMediaType() {
+ return this.mediaType;
+ }
+
+ public int getVersion() {
+ return this.version;
+ }
+
+ public String getTitle() {
+ return this.title;
+ }
+
+ public String getBaseUrl() {
+ return this.baseUrl;
+ }
+
+ public String getUrlContext() {
+ return this.urlContext;
+ }
+
+ public String getWebUrl() {
+ return this.webUrl;
+ }
+
+ public String getUrl() {
+ return this.url;
+ }
+
+ public String getSpace() {
+ return this.space;
+ }
+
+ public String getCreator() {
+ return this.creator;
+ }
+
+ public String getCreatorUsername() {
+ return this.creatorUsername;
+ }
+
+ public String getLastModifier() {
+ return this.lastModifier;
+ }
+
+ public String getLastModifierUsername() {
+ return this.lastModifierUsername;
+ }
+
+ public Date getCreatedDate() {
+ return this.createdDate;
+ }
+
+ public Date getLastModifiedDate() {
+ return this.lastModified;
+ }
+
+ public long getLength() {
+ return this.length;
+ }
+
+ public boolean hasContent() {
+ return this.length > 0 && this.content != null;
+ }
+
+ public InputStream getContentStream() {
+ String contentStream = content != null ? content : "";
+ return new ByteArrayInputStream(
+ contentStream.getBytes(StandardCharsets.UTF_8));
+ }
+
+ public List<Label> getLabels() {
+ return this.labels;
+ }
+
+ public Map<String, Object> getMetadataAsMap() {
+ Map<String, Object> pageMetadata = Maps.newHashMap();
+ pageMetadata.put(KEY_ID, this.id);
+ pageMetadata.put(PAGE_ID, this.id);
+ pageMetadata.put(KEY_TYPE, this.type.toString());
+ pageMetadata.put(KEY_TITLE, this.title);
+ pageMetadata.put(KEY_SPACE, this.space);
+ pageMetadata.put(PAGE_URL, this.url);
+ pageMetadata.put(PAGE_WEBURL, this.webUrl);
+ pageMetadata.put(KEY_CREATED_DATE,
+ DateParser.formatISO8601Date(this.createdDate));
+ pageMetadata.put(PAGE_LAST_MODIFIED,
+ DateParser.formatISO8601Date(this.lastModified));
+ pageMetadata.put(KEY_MEDIATYPE, this.mediaType);
+ pageMetadata.put(KEY_VERSION, String.valueOf(this.version));
+ pageMetadata.put(PAGE_CREATOR, this.creator);
+ pageMetadata.put(PAGE_CREATOR_USERNAME, this.creatorUsername);
+ pageMetadata.put(PAGE_LAST_MODIFIER, this.lastModifier);
+ pageMetadata
+ .put(PAGE_LAST_MODIFIER_USERNAME, this.lastModifierUsername);
+ pageMetadata.put(PAGE_SIZE, String.valueOf(this.length));
+
+ putLabelsOnMetadataMap(pageMetadata);
+ refineMetadata(pageMetadata);
+ return pageMetadata;
+ }
+
+ /**
+ * <p>Put the page labels on the metadata map</p>
+ * @param pageMetadata
+ */
+ private void putLabelsOnMetadataMap(Map<String, Object> pageMetadata) {
+ if(this.labels == null || this.labels.isEmpty()) {
+ return;
+ }
+
+ Iterable<String> labelsString = Iterables.transform(this.labels, new Function<Label, String>() {
+ @Override
+ public String apply(Label input) {
+ return input.getName();
+ }
+ });
+
+ pageMetadata.put(PAGE_LABEL, Lists.newArrayList(labelsString));
+
+ }
+
+ /**
+ * <p>
+ * Used to be overwritten by child classes to add more metadata to the map
+ * </p>
+ *
+ * @param metadata
+ */
+ protected void refineMetadata(Map<String, Object> metadata) {
+ }
+
+ public static ConfluenceResourceBuilder<? extends Page> builder() {
+ return new PageBuilder();
+ }
+
+ /**
+ * <p>PageBuilder internal class</p>
+ * <p>Used to build pages</p>
+ * @author Antonio David Perez Morales <adperezmorales@gmail.com>
+ *
+ */
+ public static class PageBuilder implements ConfluenceResourceBuilder<Page>{
+
+ public Page fromJson(JSONObject jsonPage) {
+ return fromJson(jsonPage, new Page());
+ }
+
+ public Page fromJson(JSONObject jsonPage, Page page) {
+
+ try {
+ String id = jsonPage.getString(KEY_ID);
+ String type = jsonPage.getString(KEY_TYPE);
+ String title = jsonPage.getString(KEY_TITLE);
+
+ page.delegated = jsonPage;
+
+ /* Init Page fields */
+ page.id = id;
+ page.type = PageType.fromName(type);
+ page.title = title;
+
+ page.space = processSpace(jsonPage);
+
+ /*
+ * Url & WebUrl
+ */
+ JSONObject links = (JSONObject) jsonPage.get(KEY_LINKS);
+ if (links != null) {
+ page.url = links.optString(KEY_SELF, "");
+ String webUrl = (String) links.optString(KEY_WEBUI, "");
+ page.urlContext = (String) links.optString(KEY_CONTEXT, "");
+ page.baseUrl = (String) links.optString(KEY_BASE, "");
+ page.webUrl = page.baseUrl + page.urlContext + webUrl;
+
+ }
+
+ /*
+ * Created By and created Date
+ */
+ JSONObject history = (JSONObject) jsonPage
+ .optJSONObject(KEY_HISTORY);
+ if (history != null) {
+
+ page.createdDate = DateParser.parseISO8601Date(history
+ .optString(KEY_CREATED_DATE, ""));
+ JSONObject createdBy = (JSONObject) history
+ .optJSONObject(KEY_CREATED_BY);
+ if (createdBy != null) {
+ page.creator = createdBy
+ .optString(KEY_DISPLAY_NAME, "");
+ page.creatorUsername = createdBy.optString(
+ KEY_USER_NAME, "");
+ }
+
+ }
+
+ /*
+ * Last modifier and Last modified date
+ */
+ JSONObject version = (JSONObject) jsonPage
+ .optJSONObject(KEY_VERSION);
+ if (version != null) {
+ JSONObject by = version.getJSONObject(KEY_BY);
+ if (by != null) {
+ page.lastModifier = by.optString(KEY_DISPLAY_NAME);
+ page.lastModifierUsername = by.optString(KEY_USER_NAME,
+ "");
+ }
+
+ page.lastModified = DateParser.parseISO8601Date(version
+ .optString(KEY_WHEN, ""));
+ }
+
+ /*
+ * Page Content
+ */
+ JSONObject body = (JSONObject) jsonPage.optJSONObject(KEY_BODY);
+ if (body != null) {
+ JSONObject view = (JSONObject) body.optJSONObject(KEY_VIEW);
+ if (view != null) {
+ page.content = view.optString(KEY_VALUE, null);
+ page.length = page.content.getBytes().length;
+ }
+ }
+
+ return page;
+
+ } catch (JSONException e) {
+ e.printStackTrace();
+ }
+
+ return new Page();
+
+ }
+
+ private static String processSpace(JSONObject page) {
+ /* Page */
+ try {
+ JSONObject space = (JSONObject) page.get(KEY_SPACE);
+ if (space != null)
+ return space.optString(KEY_KEY, "");
+ } catch (JSONException e) {
+ return "";
+ }
+ return "";
+ }
+
+ @Override
+ public Class<Page> getType() {
+ return Page.class;
+ }
+ }
+}
diff --git a/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/PageType.java b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/PageType.java
new file mode 100644
index 0000000..c96f5fc
--- /dev/null
+++ b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/PageType.java
@@ -0,0 +1,29 @@
+package org.apache.manifoldcf.crawler.connectors.confluence.model;
+
+import org.apache.commons.lang.WordUtils;
+
+/**
+ * <p>PageType class</p>
+ * <p>Represents the kind of pages we can have in Confluence</p>
+ *
+ * @author Antonio David Perez Morales <adperezmorales@gmail.com>
+ *
+ */
+public enum PageType {
+
+ PAGE, ATTACHMENT;
+
+ public static PageType fromName(String type) {
+ for(PageType pageType: values()) {
+ if(pageType.name().equalsIgnoreCase(type)) {
+ return pageType;
+ }
+ }
+
+ return PageType.PAGE;
+ }
+
+ public String toString() {
+ return WordUtils.capitalize(name().toLowerCase());
+ }
+}
diff --git a/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/Space.java b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/Space.java
new file mode 100644
index 0000000..2668949
--- /dev/null
+++ b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/Space.java
@@ -0,0 +1,55 @@
+package org.apache.manifoldcf.crawler.connectors.confluence.model;
+
+import org.json.JSONObject;
+
+public class Space {
+
+ private static final String KEY_NAME = "name";
+ private static final String KEY_KEY = "key";
+ private static final String KEY_TYPE = "type";
+ private static final String KEY_URL = "url";
+
+ private String key;
+ private String name;
+ private String type;
+ private String url;
+
+ public Space() {
+
+ }
+
+ public String getKey() {
+ return key;
+ }
+ public void setKey(String key) {
+ this.key = key;
+ }
+ public String getName() {
+ return name;
+ }
+ public void setName(String name) {
+ this.name = name;
+ }
+ public String getType() {
+ return type;
+ }
+ public void setType(String type) {
+ this.type = type;
+ }
+ public String getUrl() {
+ return url;
+ }
+ public void setUrl(String url) {
+ this.url = url;
+ }
+
+ public static Space fromJson(JSONObject spaceJson) {
+ Space space = new Space();
+ space.key = spaceJson.optString(KEY_KEY, "");
+ space.name = spaceJson.optString(KEY_NAME, "");
+ space.type = spaceJson.optString(KEY_TYPE, "");
+ space.url = spaceJson.optString(KEY_URL, "");
+ return space;
+ }
+
+}
diff --git a/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/Spaces.java b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/Spaces.java
new file mode 100644
index 0000000..b3b02ef
--- /dev/null
+++ b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/Spaces.java
@@ -0,0 +1,37 @@
+package org.apache.manifoldcf.crawler.connectors.confluence.model;
+
+import java.util.ArrayList;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class Spaces extends ArrayList<Space> {
+
+ private static Logger logger = LoggerFactory.getLogger(Spaces.class);
+ /**
+ *
+ */
+ private static final long serialVersionUID = -5334215263162816914L;
+
+
+ public static Spaces fromJson(JSONArray jsonSpaces) {
+ Spaces spaces = new Spaces();
+ for(int i=0,len=jsonSpaces.length();i<len;i++) {
+ try {
+ JSONObject spaceJson = jsonSpaces.getJSONObject(i);
+ Space space = Space.fromJson(spaceJson);
+ spaces.add(space);
+ } catch (JSONException e) {
+ logger.debug("Error obtaining JSON item from spaces. Item {} is not a JSON Object", i);
+ e.printStackTrace();
+ continue;
+ }
+ }
+
+ return spaces;
+
+ }
+}
diff --git a/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/builder/ConfluenceResourceBuilder.java b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/builder/ConfluenceResourceBuilder.java
new file mode 100644
index 0000000..0b2653e
--- /dev/null
+++ b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/model/builder/ConfluenceResourceBuilder.java
@@ -0,0 +1,33 @@
+package org.apache.manifoldcf.crawler.connectors.confluence.model.builder;
+
+import org.apache.manifoldcf.crawler.connectors.confluence.model.ConfluenceResource;
+import org.json.JSONObject;
+
+/**
+ * <p>ConfluenceResourceBuilder interface</p>
+ * @author Antonio David Perez Morales <adperezmorales@gmail.com>
+ *
+ * @param <T> Subtype of ConfluenceResource to be built
+ */
+public interface ConfluenceResourceBuilder<T extends ConfluenceResource> {
+
+ /**
+ * <p>Creates a <T> instance from a JSON representation
+ * @param jsonDocument
+ * @return T instance
+ */
+ T fromJson(JSONObject jsonDocument);
+
+ /**
+ * <p>Populates the given <T> instance from a JSON representation and return it</p>
+ * @param jsonDocument
+ * @return T instance
+ */
+ T fromJson(JSONObject jsonDocument, T document);
+
+ /**
+ * <p>Returns the Class of the resource that can be built</p>
+ * @return the type Class<T> of the resource which can be built by this builder
+ */
+ Class<T> getType();
+}
diff --git a/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/util/ConfluenceUtil.java b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/util/ConfluenceUtil.java
new file mode 100644
index 0000000..5c47734
--- /dev/null
+++ b/connectors/confluence/connector/src/main/java/org/apache/manifoldcf/crawler/connectors/confluence/util/ConfluenceUtil.java
@@ -0,0 +1,42 @@
+package org.apache.manifoldcf.crawler.connectors.confluence.util;
+
+/**
+ * <p>Utility class for Confluence connectors</p>
+ *
+ * @author Antonio David Perez Morales <adperezmorales@gmail.com>
+ *
+ */
+public class ConfluenceUtil {
+
+ private static final String ATTACHMENT_ID_PREFIX = "att";
+
+ /**
+ * <p>Generates a repository document identifier for the specific attachment and page to be used for Repository Documents for attachment pages</p>
+ * @param attachmentId
+ * @param pageId
+ * @return a generated
+ */
+ public static String generateRepositoryDocumentIdentifier(String attachmentId, String pageId) {
+ StringBuilder sb = new StringBuilder();
+ sb.append(attachmentId).append("-").append(pageId);
+ return sb.toString();
+ }
+
+ /**
+ * <p>Checks if the given id is an attachment or not</p>
+ * @param id
+ * @return a {@code Boolean} indicating if the id is related to an attachment or not
+ */
+ public static Boolean isAttachment(String id) {
+ return id.startsWith(ATTACHMENT_ID_PREFIX);
+ }
+
+ /**
+ * <p>Gets the attachment id and page id from a repository document id</p>
+ * @param id the repository document id
+ * @return an Array containing the attachment and page ids where index 0 is the attachment id and index 1 is the page id
+ */
+ public static String[] getAttachmentAndPageId(String id) {
+ return id.split("-");
+ }
+}
diff --git a/connectors/confluence/connector/src/main/native2ascii/org/apache/manifoldcf/authorities/authorities/confluence/common_en_US.properties b/connectors/confluence/connector/src/main/native2ascii/org/apache/manifoldcf/authorities/authorities/confluence/common_en_US.properties
new file mode 100644
index 0000000..eddade0
--- /dev/null
+++ b/connectors/confluence/connector/src/main/native2ascii/org/apache/manifoldcf/authorities/authorities/confluence/common_en_US.properties
@@ -0,0 +1,29 @@
+# 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.
+
+ConfluenceAuthorityConnector.Server=Server
+
+ConfluenceAuthorityConnector.ProtocolColon=Protocol:
+ConfluenceAuthorityConnector.HostColon=Host:
+ConfluenceAuthorityConnector.PortColon=Port:
+ConfluenceAuthorityConnector.PathColon=Path:
+ConfluenceAuthorityConnector.UsernameColon=Username:
+ConfluenceAuthorityConnector.PasswordColon=Password:
+
+ConfluenceAuthorityConnector.HostMustNotBeNull=Confluence host must not be null
+ConfluenceAuthorityConnector.HostMustNotIncludeSlash=Confluence host must not include a '/' character
+ConfluenceAuthorityConnector.PortMustBeAnInteger=Confluence port must be an integer
+ConfluenceAuthorityConnector.PathMustNotBeNull=Confluence path must not be null
+ConfluenceAuthorityConnector.PathMustBeginWithASlash=Confluence path must begin with a '/' character
diff --git a/connectors/confluence/connector/src/main/native2ascii/org/apache/manifoldcf/crawler/connectors/confluence/common_en_US.properties b/connectors/confluence/connector/src/main/native2ascii/org/apache/manifoldcf/crawler/connectors/confluence/common_en_US.properties
new file mode 100644
index 0000000..8b4bd67
--- /dev/null
+++ b/connectors/confluence/connector/src/main/native2ascii/org/apache/manifoldcf/crawler/connectors/confluence/common_en_US.properties
@@ -0,0 +1,42 @@
+# 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.
+
+ConfluenceRepositoryConnector.Server=Server
+ConfluenceRepositoryConnector.Spaces=Spaces
+ConfluenceRepositoryConnector.Pages=Pages
+
+
+ConfluenceRepositoryConnector.ProtocolColon=Protocol:
+ConfluenceRepositoryConnector.HostColon=Host:
+ConfluenceRepositoryConnector.PortColon=Port:
+ConfluenceRepositoryConnector.PathColon=Path:
+ConfluenceRepositoryConnector.UsernameColon=Username:
+ConfluenceRepositoryConnector.PasswordColon=Password:
+
+ConfluenceRepositoryConnector.HostMustNotBeNull=Confluence host must not be null
+ConfluenceRepositoryConnector.HostMustNotIncludeSlash=Confluence host must not include a '/' character
+ConfluenceRepositoryConnector.PortMustBeAnInteger=Confluence port must be an integer
+ConfluenceRepositoryConnector.PathMustNotBeNull=Confluence path must not be null
+ConfluenceRepositoryConnector.PathMustBeginWithASlash=Confluence path must begin with a '/' character
+
+ConfluenceRepositoryConnector.NoSpacesConfigured=No spaces configured. All spaces will be crawled
+
+ConfluenceRepositoryConnector.Add=Add
+ConfluenceRepositoryConnector.AddSpace=Add Space
+ConfluenceRepositoryConnector.Delete=Delete
+ConfluenceRepositoryConnector.DeleteSpace=Delete space #
+ConfluenceRepositoryConnector.TypeInASpace=Type in a space
+
+ConfluenceRepositoryConnector.ProcessAttachments=Process Attachments
\ No newline at end of file
diff --git a/connectors/confluence/connector/src/main/resources/org/apache/manifoldcf/authorities/authorities/confluence/editConfiguration_conf.js b/connectors/confluence/connector/src/main/resources/org/apache/manifoldcf/authorities/authorities/confluence/editConfiguration_conf.js
new file mode 100644
index 0000000..4b5555e
--- /dev/null
+++ b/connectors/confluence/connector/src/main/resources/org/apache/manifoldcf/authorities/authorities/confluence/editConfiguration_conf.js
@@ -0,0 +1,76 @@
+
+<script type="text/javascript">
+<!--
+function checkConfig()
+{
+ if (editconnection.confluence_port.value != "" && !isInteger(editconnection.confluence_port.value))
+ {
+ alert("$Encoder.bodyJavascriptEscape($ResourceBundle.getString('ConfluenceAuthorityConnector.ConfPortMustBeAnInteger'))");
+ editconnection.confluence_port.focus();
+ return false;
+ }
+
+ if (editconnection.confluence_host.value != "" && editconnection.confluence_host.value.indexOf("/") != -1)
+ {
+ alert("$Encoder.bodyJavascriptEscape($ResourceBundle.getString('ConfluenceAuthorityConnector.HostMustNotIncludeSlash'))");
+ editconnection.confluence_host.focus();
+ return false;
+ }
+
+ if (editconnection.confluence_path.value != "" && !(editconnection.confluence_path.value.indexOf("/") == 0))
+ {
+ alert("$Encoder.bodyJavascriptEscape($ResourceBundle.getString('ConfluenceAuthorityConnector.PathMustBeginWithASlash'))");
+ editconnection.confluence_path.focus();
+ return false;
+ }
+
+ return true;
+}
+
+function checkConfigForSave()
+{
+
+ if (editconnection.confluence_host.value == "")
+ {
+ alert("$Encoder.bodyJavascriptEscape($ResourceBundle.getString('ConfluenceAuthorityConnector.HostMustNotBeNull'))");
+ SelectTab("$Encoder.bodyJavascriptEscape($ResourceBundle.getString('ConfluenceAuthorityConnector.Server'))");
+ editconnection.confluence_host.focus();
+ return false;
+ }
+
+ if (editconnection.confluence_host.value != "" && editconnection.confluence_host.value.indexOf("/") != -1)
+ {
+ alert("$Encoder.bodyJavascriptEscape($ResourceBundle.getString('ConfluenceAuthorityConnector.HostMustNotIncludeSlash'))");
+ SelectTab("$Encoder.bodyJavascriptEscape($ResourceBundle.getString('ConfluenceAuthorityConnector.Server'))");
+ editconnection.confluence_host.focus();
+ return false;
+ }
+
+ if (editconnection.confluence_port.value != "" && !isInteger(editconnection.confluence_port.value))
+ {
+ alert("$Encoder.bodyJavascriptEscape($ResourceBundle.getString('ConfluenceAuthorityConnector.PortMustBeAnInteger'))");
+ SelectTab("$Encoder.bodyJavascriptEscape($ResourceBundle.getString('ConfluenceAuthorityConnector.Server'))");
+ editconnection.confluence_port.focus();
+ return false;
+ }
+
+ if (editconnection.confluence_path.value == "")
+ {
+ alert("$Encoder.bodyJavascriptEscape($ResourceBundle.getString('ConfluenceAuthorityConnector.PathMustNotBeNull'))");
+ SelectTab("$Encoder.bodyJavascriptEscape($ResourceBundle.getString('ConfluenceAuthorityConnector.Server'))");
+ editconnection.confluence_path.focus();
+ return false;
+ }
+
+ if (editconnection.confluence_path.value != "" && !(editconnection.confluence_path.value.indexOf("/") == 0))
+ {
+ alert("$Encoder.bodyJavascriptEscape($ResourceBundle.getString('ConfluenceAuthorityConnector.PathMustBeginWithASlash'))");
+ SelectTab("$Encoder.bodyJavascriptEscape($ResourceBundle.getString('ConfluenceAuthorityConnector.Server'))");
+ editconnection.confluence_path.focus();
+ return false;
+ }
+
+ return true;
+}
+//-->
+</script>
diff --git a/connectors/confluence/connector/src/main/resources/org/apache/manifoldcf/authorities/authorities/confluence/editConfiguration_conf_server.html b/connectors/confluence/connector/src/main/resources/org/apache/manifoldcf/authorities/authorities/confluence/editConfiguration_conf_server.html
new file mode 100644
index 0000000..545d22f
--- /dev/null
+++ b/connectors/confluence/connector/src/main/resources/org/apache/manifoldcf/authorities/authorities/confluence/editConfiguration_conf_server.html
@@ -0,0 +1,83 @@
+
+#if($TABNAME == $ResourceBundle.getString('ConfluenceAuthorityConnector.Server'))
+
+<table class="displaytable">
+ <tr><td class="separator" colspan="2"><hr/></td></tr>
+
+ <tr>
+ <td class="description">
+ <nobr>$Encoder.bodyEscape($ResourceBundle.getString('ConfluenceAuthorityConnector.ProtocolColon'))</nobr>
+ </td>
+ <td class="value">
+ <select size="2" name="confluence_protocol"/>
+#if($CONFLUENCE_PROTOCOL == 'http')
+ <option value="http" selected="true">http</option>
+#else
+ <option value="http">http</option>
+#end
+#if($CONFLUENCE_PROTOCOL == 'https')
+ <option value="https" selected="true">https</option>
+#else
+ <option value="https">https</option>
+#end
+ </select>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="description">
+ <nobr>$Encoder.bodyEscape($ResourceBundle.getString('ConfluenceAuthorityConnector.HostColon'))</nobr>
+ </td>
+ <td class="value">
+ <input size="32" type="text" id="confluence_host" name="confluence_host" value="$Encoder.attributeEscape($CONFLUENCE_HOST)" />
+ </td>
+ </tr>
+
+ <tr>
+ <td class="description">
+ <nobr>$Encoder.bodyEscape($ResourceBundle.getString('ConfluenceAuthorityConnector.PortColon'))</nobr>
+ </td>
+ <td class="value">
+ <input size="5" type="text" id="confluence_port" name="confluence_port" value="$Encoder.attributeEscape($CONFLUENCE_PORT)" />
+ </td>
+ </tr>
+
+ <tr>
+ <td class="description">
+ <nobr>$Encoder.bodyEscape($ResourceBundle.getString('ConfluenceAuthorityConnector.PathColon'))</nobr>
+ </td>
+ <td class="value">
+ <input size="32" type="text" id="confluence_path" name="confluence_path" value="$Encoder.attributeEscape($CONFLUENCE_PATH)" />
+ </td>
+ </tr>
+
+ <tr><td class="separator" colspan="2"><hr/></td></tr>
+
+ <tr>
+ <td class="description">
+ <nobr>$Encoder.bodyEscape($ResourceBundle.getString('ConfluenceAuthorityConnector.UsernameColon'))</nobr>
+ </td>
+ <td class="value">
+ <input size="16" type="text" id="confluence_username" name="confluence_username" value="$Encoder.attributeEscape($CONFLUENCE_USERNAME)" />
+ </td>
+ </tr>
+ <tr>
+ <td class="description">
+ <nobr>$Encoder.bodyEscape($ResourceBundle.getString('ConfluenceAuthorityConnector.PasswordColon'))</nobr>
+ </td>
+ <td class="value">
+ <input size="16" type="password" id="confluence_password" name="confluence_password" value="$Encoder.attributeEscape($CONFLUENCE_PASSWORD)" />
+ </td>
+ </tr>
+</table>
+
+#else
+
+<input type="hidden" name="confluence_protocol" value="$Encoder.attributeEscape($CONFLUENCE_PROTOCOL)" />
+<input type="hidden" name="confluence_host" value="$Encoder.attributeEscape($CONFLUENCE_HOST)" />
+<input type="hidden" name="confluence_port" value="$Encoder.attributeEscape($CONFLUENCE_PORT)" />
+<input type="hidden" name="confluence_path" value="$Encoder.attributeEscape($CONFLUENCE_PATH)" />
+<input type="hidden" name="confluence_username" value="$Encoder.attributeEscape($CONFLUENCE_USERNAME)" />
+<input type="hidden" name="confluence_password" value="$Encoder.attributeEscape($CONFLUENCE_PASSWORD)" />
+
+#end
diff --git a/connectors/confluence/connector/src/main/resources/org/apache/manifoldcf/authorities/authorities/confluence/viewConfiguration_conf.html b/connectors/confluence/connector/src/main/resources/org/apache/manifoldcf/authorities/authorities/confluence/viewConfiguration_conf.html
new file mode 100644
index 0000000..13bf701
--- /dev/null
+++ b/connectors/confluence/connector/src/main/resources/org/apache/manifoldcf/authorities/authorities/confluence/viewConfiguration_conf.html
@@ -0,0 +1,62 @@
+
+
+<table class="displaytable">
+
+ <tr>
+ <td class="description">
+ <nobr>$Encoder.bodyEscape($ResourceBundle.getString('ConfluenceAuthorityConnector.ProtocolColon'))</nobr>
+ </td>
+ <td class="value">
+ <nobr>$Encoder.bodyEscape($CONFLUENCE_PROTOCOL)</nobr>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="description">
+ <nobr>$Encoder.bodyEscape($ResourceBundle.getString('ConfluenceAuthorityConnector.HostColon'))</nobr>
+ </td>
+ <td class="value">
+ <nobr>$Encoder.bodyEscape($CONFLUENCE_HOST)</nobr>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="description">
+ <nobr>$Encoder.bodyEscape($ResourceBundle.getString('ConfluenceAuthorityConnector.PortColon'))</nobr>
+ </td>
+ <td class="value">
+ <nobr>$Encoder.bodyEscape($CONFLUENCE_PORT)</nobr>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="description">
+ <nobr>$Encoder.bodyEscape($ResourceBundle.getString('ConfluenceAuthorityConnector.PathColon'))</nobr>
+ </td>
+ <td class="value">
+ <nobr>$Encoder.bodyEscape($CONFLUENCE_PATH)</nobr>
+ </td>
+ </tr>
+
+ <tr><td class="separator" colspan="2"><hr/></td></tr>
+
+ <tr>
+ <td class="description">
+ <nobr>$Encoder.bodyEscape($ResourceBundle.getString('ConfluenceAuthorityConnector.UsernameColon'))</nobr>
+ </td>
+ <td class="value">
+ <nobr>$Encoder.bodyEscape($CONFLUENCE_USERNAME)</nobr>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="description">
+ <nobr>$Encoder.bodyEscape($ResourceBundle.getString('ConfluenceAuthorityConnector.PasswordColon'))</nobr>
+ </td>
+ <td class="value">
+ <nobr>********</nobr>
+ </td>
+ </tr>
+
+</table>
+
diff --git a/connectors/confluence/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/confluence/editConfiguration_conf.js b/connectors/confluence/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/confluence/editConfiguration_conf.js
new file mode 100644
index 0000000..ebed889
--- /dev/null
+++ b/connectors/confluence/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/confluence/editConfiguration_conf.js
@@ -0,0 +1,76 @@
+
+<script type="text/javascript">
+<!--
+function checkConfig()
+{
+ if (editconnection.confluence_port.value != "" && !isInteger(editconnection.confluence_port.value))
+ {
+ alert("$Encoder.bodyJavascriptEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.ConfPortMustBeAnInteger'))");
+ editconnection.confluence_port.focus();
+ return false;
+ }
+
+ if (editconnection.confluence_host.value != "" && editconnection.confluence_host.value.indexOf("/") != -1)
+ {
+ alert("$Encoder.bodyJavascriptEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.HostMustNotIncludeSlash'))");
+ editconnection.confluence_host.focus();
+ return false;
+ }
+
+// if (editconnection.confluence_path.value != "" && !(editconnection.confluence_path.value.indexOf("/") == 0))
+// {
+// alert("$Encoder.bodyJavascriptEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.PathMustBeginWithASlash'))");
+// editconnection.confluence_path.focus();
+// return false;
+// }
+
+ return true;
+}
+
+function checkConfigForSave()
+{
+
+ if (editconnection.confluence_host.value == "")
+ {
+ alert("$Encoder.bodyJavascriptEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.HostMustNotBeNull'))");
+ SelectTab("$Encoder.bodyJavascriptEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.Server'))");
+ editconnection.confluence_host.focus();
+ return false;
+ }
+
+ if (editconnection.confluence_host.value != "" && editconnection.confluence_host.value.indexOf("/") != -1)
+ {
+ alert("$Encoder.bodyJavascriptEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.HostMustNotIncludeSlash'))");
+ SelectTab("$Encoder.bodyJavascriptEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.Server'))");
+ editconnection.confluence_host.focus();
+ return false;
+ }
+
+ if (editconnection.confluence_port.value != "" && !isInteger(editconnection.confluence_port.value))
+ {
+ alert("$Encoder.bodyJavascriptEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.PortMustBeAnInteger'))");
+ SelectTab("$Encoder.bodyJavascriptEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.Server'))");
+ editconnection.confluence_port.focus();
+ return false;
+ }
+
+// if (editconnection.confluence_path.value == "")
+// {
+// alert("$Encoder.bodyJavascriptEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.PathMustNotBeNull'))");
+// SelectTab("$Encoder.bodyJavascriptEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.Server'))");
+// editconnection.confluence_path.focus();
+// return false;
+// }
+//
+// if (editconnection.confluence_path.value != "" && !(editconnection.confluence_path.value.indexOf("/") == 0))
+// {
+// alert("$Encoder.bodyJavascriptEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.PathMustBeginWithASlash'))");
+// SelectTab("$Encoder.bodyJavascriptEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.Server'))");
+// editconnection.confluence_path.focus();
+// return false;
+// }
+
+ return true;
+}
+//-->
+</script>
diff --git a/connectors/confluence/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/confluence/editConfiguration_conf_server.html b/connectors/confluence/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/confluence/editConfiguration_conf_server.html
new file mode 100644
index 0000000..9f73455
--- /dev/null
+++ b/connectors/confluence/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/confluence/editConfiguration_conf_server.html
@@ -0,0 +1,83 @@
+
+#if($TABNAME == $ResourceBundle.getString('ConfluenceRepositoryConnector.Server'))
+
+<table class="displaytable">
+ <tr><td class="separator" colspan="2"><hr/></td></tr>
+
+ <tr>
+ <td class="description">
+ <nobr>$Encoder.bodyEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.ProtocolColon'))</nobr>
+ </td>
+ <td class="value">
+ <select size="2" name="confluence_protocol"/>
+#if($CONFLUENCE_PROTOCOL == 'http')
+ <option value="http" selected="true">http</option>
+#else
+ <option value="http">http</option>
+#end
+#if($CONFLUENCE_PROTOCOL == 'https')
+ <option value="https" selected="true">https</option>
+#else
+ <option value="https">https</option>
+#end
+ </select>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="description">
+ <nobr>$Encoder.bodyEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.HostColon'))</nobr>
+ </td>
+ <td class="value">
+ <input size="32" type="text" id="confluence_host" name="confluence_host" value="$Encoder.attributeEscape($CONFLUENCE_HOST)" />
+ </td>
+ </tr>
+
+ <tr>
+ <td class="description">
+ <nobr>$Encoder.bodyEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.PortColon'))</nobr>
+ </td>
+ <td class="value">
+ <input size="5" type="text" id="confluence_port" name="confluence_port" value="$Encoder.attributeEscape($CONFLUENCE_PORT)" />
+ </td>
+ </tr>
+
+ <tr>
+ <td class="description">
+ <nobr>$Encoder.bodyEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.PathColon'))</nobr>
+ </td>
+ <td class="value">
+ <input size="32" type="text" id="confluence_path" name="confluence_path" value="$Encoder.attributeEscape($CONFLUENCE_PATH)" />
+ </td>
+ </tr>
+
+ <tr><td class="separator" colspan="2"><hr/></td></tr>
+
+ <tr>
+ <td class="description">
+ <nobr>$Encoder.bodyEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.UsernameColon'))</nobr>
+ </td>
+ <td class="value">
+ <input size="16" type="text" id="confluence_username" name="confluence_username" value="$Encoder.attributeEscape($CONFLUENCE_USERNAME)" />
+ </td>
+ </tr>
+ <tr>
+ <td class="description">
+ <nobr>$Encoder.bodyEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.PasswordColon'))</nobr>
+ </td>
+ <td class="value">
+ <input size="16" type="password" id="confluence_password" name="confluence_password" value="$Encoder.attributeEscape($CONFLUENCE_PASSWORD)" />
+ </td>
+ </tr>
+</table>
+
+#else
+
+<input type="hidden" name="confluence_protocol" value="$Encoder.attributeEscape($CONFLUENCE_PROTOCOL)" />
+<input type="hidden" name="confluence_host" value="$Encoder.attributeEscape($CONFLUENCE_HOST)" />
+<input type="hidden" name="confluence_port" value="$Encoder.attributeEscape($CONFLUENCE_PORT)" />
+<input type="hidden" name="confluence_path" value="$Encoder.attributeEscape($CONFLUENCE_PATH)" />
+<input type="hidden" name="confluence_username" value="$Encoder.attributeEscape($CONFLUENCE_USERNAME)" />
+<input type="hidden" name="confluence_password" value="$Encoder.attributeEscape($CONFLUENCE_PASSWORD)" />
+
+#end
diff --git a/connectors/confluence/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/confluence/editSpecification_conf.js b/connectors/confluence/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/confluence/editSpecification_conf.js
new file mode 100644
index 0000000..1521887
--- /dev/null
+++ b/connectors/confluence/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/confluence/editSpecification_conf.js
@@ -0,0 +1,32 @@
+<script type="text/javascript">
+<!--
+function checkSpecificationForSave()
+{
+ return true;
+}
+
+function s${SeqNum}_SpecOp(n, opValue, anchorvalue)
+{
+ eval("editjob."+n+".value = \""+opValue+"\"");
+ postFormSetAnchor(anchorvalue);
+}
+
+function s${SeqNum}_SpecDeleteSpace(i)
+{
+ s${SeqNum}_SpecOp("s${SeqNum}_spaceop_"+i,"Delete","space_"+i);
+}
+
+function s${SeqNum}_SpecAddSpace(i)
+{
+ var x = i-1;
+ if (editjob["s${SeqNum}_space"].value == "")
+ {
+ alert("$Encoder.bodyJavascriptEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.TypeInASpace'))");
+ editjob.s${SeqNum}_space.focus();
+ return;
+ }
+ s${SeqNum}_SpecOp("s${SeqNum}_spaceop","Add","space_"+i);
+}
+
+//-->
+</script>
\ No newline at end of file
diff --git a/connectors/confluence/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/confluence/editSpecification_confPages.html b/connectors/confluence/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/confluence/editSpecification_confPages.html
new file mode 100644
index 0000000..7e1c1c1
--- /dev/null
+++ b/connectors/confluence/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/confluence/editSpecification_confPages.html
@@ -0,0 +1,26 @@
+
+
+#if($TabName == $ResourceBundle.getString('ConfluenceRepositoryConnector.Pages') && ${SeqNum} == ${SelectedNum})
+
+<table class="displaytable">
+ <tr><td class="separator" colspan="2"><hr/></td></tr>
+
+ <tr>
+ <td class="description" colspan="2">
+ $Encoder.bodyEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.ProcessAttachments'))
+ </td>
+
+ <td class="value">
+ <input type="checkbox" name="s${SeqNum}_process_attachments" value="true"
+ #if($PROCESS_ATTACHMENTS) checked
+ #end
+ />
+ </td>
+ </tr>
+</table>
+
+#else
+
+<input type="hidden" name="s${SeqNum}_process_attachments" value="$PROCESS_ATTACHMENTS"/>
+
+#end
diff --git a/connectors/confluence/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/confluence/editSpecification_confSpaces.html b/connectors/confluence/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/confluence/editSpecification_confSpaces.html
new file mode 100644
index 0000000..2a95cae
--- /dev/null
+++ b/connectors/confluence/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/confluence/editSpecification_confSpaces.html
@@ -0,0 +1,61 @@
+
+
+#if($TabName == $ResourceBundle.getString('ConfluenceRepositoryConnector.Spaces') && ${SeqNum} == ${SelectedNum})
+
+<table class="displaytable">
+ <tr><td class="separator" colspan="2"><hr/></td></tr>
+
+
+ <tr><td class="separator" colspan="2"><hr/></td></tr>
+
+ #set($spacecounter = 0)
+ #foreach($space in $SPACES)
+
+ <tr>
+ <td class="description">
+ <input type="hidden" name="s${SeqNum}_spaceop_$spacecounter" value=""/>
+ <input type="hidden" name="s${SeqNum}_space_$spacecounter" value="$Encoder.attributeEscape($space)"/>
+ <a name="s${SeqNum}_tokenspace_$spacecounter">
+ <input type="button" value="$Encoder.attributeEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.Delete'))" onClick='Javascript:s${SeqNum}_SpecDeleteSpace($spacecounter)' alt="$Encoder.attributeEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.DeleteSpace'))$spacecounter"/>
+ </a>
+ </td>
+ <td class="value">$Encoder.bodyEscape($space)</td>
+ </tr>
+
+ #set($spacecounter = $spacecounter + 1)
+ #end
+
+ #set($nextspace = $spacecounter + 1)
+
+ #if($spacecounter == 0)
+ <tr>
+ <td class="message" colspan="2">$Encoder.bodyEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.NoSpacesConfigured'))</td>
+ </tr>
+ #end
+
+ <tr><td class="lightseparator" colspan="2"><hr/></td></tr>
+
+ <tr>
+ <td class="description">
+ <input type="hidden" name="s${SeqNum}_spacescount" value="$spacecounter"/>
+ <input type="hidden" name="s${SeqNum}_spaceop" value=""/>
+ <a name="space_$spacecounter">
+ <input type="button" value="$Encoder.attributeEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.Add'))" onClick='Javascript:s${SeqNum}_SpecAddSpace($nextspace)' alt="$Encoder.attributeEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.AddSpace'))"/>
+ </a>
+ </td>
+ <td class="value">
+ <input type="text" size="30" name="s${SeqNum}_space" value=""/>
+ </td>
+ </tr>
+</table>
+
+#else
+
+ #set($spacecounter = 0)
+ #foreach($space in $SPACES)
+<input type="hidden" name="s${SeqNum}_space_$spacecounter" value="$Encoder.attributeEscape($space)"/>
+ #set($space = $space + 1)
+ #end
+<input type="hidden" name="s${SeqNum}_spacescount" value="$spacecounter"/>
+
+#end
diff --git a/connectors/confluence/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/confluence/viewConfiguration_conf.html b/connectors/confluence/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/confluence/viewConfiguration_conf.html
new file mode 100644
index 0000000..dddc9af
--- /dev/null
+++ b/connectors/confluence/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/confluence/viewConfiguration_conf.html
@@ -0,0 +1,62 @@
+
+
+<table class="displaytable">
+
+ <tr>
+ <td class="description">
+ <nobr>$Encoder.bodyEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.ProtocolColon'))</nobr>
+ </td>
+ <td class="value">
+ <nobr>$Encoder.bodyEscape($CONFLUENCE_PROTOCOL)</nobr>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="description">
+ <nobr>$Encoder.bodyEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.HostColon'))</nobr>
+ </td>
+ <td class="value">
+ <nobr>$Encoder.bodyEscape($CONFLUENCE_HOST)</nobr>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="description">
+ <nobr>$Encoder.bodyEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.PortColon'))</nobr>
+ </td>
+ <td class="value">
+ <nobr>$Encoder.bodyEscape($CONFLUENCE_PORT)</nobr>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="description">
+ <nobr>$Encoder.bodyEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.PathColon'))</nobr>
+ </td>
+ <td class="value">
+ <nobr>$Encoder.bodyEscape($CONFLUENCE_PATH)</nobr>
+ </td>
+ </tr>
+
+ <tr><td class="separator" colspan="2"><hr/></td></tr>
+
+ <tr>
+ <td class="description">
+ <nobr>$Encoder.bodyEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.UsernameColon'))</nobr>
+ </td>
+ <td class="value">
+ <nobr>$Encoder.bodyEscape($CONFLUENCE_USERNAME)</nobr>
+ </td>
+ </tr>
+
+ <tr>
+ <td class="description">
+ <nobr>$Encoder.bodyEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.PasswordColon'))</nobr>
+ </td>
+ <td class="value">
+ <nobr>********</nobr>
+ </td>
+ </tr>
+
+</table>
+
diff --git a/connectors/confluence/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/confluence/viewSpecification_conf.html b/connectors/confluence/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/confluence/viewSpecification_conf.html
new file mode 100644
index 0000000..8cb57da
--- /dev/null
+++ b/connectors/confluence/connector/src/main/resources/org/apache/manifoldcf/crawler/connectors/confluence/viewSpecification_conf.html
@@ -0,0 +1,29 @@
+<table class="displaytable">
+ <tr>
+#if($SPACES.size() == 0)
+ <td class="message" colspan="2">
+ $Encoder.bodyEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.NoSpacesConfigured'))
+ </td>
+#else
+ <td class="description">
+ <nobr>$Encoder.bodyEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.Spaces'))</nobr>
+ </td>
+ <td class="value">
+ #set($spacecounter = 0)
+ #foreach($space in $SPACES)
+ <nobr>$Encoder.bodyEscape($space)</nobr><br/>
+ #set($spacecounter = $spacecounter + 1)
+ #end
+ </td>
+#end
+ </tr>
+
+ <tr>
+ <td class="message" colspan="2">
+ $Encoder.bodyEscape($ResourceBundle.getString('ConfluenceRepositoryConnector.ProcessAttachments'))
+ </td>
+ <td class="description">
+ <nobr>$PROCESS_ATTACHMENTS</nobr>
+ </td>
+ </tr>
+</table>
\ No newline at end of file
diff --git a/connectors/confluence/connector/src/test/java/org/apache/manifoldcf/authorities/confluence/tests/ConfluenceAuthorityTest.java b/connectors/confluence/connector/src/test/java/org/apache/manifoldcf/authorities/confluence/tests/ConfluenceAuthorityTest.java
new file mode 100644
index 0000000..940125b
--- /dev/null
+++ b/connectors/confluence/connector/src/test/java/org/apache/manifoldcf/authorities/confluence/tests/ConfluenceAuthorityTest.java
@@ -0,0 +1,70 @@
+package org.apache.manifoldcf.authorities.confluence.tests;
+
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import junit.framework.Assert;
+
+import org.apache.manifoldcf.authorities.authorities.confluence.ConfluenceAuthorityConnector;
+import org.apache.manifoldcf.authorities.interfaces.AuthorizationResponse;
+import org.apache.manifoldcf.authorities.interfaces.IAuthorityConnector;
+import org.apache.manifoldcf.crawler.connectors.confluence.client.ConfluenceClient;
+import org.apache.manifoldcf.crawler.connectors.confluence.model.ConfluenceUser;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.runners.MockitoJUnitRunner;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ConfluenceAuthorityTest {
+
+ @Mock
+ private ConfluenceClient client;
+
+ private ConfluenceAuthorityConnector connector;
+
+ @Before
+ public void setup() throws Exception{
+ connector = new ConfluenceAuthorityConnector();
+ connector.setConfluenceClient(client);
+ }
+
+ @Test
+ public void checkMockInjection() throws Exception{
+ when(client.checkAuth()).thenReturn(true);
+ Assert.assertEquals(connector.check(), "Connection working");
+ }
+
+ @Test
+ public void checkUserNotFound() throws Exception{
+ ConfluenceUser user = mock(ConfluenceUser.class);
+ when(user.getUsername()).thenReturn(null);
+ when(client.getUserAuthorities(anyString())).thenReturn(user);
+ AuthorizationResponse response = connector.getAuthorizationResponse(anyString());
+ String[] tokens = response.getAccessTokens();
+ Assert.assertEquals(tokens.length, 1);
+ Assert.assertEquals(tokens[0], IAuthorityConnector.GLOBAL_DENY_TOKEN);
+ Assert.assertEquals(response.getResponseStatus(), AuthorizationResponse.RESPONSE_USERNOTFOUND);
+ }
+
+ @Test
+ public void checkUserFound() throws Exception{
+ ConfluenceUser user = mock(ConfluenceUser.class);
+ when(user.getUsername()).thenReturn("A");
+ List<String> tokens = new ArrayList<String>();
+ tokens.add("B");
+ when(user.getAuthorities()).thenReturn(tokens);
+ when(client.getUserAuthorities(anyString())).thenReturn(user);
+ AuthorizationResponse response = connector.getAuthorizationResponse(anyString());
+ String[] tokens_aux = response.getAccessTokens();
+ Assert.assertEquals(tokens_aux.length, 1);
+ Assert.assertEquals(tokens_aux[0], tokens.get(0));
+ Assert.assertEquals(response.getResponseStatus(), AuthorizationResponse.RESPONSE_OK);
+ }
+
+}
diff --git a/connectors/confluence/connector/src/test/java/org/apache/manifoldcf/crawler/connectors/confluence/tests/ConfluenceConnectorTest.java b/connectors/confluence/connector/src/test/java/org/apache/manifoldcf/crawler/connectors/confluence/tests/ConfluenceConnectorTest.java
new file mode 100644
index 0000000..3c5e182
--- /dev/null
+++ b/connectors/confluence/connector/src/test/java/org/apache/manifoldcf/crawler/connectors/confluence/tests/ConfluenceConnectorTest.java
@@ -0,0 +1,195 @@
+package org.apache.manifoldcf.crawler.connectors.confluence.tests;
+
+import static org.mockito.Matchers.anyInt;
+import static org.mockito.Matchers.anyLong;
+import static org.mockito.Matchers.anyObject;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Matchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+import java.nio.charset.StandardCharsets;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.apache.manifoldcf.agents.interfaces.RepositoryDocument;
+import org.apache.manifoldcf.core.interfaces.Specification;
+import org.apache.manifoldcf.crawler.connectors.BaseRepositoryConnector;
+import org.apache.manifoldcf.crawler.connectors.confluence.ConfluenceRepositoryConnector;
+import org.apache.manifoldcf.crawler.connectors.confluence.client.ConfluenceClient;
+import org.apache.manifoldcf.crawler.connectors.confluence.model.ConfluenceResponse;
+import org.apache.manifoldcf.crawler.connectors.confluence.model.Page;
+import org.apache.manifoldcf.crawler.interfaces.IExistingVersions;
+import org.apache.manifoldcf.crawler.interfaces.IProcessActivity;
+import org.apache.manifoldcf.crawler.system.SeedingActivity;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.Mockito;
+import org.mockito.runners.MockitoJUnitRunner;
+
+import com.google.common.base.Optional;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ConfluenceConnectorTest {
+
+ @Mock
+ private ConfluenceClient client;
+
+ private ConfluenceRepositoryConnector connector;
+
+ @SuppressWarnings("unchecked")
+ @Before
+ public void setup() throws Exception{
+ connector = new ConfluenceRepositoryConnector();
+ connector.setConfluenceClient(client);
+ when(client.getPages(anyInt(), anyInt(), Mockito.any(Optional.class))).
+ thenReturn(new ConfluenceResponse<Page>(Collections.<Page>emptyList(), 0, 0, true));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void mockEmptySeeding() throws Exception {
+
+ SeedingActivity activities = mock(SeedingActivity.class);
+ Specification spec = new Specification();
+ long seedTime = 0;
+
+ connector.addSeedDocuments(activities, spec, "", seedTime, BaseRepositoryConnector.JOBMODE_ONCEONLY);
+ // Verify it starts always at 0. Pagination configurable so anyInt(). Only one call because isLast must be false
+ verify(client, times(1)).getPages(eq(0), anyInt(), Mockito.any(Optional.class));
+ }
+
+ @SuppressWarnings("unchecked")
+ @Test
+ public void mockSeeding() throws Exception {
+
+ SeedingActivity activities = mock(SeedingActivity.class);
+ Specification spec = new Specification();
+ long seedTime = 0;
+
+ List<Page> pages = new ArrayList<Page>();
+ Page page = mock(Page.class);
+ pages.add(page);
+ when(client.getPages(anyInt(), anyInt(), Mockito.any(Optional.class))).
+ thenReturn(new ConfluenceResponse<Page>(pages, 0, 0, false)).
+ thenReturn(new ConfluenceResponse<Page>(Collections.<Page>emptyList(), 0, 0, true));
+ connector.addSeedDocuments(activities, spec, "", seedTime, BaseRepositoryConnector.JOBMODE_ONCEONLY);
+ verify(activities, times(1)).addSeedDocument(Mockito.anyString());
+ verify(client, times(1)).getPages(eq(0), anyInt(), Mockito.any(Optional.class));
+ verify(client, times(1)).getPages(eq(1), anyInt(), Mockito.any(Optional.class));
+ }
+
+ @Test
+ public void mockSimpleIngestion() throws Exception{
+
+ Page fakePage = mock(Page.class);
+
+ Date date = new Date();
+ DateFormat df = DateFormat.getDateTimeInstance();
+ String content = "A";
+ String uri = "http://test";
+ byte[] documentBytes = content
+ .getBytes(StandardCharsets.UTF_8);
+ long size = (long) documentBytes.length;
+
+ when(fakePage.hasContent()).thenReturn(true);
+ when(fakePage.getContent()).thenReturn(content);
+ when(fakePage.getLength()).thenReturn(size);
+ when(fakePage.getLastModifiedDate()).thenReturn(date);
+ when(fakePage.getMediaType()).thenReturn("text/plain");
+ when(fakePage.getCreatedDate()).thenReturn(date);
+ when(fakePage.getWebUrl()).thenReturn(uri);
+ Map<String, Object> metadata = new HashMap<String, Object>();
+ metadata.put("x", "y");
+ when(fakePage.getMetadataAsMap()).thenReturn(metadata);
+
+ IProcessActivity activities = mock(IProcessActivity.class);
+ when(activities.checkLengthIndexable(anyLong()))
+ .thenReturn(true);
+ when(activities.checkMimeTypeIndexable(anyString()))
+ .thenReturn(true);
+ when(activities.checkDateIndexable((Date)anyObject()))
+ .thenReturn(true);
+ when(activities.checkURLIndexable(anyString()))
+ .thenReturn(true);
+ when(activities.checkDocumentNeedsReindexing(anyString(), anyString()))
+ .thenReturn(true);
+ IExistingVersions statuses = mock(IExistingVersions.class);
+
+ String ID = df.format(date);
+ when(statuses.getIndexedVersionString(ID)).
+ thenReturn(null);
+
+ when(client.getPage(Mockito.anyString())).
+ thenReturn(fakePage);
+
+ connector.processDocuments(new String[]{ID}, statuses, new Specification(), activities, 0, true);
+ ArgumentCaptor<RepositoryDocument> rd = ArgumentCaptor.forClass(RepositoryDocument.class);
+
+ verify(client, times(1)).getPage(ID);
+ verify(activities, times(1)).ingestDocumentWithException(eq(ID),
+ eq(df.format(date)), eq(uri), rd.capture());
+ verify(activities, times(1)).recordActivity(anyLong(),
+ eq("read document"), eq(size), eq(ID), eq("OK"),
+ anyString(), Mockito.isNull(String[].class));
+
+ RepositoryDocument doc = rd.getValue();
+ Assert.assertEquals(size, doc.getBinaryLength());
+ String[] values = doc.getFieldAsStrings("x");
+ Assert.assertEquals(values.length, 1);
+ Assert.assertEquals(values[0], "y");
+
+ }
+
+ @Test
+ public void mockNeedsReindexing() throws Exception{
+ Page fakePage = mock(Page.class);
+ when(fakePage.hasContent()).thenReturn(true);
+ Date date = new Date();
+ DateFormat df = DateFormat.getDateTimeInstance();
+ String version = df.format(date);
+ when(fakePage.getLastModifiedDate()).thenReturn(df.parse(version));
+
+ String id = "1";
+ IProcessActivity activities = mock(IProcessActivity.class);
+ IExistingVersions statuses = mock(IExistingVersions.class);
+ when(statuses.getIndexedVersionString(id)).
+ thenReturn(version);
+
+ when(client.getPage(Mockito.anyString())).
+ thenReturn(fakePage);
+
+ connector.processDocuments(new String[]{id}, statuses, new Specification(), activities, 0, true);
+ verify(client, times(1)).getPage(id);
+ verify(activities, times(1)).checkDocumentNeedsReindexing(id, version);
+ }
+
+ @Test
+ public void mockDeleteDocument() throws Exception{
+ Page fakePage = mock(Page.class);
+ when(fakePage.hasContent()).thenReturn(false);
+ String id = "A";
+ when(fakePage.hasContent()).thenReturn(false);
+ when(client.getPage(Mockito.anyString())).
+ thenReturn(fakePage);
+
+ IExistingVersions statuses = mock(IExistingVersions.class);
+ IProcessActivity activities = mock(IProcessActivity.class);
+ connector.processDocuments(new String[]{id}, statuses, new Specification(), activities, 0, true);
+ verify(client, times(1)).getPage(id);
+ verify(activities, times(1)).deleteDocument(id);
+
+ }
+
+}
diff --git a/connectors/confluence/pom.xml b/connectors/confluence/pom.xml
new file mode 100644
index 0000000..fef0992
--- /dev/null
+++ b/connectors/confluence/pom.xml
@@ -0,0 +1,352 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <parent>
+ <groupId>org.apache.manifoldcf</groupId>
+ <artifactId>mcf-connectors</artifactId>
+ <version>2.2-SNAPSHOT</version>
+ </parent>
+
+ <name>ManifoldCF - Connectors - Confluence Connector</name>
+ <modelVersion>4.0.0</modelVersion>
+ <artifactId>mcf-confluence-connector</artifactId>
+ <packaging>jar</packaging>
+
+ <url>http://maven.apache.org</url>
+
+ <developers>
+ <developer>
+ <name>Antonio David Perez Morales</name>
+ <email>adperezmorales@gmail.com</email>
+ </developer>
+ </developers>
+
+ <properties>
+ <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
+ <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
+ </properties>
+
+ <build>
+ <defaultGoal>integration-test</defaultGoal>
+ <sourceDirectory>${basedir}/connector/src/main/java</sourceDirectory>
+ <testSourceDirectory>${basedir}/connector/src/test/java</testSourceDirectory>
+ <resources>
+ <resource>
+ <directory>${basedir}/connector/src/main/native2ascii</directory>
+ <includes>
+ <include>**/*.properties</include>
+ </includes>
+ </resource>
+ <resource>
+ <directory>${basedir}/connector/src/main/resources</directory>
+ <includes>
+ <include>**/*.html</include>
+ <include>**/*.js</include>
+ </includes>
+ </resource>
+ </resources>
+ <testResources>
+ <testResource>
+ <directory>${basedir}/connector/src/test/resources</directory>
+ </testResource>
+ </testResources>
+
+
+ <plugins>
+ <plugin>
+ <groupId>org.codehaus.mojo</groupId>
+ <artifactId>native2ascii-maven-plugin</artifactId>
+ <version>1.0-beta-1</version>
+ <configuration>
+ <workDir>target/classes</workDir>
+ </configuration>
+ <executions>
+ <execution>
+ <id>native2ascii-utf8</id>
+ <goals>
+ <goal>native2ascii</goal>
+ </goals>
+ <configuration>
+ <encoding>UTF8</encoding>
+ <includes>
+ <include>**/*.properties</include>
+ </includes>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+ <!-- Test plugin configuration -->
+ <plugin>
+ <artifactId>maven-dependency-plugin</artifactId>
+ <executions>
+ <execution>
+ <id>copy-war</id>
+ <phase>generate-resources</phase>
+ <goals>
+ <goal>copy</goal>
+ </goals>
+ <configuration>
+ <outputDirectory>target/dependency</outputDirectory>
+ <artifactItems>
+ <artifactItem>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>mcf-api-service</artifactId>
+ <version>${project.version}</version>
+ <type>war</type>
+ <overWrite>false</overWrite>
+ <destFileName>mcf-api-service.war</destFileName>
+ </artifactItem>
+ <artifactItem>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>mcf-authority-service</artifactId>
+ <version>${project.version}</version>
+ <type>war</type>
+ <overWrite>false</overWrite>
+ <destFileName>mcf-authority-service.war</destFileName>
+ </artifactItem>
+ <artifactItem>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>mcf-crawler-ui</artifactId>
+ <version>${project.version}</version>
+ <type>war</type>
+ <overWrite>false</overWrite>
+ <destFileName>mcf-crawler-ui.war</destFileName>
+ </artifactItem>
+ </artifactItems>
+ </configuration>
+ </execution>
+ </executions>
+ </plugin>
+
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-surefire-plugin</artifactId>
+ <configuration>
+ <excludes>
+ <exclude>**/*Postgresql*.java</exclude>
+ <exclude>**/*MySQL*.java</exclude>
+ </excludes>
+ <forkMode>always</forkMode>
+ <workingDirectory>target/test-output</workingDirectory>
+ </configuration>
+ </plugin>
+
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-failsafe-plugin</artifactId>
+ <version>2.12.3</version>
+ <configuration>
+ <skipTests>${skipITs}</skipTests>
+ <systemPropertyVariables>
+ <crawlerWarPath>../dependency/mcf-crawler-ui.war</crawlerWarPath>
+ <authorityserviceWarPath>../dependency/mcf-authority-service.war</authorityserviceWarPath>
+ <apiWarPath>../dependency/mcf-api-service.war</apiWarPath>
+ </systemPropertyVariables>
+ <excludes>
+ <exclude>**/*Postgresql*.java</exclude>
+ <exclude>**/*MySQL*.java</exclude>
+ </excludes>
+ <forkMode>always</forkMode>
+ <workingDirectory>target/test-output</workingDirectory>
+ </configuration>
+ <executions>
+ <execution>
+ <id>integration-test</id>
+ <goals>
+ <goal>integration-test</goal>
+ </goals>
+ </execution>
+ <execution>
+ <id>verify</id>
+ <goals>
+ <goal>verify</goal>
+ </goals>
+ </execution>
+ </executions>
+ </plugin>
+
+ </plugins>
+ <pluginManagement>
+ <plugins>
+ <!--This plugin's configuration is used to store Eclipse m2e settings only. It has no influence on the Maven build itself.-->
+ <plugin>
+ <groupId>org.eclipse.m2e</groupId>
+ <artifactId>lifecycle-mapping</artifactId>
+ <version>1.0.0</version>
+ <configuration>
+ <lifecycleMappingMetadata>
+ <pluginExecutions>
+ <pluginExecution>
+ <pluginExecutionFilter>
+ <groupId>
+ org.apache.maven.plugins
+ </groupId>
+ <artifactId>
+ maven-dependency-plugin
+ </artifactId>
+ <versionRange>
+ [2.8,)
+ </versionRange>
+ <goals>
+ <goal>copy</goal>
+ </goals>
+ </pluginExecutionFilter>
+ <action>
+ <ignore></ignore>
+ </action>
+ </pluginExecution>
+ <pluginExecution>
+ <pluginExecutionFilter>
+ <groupId>
+ org.codehaus.mojo
+ </groupId>
+ <artifactId>
+ native2ascii-maven-plugin
+ </artifactId>
+ <versionRange>
+ [1.0-beta-1,)
+ </versionRange>
+ <goals>
+ <goal>native2ascii</goal>
+ </goals>
+ </pluginExecutionFilter>
+ <action>
+ <ignore></ignore>
+ </action>
+ </pluginExecution>
+ <pluginExecution>
+ <pluginExecutionFilter>
+ <groupId>
+ org.apache.maven.plugins
+ </groupId>
+ <artifactId>
+ maven-remote-resources-plugin
+ </artifactId>
+ <versionRange>
+ [1.5,)
+ </versionRange>
+ <goals>
+ <goal>process</goal>
+ </goals>
+ </pluginExecutionFilter>
+ <action>
+ <ignore></ignore>
+ </action>
+ </pluginExecution>
+ </pluginExecutions>
+ </lifecycleMappingMetadata>
+ </configuration>
+ </plugin>
+ </plugins>
+ </pluginManagement>
+ </build>
+
+
+
+ <dependencies>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>mcf-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>mcf-connector-common</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>mcf-pull-agent</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>mcf-agents</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>mcf-ui-core</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-lang</groupId>
+ <artifactId>commons-lang</artifactId>
+ <version>${commons-lang.version}</version>
+ <type>jar</type>
+ </dependency>
+
+ <dependency>
+ <groupId>com.google.guava</groupId>
+ <artifactId>guava</artifactId>
+ <version>15.0</version>
+ </dependency>
+
+ <!-- Testing dependencies -->
+
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <version>${junit.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-core</artifactId>
+ <version>${mockito.version}</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>com.github.tomakehurst</groupId>
+ <artifactId>wiremock</artifactId>
+ <version>${wiremock.version}</version>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>${project.groupId}</groupId>
+ <artifactId>mcf-core</artifactId>
+ <version>${project.version}</version>
+ <type>test-jar</type>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ <version>${slf4j.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <version>${slf4j.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>commons-logging</groupId>
+ <artifactId>commons-logging</artifactId>
+ <version>1.1.1</version>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>log4j</groupId>
+ <artifactId>log4j</artifactId>
+ <version>1.2.16</version>
+ <scope>provided</scope>
+ <type>jar</type>
+ </dependency>
+ <dependency>
+ <groupId>com.googlecode.json-simple</groupId>
+ <artifactId>json-simple</artifactId>
+ <version>1.1</version>
+ </dependency>
+ <dependency>
+ <groupId>commons-codec</groupId>
+ <artifactId>commons-codec</artifactId>
+ <version>1.8</version>
+ </dependency>
+ </dependencies>
+
+</project>
diff --git a/connectors/pom.xml b/connectors/pom.xml
index 363adc2..0d05fab 100644
--- a/connectors/pom.xml
+++ b/connectors/pom.xml
@@ -65,6 +65,7 @@
<module>tika</module>
<module>documentfilter</module>
<module>searchblox</module>
+ <module>confluence</module>
</modules>
</project>
diff --git a/framework/buildfiles/connector-build.xml b/framework/buildfiles/connector-build.xml
index 97def0c..c7c344b 100644
--- a/framework/buildfiles/connector-build.xml
+++ b/framework/buildfiles/connector-build.xml
@@ -194,6 +194,9 @@
<include name="castor*.jar"/>
<include name="geronimo-javamail_1.4_spec*.jar"/>
</fileset>
+ <fileset dir="${mcf-dist}/connector-lib">
+ <include name="guava*.jar"/>
+ </fileset>
<pathelement location="build/stubclasses"/>
<pathelement location="build/wsdlclasses"/>
<pathelement location="build/xsdclasses"/>