blob: 964e626a511b8b9ec31e05394577b5086099bfbd [file] [log] [blame]
/* $Id$ */
/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.manifoldcf.agents.output.cmisoutput;
import java.io.IOException;
import java.io.InputStream;
import java.io.InterruptedIOException;
import java.math.BigInteger;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.apache.chemistry.opencmis.client.api.Document;
import org.apache.chemistry.opencmis.client.api.Folder;
import org.apache.chemistry.opencmis.client.api.ItemIterable;
import org.apache.chemistry.opencmis.client.api.ObjectId;
import org.apache.chemistry.opencmis.client.api.QueryResult;
import org.apache.chemistry.opencmis.client.api.Repository;
import org.apache.chemistry.opencmis.client.api.Session;
import org.apache.chemistry.opencmis.client.api.SessionFactory;
import org.apache.chemistry.opencmis.client.runtime.ObjectIdImpl;
import org.apache.chemistry.opencmis.client.runtime.SessionFactoryImpl;
import org.apache.chemistry.opencmis.commons.PropertyIds;
import org.apache.chemistry.opencmis.commons.SessionParameter;
import org.apache.chemistry.opencmis.commons.data.ContentStream;
import org.apache.chemistry.opencmis.commons.enums.BindingType;
import org.apache.chemistry.opencmis.commons.enums.VersioningState;
import org.apache.chemistry.opencmis.commons.exceptions.CmisConnectionException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisContentAlreadyExistsException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisObjectNotFoundException;
import org.apache.chemistry.opencmis.commons.exceptions.CmisPermissionDeniedException;
import org.apache.chemistry.opencmis.commons.impl.dataobjects.ContentStreamImpl;
import org.apache.commons.lang.StringUtils;
import org.apache.manifoldcf.agents.interfaces.IOutputAddActivity;
import org.apache.manifoldcf.agents.interfaces.IOutputRemoveActivity;
import org.apache.manifoldcf.agents.interfaces.RepositoryDocument;
import org.apache.manifoldcf.agents.interfaces.ServiceInterruption;
import org.apache.manifoldcf.agents.output.BaseOutputConnector;
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.VersionContext;
import org.apache.manifoldcf.crawler.system.Logging;
/**
* This is the "output connector" for a CMIS-compliant repository.
*
* @author Piergiorgio Lucidi
*/
public class CmisOutputConnector extends BaseOutputConnector {
protected final static String ACTIVITY_READ = "read document";
protected static final String RELATIONSHIP_CHILD = "child";
private static final String CMIS_FOLDER_BASE_TYPE = "cmis:folder";
// Tab name properties
private static final String CMIS_SERVER_TAB_PROPERTY = "CmisOutputConnector.Server";
// Template names
/** Forward to the javascript to check the configuration parameters */
private static final String EDIT_CONFIG_HEADER_FORWARD = "editConfiguration.js";
/** Server tab template */
private static final String EDIT_CONFIG_FORWARD_SERVER = "editConfiguration_Server.html";
/** Forward to the HTML template to view the configuration parameters */
private static final String VIEW_CONFIG_FORWARD = "viewConfiguration.html";
/**
* CMIS Session handle
*/
Session session = null;
protected String username = null;
protected String password = null;
/** Endpoint protocol */
protected String protocol = null;
/** Endpoint server name */
protected String server = null;
/** Endpoint port */
protected String port = null;
/** Endpoint context path of the Alfresco webapp */
protected String path = null;
protected String repositoryId = null;
protected String binding = null;
/** Target folder for the drop zone */
protected String cmisQuery = null;
/** Flag for creating the new tree structure using timestamp**/
protected String createTimestampTree = Boolean.FALSE.toString();
protected SessionFactory factory = SessionFactoryImpl.newInstance();
protected Map<String, String> parameters = new HashMap<String, String>();
protected static final long timeToRelease = 300000L;
protected long lastSessionFetch = -1L;
protected Folder parentDropZoneFolder = null;
/** Save activity */
protected final static String ACTIVITY_INJECTION = "Injection";
/** Delete activity */
protected final static String ACTIVITY_DELETE = "Delete";
private static final String CMIS_PROPERTY_PREFIX = "cmis:";
private static final String CMIS_DOCUMENT_TYPE = "cmis:document";
/** Document accepted */
private final static int DOCUMENT_STATUS_ACCEPTED = 0;
private static final String DOCUMENT_STATUS_ACCEPTED_DESC = "Injection OK - ";
private static final String DOCUMENT_STATUS_REJECTED_DESC = "Injection KO - ";
/** Document permanently rejected */
private final static int DOCUMENT_STATUS_REJECTED = 1;
/** Document remove accepted */
private final static String DOCUMENT_DELETION_STATUS_ACCEPTED = "Remove request accepted";
/** Document remove permanently rejected */
private final static String DOCUMENT_DELETION_STATUS_REJECTED = "Remove request rejected";
/**
* Constructor
*/
public CmisOutputConnector() {
super();
}
/**
* Return the list of activities that this connector supports (i.e. writes
* into the log).
*
* @return the list.
*/
@Override
public String[] getActivitiesList() {
return new String[] { ACTIVITY_INJECTION, ACTIVITY_DELETE };
}
protected class GetSessionThread extends Thread {
protected Throwable exception = null;
public GetSessionThread() {
super();
setDaemon(true);
}
public void run() {
try {
// Create a session
parameters.clear();
// user credentials
parameters.put(SessionParameter.USER, username);
parameters.put(SessionParameter.PASSWORD, password);
String endpoint = protocol + "://" + server + ":" + port + path;
// connection settings
if (CmisOutputConfig.BINDING_ATOM_VALUE.equals(binding)) {
// AtomPub protocol
parameters.put(SessionParameter.ATOMPUB_URL, endpoint);
parameters.put(SessionParameter.BINDING_TYPE, BindingType.ATOMPUB.value());
} else if (CmisOutputConfig.BINDING_WS_VALUE.equals(binding)) {
// Web Services - SOAP - protocol
parameters.put(SessionParameter.BINDING_TYPE, BindingType.WEBSERVICES.value());
parameters.put(SessionParameter.WEBSERVICES_ACL_SERVICE, endpoint + "/ACLService?wsdl");
parameters.put(SessionParameter.WEBSERVICES_DISCOVERY_SERVICE, endpoint + "/DiscoveryService?wsdl");
parameters.put(SessionParameter.WEBSERVICES_MULTIFILING_SERVICE, endpoint + "/MultiFilingService?wsdl");
parameters.put(SessionParameter.WEBSERVICES_NAVIGATION_SERVICE, endpoint + "/NavigationService?wsdl");
parameters.put(SessionParameter.WEBSERVICES_OBJECT_SERVICE, endpoint + "/ObjectService?wsdl");
parameters.put(SessionParameter.WEBSERVICES_POLICY_SERVICE, endpoint + "/PolicyService?wsdl");
parameters.put(SessionParameter.WEBSERVICES_RELATIONSHIP_SERVICE, endpoint + "/RelationshipService?wsdl");
parameters.put(SessionParameter.WEBSERVICES_REPOSITORY_SERVICE, endpoint + "/RepositoryService?wsdl");
parameters.put(SessionParameter.WEBSERVICES_VERSIONING_SERVICE, endpoint + "/VersioningService?wsdl");
}
// create session
if (StringUtils.isEmpty(repositoryId)) {
// get a session from the first CMIS repository exposed by
// the endpoint
List<Repository> repos = null;
try {
repos = factory.getRepositories(parameters);
session = repos.get(0).createSession();
} catch (Exception e) {
Logging.connectors.error(
"CMIS: Error during getting CMIS repositories. Please check the endpoint parameters: " + e.getMessage(),
e);
this.exception = e;
}
} else {
// get a session from the repository specified in the
// configuration with its own ID
parameters.put(SessionParameter.REPOSITORY_ID, repositoryId);
try {
session = factory.createSession(parameters);
} catch (Exception e) {
Logging.connectors
.error("CMIS: Error during the creation of the new session. Please check the endpoint parameters: "
+ e.getMessage(), e);
this.exception = e;
}
}
} catch (Throwable e) {
this.exception = e;
}
}
public Throwable getException() {
return exception;
}
}
protected class CheckConnectionThread extends Thread {
protected Throwable exception = null;
public CheckConnectionThread() {
super();
setDaemon(true);
}
public void run() {
try {
session.getRepositoryInfo();
} catch (Throwable e) {
Logging.connectors.warn("CMIS: Error checking repository: " + e.getMessage(), e);
this.exception = e;
}
}
public Throwable getException() {
return exception;
}
}
protected class DestroySessionThread extends Thread {
protected Throwable exception = null;
public DestroySessionThread() {
super();
setDaemon(true);
}
public void run() {
try {
session = null;
} catch (Throwable e) {
this.exception = e;
}
}
public Throwable getException() {
return exception;
}
}
/**
* Close the connection. Call this before discarding the connection.
*/
@Override
public void disconnect() throws ManifoldCFException {
if (session != null) {
DestroySessionThread t = new DestroySessionThread();
try {
t.start();
t.join();
Throwable thr = t.getException();
if (thr != null) {
if (thr instanceof RemoteException)
throw (RemoteException) thr;
else
throw (Error) thr;
}
session = null;
lastSessionFetch = -1L;
} catch (InterruptedException e) {
t.interrupt();
throw new ManifoldCFException("Interrupted: " + e.getMessage(), e, ManifoldCFException.INTERRUPTED);
} catch (RemoteException e) {
Throwable e2 = e.getCause();
if (e2 instanceof InterruptedException || e2 instanceof InterruptedIOException)
throw new ManifoldCFException(e2.getMessage(), e2, ManifoldCFException.INTERRUPTED);
session = null;
lastSessionFetch = -1L;
// Treat this as a transient problem
Logging.connectors.warn("CMIS: Transient remote exception closing session: " + e.getMessage(), e);
}
}
username = null;
password = null;
protocol = null;
server = null;
port = null;
path = null;
binding = null;
repositoryId = null;
cmisQuery = null;
createTimestampTree = Boolean.FALSE.toString();
}
/**
* This method create a new CMIS session for a CMIS repository, if the
* repositoryId is not provided in the configuration, the connector will
* retrieve all the repositories exposed for this endpoint the it will start
* to use the first one.
*
* @param configParameters
* is the set of configuration parameters, which in this case
* describe the target appliance, basic auth configuration, etc.
* (This formerly came out of the ini file.)
*/
@Override
public void connect(ConfigParams configParams) {
super.connect(configParams);
username = params.getParameter(CmisOutputConfig.USERNAME_PARAM);
password = params.getParameter(CmisOutputConfig.PASSWORD_PARAM);
protocol = params.getParameter(CmisOutputConfig.PROTOCOL_PARAM);
server = params.getParameter(CmisOutputConfig.SERVER_PARAM);
port = params.getParameter(CmisOutputConfig.PORT_PARAM);
path = params.getParameter(CmisOutputConfig.PATH_PARAM);
binding = params.getParameter(CmisOutputConfig.BINDING_PARAM);
cmisQuery = params.getParameter(CmisOutputConfig.CMIS_QUERY_PARAM);
createTimestampTree = params.getParameter(CmisOutputConfig.CREATE_TIMESTAMP_TREE_PARAM);
if (StringUtils.isNotEmpty(params.getParameter(CmisOutputConfig.REPOSITORY_ID_PARAM))) {
repositoryId = params.getParameter(CmisOutputConfig.REPOSITORY_ID_PARAM);
}
}
/**
* Test the connection. Returns a string describing the connection integrity.
*
* @return the connection's status as a displayable string.
*/
@Override
public String check() throws ManifoldCFException {
try {
checkConnection();
return super.check();
} catch (ServiceInterruption e) {
return "Connection temporarily failed: " + e.getMessage();
} catch (ManifoldCFException e) {
return "Connection failed: " + e.getMessage();
}
}
/** Set up a session */
protected void getSession() throws ManifoldCFException, ServiceInterruption {
if (session == null) {
// Check for parameter validity
if (StringUtils.isEmpty(binding))
throw new ManifoldCFException("Parameter " + CmisOutputConfig.BINDING_PARAM + " required but not set");
if (StringUtils.isEmpty(username))
throw new ManifoldCFException("Parameter " + CmisOutputConfig.USERNAME_PARAM + " required but not set");
if (Logging.connectors.isDebugEnabled())
Logging.connectors.debug("CMIS: Username = '" + username + "'");
if (StringUtils.isEmpty(password))
throw new ManifoldCFException("Parameter " + CmisOutputConfig.PASSWORD_PARAM + " required but not set");
Logging.connectors.debug("CMIS: Password exists");
if (StringUtils.isEmpty(protocol))
throw new ManifoldCFException("Parameter " + CmisOutputConfig.PROTOCOL_PARAM + " required but not set");
if (StringUtils.isEmpty(server))
throw new ManifoldCFException("Parameter " + CmisOutputConfig.SERVER_PARAM + " required but not set");
if (StringUtils.isEmpty(port))
throw new ManifoldCFException("Parameter " + CmisOutputConfig.PORT_PARAM + " required but not set");
if (StringUtils.isEmpty(path))
throw new ManifoldCFException("Parameter " + CmisOutputConfig.PATH_PARAM + " required but not set");
if (StringUtils.isEmpty(cmisQuery))
throw new ManifoldCFException("Parameter " + CmisOutputConfig.CMIS_QUERY_PARAM + " required but not set");
long currentTime;
GetSessionThread t = new GetSessionThread();
try {
t.start();
t.join();
Throwable thr = t.getException();
if (thr != null) {
if (thr instanceof java.net.MalformedURLException)
throw (java.net.MalformedURLException) thr;
else if (thr instanceof NotBoundException)
throw (NotBoundException) thr;
else if (thr instanceof RemoteException)
throw (RemoteException) thr;
else if (thr instanceof CmisConnectionException)
throw new ManifoldCFException("CMIS: Error during getting a new session: " + thr.getMessage(), thr);
else if (thr instanceof CmisPermissionDeniedException)
throw new ManifoldCFException("CMIS: Wrong credentials during getting a new session: " + thr.getMessage(),
thr);
else
throw (Error) thr;
}
} catch (InterruptedException e) {
t.interrupt();
throw new ManifoldCFException("Interrupted: " + e.getMessage(), e, ManifoldCFException.INTERRUPTED);
} catch (java.net.MalformedURLException e) {
throw new ManifoldCFException(e.getMessage(), e);
} catch (NotBoundException e) {
// Transient problem: Server not available at the moment.
Logging.connectors.warn("CMIS: Server not up at the moment: " + e.getMessage(), e);
currentTime = System.currentTimeMillis();
throw new ServiceInterruption(e.getMessage(), currentTime + 60000L);
} catch (RemoteException e) {
Throwable e2 = e.getCause();
if (e2 instanceof InterruptedException || e2 instanceof InterruptedIOException)
throw new ManifoldCFException(e2.getMessage(), e2, ManifoldCFException.INTERRUPTED);
// Treat this as a transient problem
Logging.connectors.warn("CMIS: Transient remote exception creating session: " + e.getMessage(), e);
currentTime = System.currentTimeMillis();
throw new ServiceInterruption(e.getMessage(), currentTime + 60000L);
}
}
lastSessionFetch = System.currentTimeMillis();
}
/**
* Release the session, if it's time.
*/
protected void releaseCheck() throws ManifoldCFException {
if (lastSessionFetch == -1L)
return;
long currentTime = System.currentTimeMillis();
if (currentTime >= lastSessionFetch + timeToRelease) {
DestroySessionThread t = new DestroySessionThread();
try {
t.start();
t.join();
Throwable thr = t.getException();
if (thr != null) {
if (thr instanceof RemoteException)
throw (RemoteException) thr;
else
throw (Error) thr;
}
session = null;
lastSessionFetch = -1L;
} catch (InterruptedException e) {
t.interrupt();
throw new ManifoldCFException("Interrupted: " + e.getMessage(), e, ManifoldCFException.INTERRUPTED);
} catch (RemoteException e) {
Throwable e2 = e.getCause();
if (e2 instanceof InterruptedException || e2 instanceof InterruptedIOException)
throw new ManifoldCFException(e2.getMessage(), e2, ManifoldCFException.INTERRUPTED);
session = null;
lastSessionFetch = -1L;
// Treat this as a transient problem
Logging.connectors.warn("CMIS: Transient remote exception closing session: " + e.getMessage(), e);
}
}
}
protected void checkConnection() throws ManifoldCFException, ServiceInterruption {
while (true) {
boolean noSession = (session == null);
getSession();
long currentTime;
CheckConnectionThread t = new CheckConnectionThread();
try {
t.start();
t.join();
Throwable thr = t.getException();
if (thr != null) {
if (thr instanceof RemoteException)
throw (RemoteException) thr;
else if (thr instanceof CmisConnectionException)
throw new ManifoldCFException("CMIS: Error during checking connection: " + thr.getMessage(), thr);
else
throw (Error) thr;
}
return;
} catch (InterruptedException e) {
t.interrupt();
throw new ManifoldCFException("Interrupted: " + e.getMessage(), e, ManifoldCFException.INTERRUPTED);
} catch (RemoteException e) {
Throwable e2 = e.getCause();
if (e2 instanceof InterruptedException || e2 instanceof InterruptedIOException)
throw new ManifoldCFException(e2.getMessage(), e2, ManifoldCFException.INTERRUPTED);
if (noSession) {
currentTime = System.currentTimeMillis();
throw new ServiceInterruption("Transient error connecting to filenet service: " + e.getMessage(),
currentTime + 60000L);
}
session = null;
lastSessionFetch = -1L;
continue;
}
}
}
/**
* This method is periodically called for all connectors that are connected
* but not in active use.
*/
@Override
public void poll() throws ManifoldCFException {
if (lastSessionFetch == -1L)
return;
long currentTime = System.currentTimeMillis();
if (currentTime >= lastSessionFetch + timeToRelease) {
DestroySessionThread t = new DestroySessionThread();
try {
t.start();
t.join();
Throwable thr = t.getException();
if (thr != null) {
if (thr instanceof RemoteException)
throw (RemoteException) thr;
else
throw (Error) thr;
}
session = null;
lastSessionFetch = -1L;
} catch (InterruptedException e) {
t.interrupt();
throw new ManifoldCFException("Interrupted: " + e.getMessage(), e, ManifoldCFException.INTERRUPTED);
} catch (RemoteException e) {
Throwable e2 = e.getCause();
if (e2 instanceof InterruptedException || e2 instanceof InterruptedIOException)
throw new ManifoldCFException(e2.getMessage(), e2, ManifoldCFException.INTERRUPTED);
session = null;
lastSessionFetch = -1L;
// Treat this as a transient problem
Logging.connectors.warn("CMIS: Transient remote exception closing session: " + e.getMessage(), e);
}
}
}
/**
* 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 session != null;
}
/**
* Read the content of a resource, replace the variable ${PARAMNAME} with the
* value and copy it to the out.
*
* @param resName
* @param out
* @throws ManifoldCFException
*/
private static void outputResource(String resName, IHTTPOutput out, Locale locale, Map<String, String> paramMap)
throws ManifoldCFException {
Messages.outputResourceWithVelocity(out, locale, resName, paramMap, true);
}
/**
* Fill in a Server tab configuration parameter map for calling a Velocity
* template.
*
* @param newMap
* is the map to fill in
* @param parameters
* is the current set of configuration parameters
*/
private static void fillInServerConfigurationMap(Map<String, String> newMap, IPasswordMapperActivity mapper,
ConfigParams parameters) {
String username = parameters.getParameter(CmisOutputConfig.USERNAME_PARAM);
String password = parameters.getParameter(CmisOutputConfig.PASSWORD_PARAM);
String protocol = parameters.getParameter(CmisOutputConfig.PROTOCOL_PARAM);
String server = parameters.getParameter(CmisOutputConfig.SERVER_PARAM);
String port = parameters.getParameter(CmisOutputConfig.PORT_PARAM);
String path = parameters.getParameter(CmisOutputConfig.PATH_PARAM);
String repositoryId = parameters.getParameter(CmisOutputConfig.REPOSITORY_ID_PARAM);
String binding = parameters.getParameter(CmisOutputConfig.BINDING_PARAM);
String cmisQuery = parameters.getParameter(CmisOutputConfig.CMIS_QUERY_PARAM);
String createTimestampTree = parameters.getParameter(CmisOutputConfig.CREATE_TIMESTAMP_TREE_PARAM);
if (username == null)
username = StringUtils.EMPTY;
if (password == null)
password = StringUtils.EMPTY;
else
password = mapper.mapPasswordToKey(password);
if (protocol == null)
protocol = CmisOutputConfig.PROTOCOL_DEFAULT_VALUE;
if (server == null)
server = CmisOutputConfig.SERVER_DEFAULT_VALUE;
if (port == null)
port = CmisOutputConfig.PORT_DEFAULT_VALUE;
if (path == null)
path = CmisOutputConfig.PATH_DEFAULT_VALUE;
if (repositoryId == null)
repositoryId = StringUtils.EMPTY;
if (binding == null)
binding = CmisOutputConfig.BINDING_ATOM_VALUE;
if (cmisQuery == null)
cmisQuery = CmisOutputConfig.CMIS_QUERY_DEFAULT_VALUE;
if(createTimestampTree == null)
createTimestampTree = CmisOutputConfig.CREATE_TIMESTAMP_TREE_DEFAULT_VALUE;
newMap.put(CmisOutputConfig.USERNAME_PARAM, username);
newMap.put(CmisOutputConfig.PASSWORD_PARAM, password);
newMap.put(CmisOutputConfig.PROTOCOL_PARAM, protocol);
newMap.put(CmisOutputConfig.SERVER_PARAM, server);
newMap.put(CmisOutputConfig.PORT_PARAM, port);
newMap.put(CmisOutputConfig.PATH_PARAM, path);
newMap.put(CmisOutputConfig.REPOSITORY_ID_PARAM, repositoryId);
newMap.put(CmisOutputConfig.BINDING_PARAM, binding);
newMap.put(CmisOutputConfig.CMIS_QUERY_PARAM, cmisQuery);
newMap.put(CmisOutputConfig.CREATE_TIMESTAMP_TREE_PARAM, createTimestampTree);
}
/**
* View configuration. This method is called in the body section of the
* connector's view configuration page. Its purpose is to present the
* connection information to the user. The coder can presume that the HTML
* that is output from this configuration will be within appropriate
* <html> and <body> tags.
*
* @param threadContext
* is the local thread context.
* @param out
* is the output to which any HTML should be sent.
* @param parameters
* are the configuration parameters, as they currently exist, for
* this connection being configured.
*/
@Override
public void viewConfiguration(IThreadContext threadContext, IHTTPOutput out, Locale locale, ConfigParams parameters)
throws ManifoldCFException, IOException {
Map<String, String> paramMap = new HashMap<String, String>();
// Fill in map from each tab
fillInServerConfigurationMap(paramMap, out, parameters);
outputResource(VIEW_CONFIG_FORWARD, out, locale, paramMap);
}
/**
*
* Output the configuration header section. This method is called in the head
* section of the connector's configuration page. Its purpose is to add the
* required tabs to the list, and to output any javascript methods that might
* be needed by the configuration editing HTML.
*
* @param threadContext
* is the local thread context.
* @param out
* is the output to which any HTML should be sent.
* @param parameters
* are the configuration parameters, as they currently exist, for
* this connection being configured.
* @param tabsArray
* is an array of tab names. Add to this array any tab names that are
* specific to the connector.
*/
@Override
public void outputConfigurationHeader(IThreadContext threadContext, IHTTPOutput out, Locale locale,
ConfigParams parameters, List<String> tabsArray) throws ManifoldCFException, IOException {
// Add the Server tab
tabsArray.add(Messages.getString(locale, CMIS_SERVER_TAB_PROPERTY));
// Map the parameters
Map<String, String> paramMap = new HashMap<String, String>();
// Fill in the parameters from each tab
fillInServerConfigurationMap(paramMap, out, parameters);
// Output the Javascript - only one Velocity template for all tabs
outputResource(EDIT_CONFIG_HEADER_FORWARD, out, locale, paramMap);
}
@Override
public void outputConfigurationBody(IThreadContext threadContext, IHTTPOutput out, Locale locale,
ConfigParams parameters, String tabName) throws ManifoldCFException, IOException {
// Call the Velocity templates for each tab
// Server 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);
outputResource(EDIT_CONFIG_FORWARD_SERVER, out, locale, paramMap);
}
/**
* Process a configuration post. This method is called at the start of the
* connector's configuration page, whenever there is a possibility that form
* data for a connection has been posted. Its purpose is to gather form
* information and modify the configuration parameters accordingly. The name
* of the posted form is "editconnection".
*
* @param threadContext
* is the local thread context.
* @param variableContext
* is the set of variables available from the post, including binary
* file post information.
* @param parameters
* are the configuration parameters, as they currently exist, for
* this connection being configured.
* @return null if all is well, or a string error message if there is an error
* that should prevent saving of the connection (and cause a
* redirection to an error page).
*/
@Override
public String processConfigurationPost(IThreadContext threadContext, IPostParameters variableContext,
ConfigParams parameters) throws ManifoldCFException {
String binding = variableContext.getParameter(CmisOutputConfig.BINDING_PARAM);
if (binding != null)
parameters.setParameter(CmisOutputConfig.BINDING_PARAM, binding);
String username = variableContext.getParameter(CmisOutputConfig.USERNAME_PARAM);
if (username != null)
parameters.setParameter(CmisOutputConfig.USERNAME_PARAM, username);
String password = variableContext.getParameter(CmisOutputConfig.PASSWORD_PARAM);
if (password != null)
parameters.setParameter(CmisOutputConfig.PASSWORD_PARAM, variableContext.mapKeyToPassword(password));
String protocol = variableContext.getParameter(CmisOutputConfig.PROTOCOL_PARAM);
if (protocol != null) {
parameters.setParameter(CmisOutputConfig.PROTOCOL_PARAM, protocol);
}
String server = variableContext.getParameter(CmisOutputConfig.SERVER_PARAM);
if (server != null && !StringUtils.contains(server, '/')) {
parameters.setParameter(CmisOutputConfig.SERVER_PARAM, server);
}
String port = variableContext.getParameter(CmisOutputConfig.PORT_PARAM);
if (port != null) {
try {
Integer.parseInt(port);
parameters.setParameter(CmisOutputConfig.PORT_PARAM, port);
} catch (NumberFormatException e) {
}
}
String path = variableContext.getParameter(CmisOutputConfig.PATH_PARAM);
if (path != null) {
parameters.setParameter(CmisOutputConfig.PATH_PARAM, path);
}
String cmisQuery = variableContext.getParameter(CmisOutputConfig.CMIS_QUERY_PARAM);
if (cmisQuery != null) {
parameters.setParameter(CmisOutputConfig.CMIS_QUERY_PARAM, cmisQuery);
}
String createTimestampTree = variableContext.getParameter(CmisOutputConfig.CREATE_TIMESTAMP_TREE_PARAM);
if (createTimestampTree != null) {
parameters.setParameter(CmisOutputConfig.CREATE_TIMESTAMP_TREE_PARAM, createTimestampTree);
}
String repositoryId = variableContext.getParameter(CmisOutputConfig.REPOSITORY_ID_PARAM);
if (repositoryId != null) {
parameters.setParameter(CmisOutputConfig.REPOSITORY_ID_PARAM, repositoryId);
}
return null;
}
protected static void handleIOException(IOException e, String context)
throws ManifoldCFException, ServiceInterruption {
if (e instanceof InterruptedIOException) {
throw new ManifoldCFException(e.getMessage(), e, ManifoldCFException.INTERRUPTED);
} else {
Logging.connectors.warn("CMIS: IOException " + context + ": " + e.getMessage(), e);
throw new ManifoldCFException(e.getMessage(), e);
}
}
/**
* Check if the target drop zone is a CMIS folder
*
* @return
*/
private boolean isDropZoneFolder(String cmisQuery) {
boolean isDropZoneFolder = false;
// Get the drop zone folder
ItemIterable<QueryResult> dropZoneItemIterable = session.query(cmisQuery, false);
Iterator<QueryResult> dropZoneIterator = dropZoneItemIterable.iterator();
String baseTypeId = null;
while (dropZoneIterator.hasNext()) {
QueryResult dropZoneResult = (QueryResult) dropZoneIterator.next();
// check if it is a base folder content type
baseTypeId = dropZoneResult.getPropertyByQueryName(PropertyIds.BASE_TYPE_ID).getFirstValue().toString();
if (StringUtils.isNotEmpty(baseTypeId) && StringUtils.equals(baseTypeId, CMIS_FOLDER_BASE_TYPE)) {
String objectId = dropZoneResult.getPropertyValueById(PropertyIds.OBJECT_ID);
parentDropZoneFolder = (Folder) session.getObject(objectId);
isDropZoneFolder = true;
}
}
return isDropZoneFolder;
}
private boolean isSourceRepoCmisCompliant(RepositoryDocument document) {
Iterator<String> fields = document.getFields();
while (fields.hasNext()) {
String fieldName = (String) fields.next();
if (StringUtils.startsWith(fieldName, CMIS_PROPERTY_PREFIX)) {
return true;
}
}
return false;
}
@Override
public int addOrReplaceDocumentWithException(String documentURI, VersionContext pipelineDescription,
RepositoryDocument document, String authorityNameString, IOutputAddActivity activities)
throws ManifoldCFException, ServiceInterruption, IOException {
getSession();
boolean isDropZoneFolder = isDropZoneFolder(cmisQuery);
long startTime = System.currentTimeMillis();
Document injectedDocument = null;
String resultDescription = StringUtils.EMPTY;
Folder leafParent = null;
String fileName = StringUtils.EMPTY;
ContentStream contentStream = null;
try {
if (isDropZoneFolder) {
// Creation of the new Repository Node
fileName = document.getFileName();
Date creationDate = document.getCreatedDate();
Date lastModificationDate = document.getModifiedDate();
String mimeType = document.getMimeType();
Long binaryLength = document.getBinaryLength();
// properties
// (minimal set: name and object type id)
Map<String, Object> properties = new HashMap<String, Object>();
properties.put(PropertyIds.OBJECT_TYPE_ID, CMIS_DOCUMENT_TYPE);
properties.put(PropertyIds.NAME, fileName);
properties.put(PropertyIds.CREATION_DATE, creationDate);
properties.put(PropertyIds.LAST_MODIFICATION_DATE, lastModificationDate);
// TODO add fields management for extended properties
// if (isSourceRepoCmisCompliant(document)) {
// Iterator<String> fields = document.getFields();
// while (fields.hasNext()) {
// String fieldName = (String) fields.next();
// // ????
// }
// }
// Content Stream
InputStream inputStream = document.getBinaryStream();
contentStream = new ContentStreamImpl(fileName, BigInteger.valueOf(binaryLength), mimeType,
inputStream);
// create a major version
leafParent = getOrCreateLeafParent(parentDropZoneFolder, creationDate, Boolean.valueOf(createTimestampTree));
injectedDocument = leafParent.createDocument(properties, contentStream, VersioningState.MAJOR);
resultDescription = DOCUMENT_STATUS_ACCEPTED_DESC;
return DOCUMENT_STATUS_ACCEPTED;
} else {
resultDescription = DOCUMENT_STATUS_REJECTED_DESC;
return DOCUMENT_STATUS_REJECTED;
}
} catch (CmisContentAlreadyExistsException e) {
String documentFullPath = leafParent.getPath() + CmisOutputConnectorUtils.SLASH + fileName;
injectedDocument = (Document) session.getObjectByPath(documentFullPath);
if(injectedDocument != null) {
injectedDocument.setContentStream(contentStream, true);
}
Logging.connectors.warn(
"CMIS: Document already exists: " + documentFullPath+ CmisOutputConnectorUtils.SEP + e.getMessage(), e);
resultDescription = DOCUMENT_STATUS_ACCEPTED_DESC;
return DOCUMENT_STATUS_ACCEPTED;
} catch (Exception e) {
resultDescription = DOCUMENT_STATUS_REJECTED_DESC;
throw new ManifoldCFException(e.getMessage(), e);
} finally {
String injectedId = StringUtils.EMPTY;
if (injectedDocument != null) {
injectedId = injectedDocument.getId();
// override documentURI in a CMIS standard way for the removeDocument
// method
// documentURI -> Node UUID
documentURI = injectedId;
}
activities.recordActivity(startTime, ACTIVITY_INJECTION, document.getBinaryLength(), documentURI, injectedId,
resultDescription);
}
}
/**
* Check and create the leaf folder dedicate to inject the content
* @param folder: this is the root folder where starts the tree
* @param creationDate: this is the creation date of the current content
* @param createTimestampTree: this is the flag checked in the ManifoldCF configuration panel
* @return the target folder created using the creationDate related to the injected content
*/
private Folder getOrCreateLeafParent(Folder folder, Date creationDate, boolean createTimestampTree) {
Folder leafParent = folder;
if (createTimestampTree) {
GregorianCalendar calendar = new GregorianCalendar();
calendar.setTime(creationDate);
String year = String.valueOf(calendar.get(GregorianCalendar.YEAR));
String month = String.valueOf((calendar.get(GregorianCalendar.MONTH)+1));
String day = String.valueOf(calendar.get(GregorianCalendar.DAY_OF_MONTH));
//Check and create all the new folders
Folder yearFolder = createFolderIfNotExist(leafParent, year);
Folder monthFolder = createFolderIfNotExist(yearFolder, month);
Folder dayFolder = createFolderIfNotExist(monthFolder, day);
leafParent = dayFolder;
}
return leafParent;
}
/**
* Create a new CMIS folder as a child node of leafParent
* @param leafParent
* @param folderName
* @return the current CMIS folder if exists otherwise it will return a new one
*/
private Folder createFolderIfNotExist(Folder leafParent, String folderName) {
Folder folder = null;
try {
folder = (Folder) session.getObjectByPath(leafParent.getPath() + CmisOutputConnectorUtils.SLASH + folderName);
} catch (CmisObjectNotFoundException onfe) {
Map<String, Object> props = new HashMap<String, Object>();
props.put(PropertyIds.OBJECT_TYPE_ID, "cmis:folder");
props.put(PropertyIds.NAME, folderName);
folder = leafParent.createFolder(props);
String folderId = folder.getId();
String folderPath = folder.getPath();
Logging.connectors.info(
"CMIS: Created a new folder - id: " + folderId +
" | Path: " + folderPath);
}
return folder;
}
@Override
public void removeDocument(String documentURI, String outputDescription, IOutputRemoveActivity activities)
throws ManifoldCFException, ServiceInterruption {
getSession();
long startTime = System.currentTimeMillis();
ObjectId objectId = new ObjectIdImpl(documentURI);
String result = StringUtils.EMPTY;
try {
session.delete(objectId);
result = DOCUMENT_DELETION_STATUS_ACCEPTED;
} catch (Exception e) {
result = DOCUMENT_DELETION_STATUS_REJECTED;
throw new ManifoldCFException(e.getMessage(), e);
} finally {
activities.recordActivity(startTime, ACTIVITY_DELETE, null, documentURI, objectId.toString(), result);
}
}
}