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"/>