/*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software
* Foundation.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html
* or from the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* Copyright 2009 Pentaho Corporation. All rights reserved.
*
* Created Nov 4, 2009
* @author jdixon
*/
package org.pentaho.platform.dataaccess.client;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.impl.builder.StAXOMBuilder;
import org.apache.axis2.AxisFault;
import org.apache.axis2.databinding.utils.BeanUtil;
import org.apache.axis2.engine.ObjectSupplier;
import org.apache.commons.httpclient.Credentials;
import org.apache.commons.httpclient.HttpClient;
import org.apache.commons.httpclient.HttpMethod;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.UsernamePasswordCredentials;
import org.apache.commons.httpclient.auth.AuthScope;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.httpclient.methods.PostMethod;
import org.apache.commons.httpclient.methods.RequestEntity;
import org.apache.commons.httpclient.methods.StringRequestEntity;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Node;
import org.pentaho.database.model.DatabaseAccessType;
import org.pentaho.database.model.DatabaseConnection;
import org.pentaho.database.model.IDatabaseConnection;
import org.pentaho.platform.dataaccess.datasource.IConnection;
import org.pentaho.platform.dataaccess.datasource.beans.Connection;
import org.pentaho.platform.dataaccess.datasource.wizard.service.ConnectionServiceException;
import org.pentaho.platform.dataaccess.datasource.wizard.service.gwt.IConnectionService;
import com.thoughtworks.xstream.XStream;
import com.thoughtworks.xstream.io.xml.DomDriver;
/**
* Provides a simple API for working with the connections web service.
*
* This class implements IConnectionService
* {@link org.pentaho.platform.dataaccess.datasource.wizard.service.gwt.IConnectionService }
*
* Example code for using this class:
*
* ConnectionServiceClient serviceClient = new ConnectionServiceClient();
* serviceClient.setHost("http://localhost:8080/pentaho");
* serviceClient.setUserId("joe");
* serviceClient.setPassword("password");
* List<IConnection> connections = serviceClient.getConnections();
*
* @author jamesdixon
*
*/
public class ConnectionServiceClient implements IConnectionService, ObjectSupplier {
protected static final Log logger = LogFactory.getLog(ConnectionServiceClient.class);
private String serviceUrl; // e.g. http://someserver:port/pentaho
private String userId;
private String password;
private HttpClient httpClientInstance = null;
/**
* Returns an instance of an HttpClient. Only one is created per
* ConnectionServiceClient so all calls should be made synchronously.
* @return The HTTP client to be used for web service calls
*/
private HttpClient getClient() {
if( httpClientInstance == null ) {
httpClientInstance = new HttpClient();
if ((userId != null) && (userId.length() > 0) && (password != null)
&& (password.length() > 0)) {
Credentials creds = new UsernamePasswordCredentials(userId, password);
httpClientInstance.getState().setCredentials(AuthScope.ANY, creds);
httpClientInstance.getParams().setAuthenticationPreemptive(true);
}
}
return httpClientInstance;
}
/**
* Adds a connection to the server's configuration. Returns true if the attempt was successful.
* setHost(), setUserId() and setPassword() must be called before this
* method is called.
* @param connection The connection to be added
* @return True if the addition was successful
*/
public boolean addConnection(IConnection connection) throws ConnectionServiceException {
String xml = getConnectionXml( connection );
PostMethod callMethod = new PostMethod( serviceUrl+"/addConnection" ); //$NON-NLS-1$
// add the xml to the request entity
RequestEntity requestEntity = new StringRequestEntity( xml );
callMethod.setRequestEntity( requestEntity );
// get the result and parse de-serialize it
Node node = getResultNode( callMethod );
return node != null && Boolean.parseBoolean( this.getNodeText(node) );
}
public DatabaseConnection convertFromConnection(IConnection connection) throws ConnectionServiceException {
String xml = getConnectionXml( connection );
PostMethod callMethod = new PostMethod( serviceUrl+"/convertFromConnection" ); //$NON-NLS-1$
// add the xml to the request entity
RequestEntity requestEntity = new StringRequestEntity( xml );
callMethod.setRequestEntity( requestEntity );
// get the result and parse de-serialize it
Document doc = getResultDocument( callMethod );
try {
return getResponseDatabaseConnection( doc.getRootElement() );
} catch ( XMLStreamException e ) {
e.printStackTrace();
throw new ConnectionServiceException(e);
} catch ( AxisFault e ) {
e.printStackTrace();
throw new ConnectionServiceException(e);
} catch ( Exception e ) {
e.printStackTrace();
throw new ConnectionServiceException(e);
}
}
public Document serializeDatabaseConnection( DatabaseConnection databaseConnection ) {
XStream xstream = new XStream(new DomDriver());
xstream.alias("databaseConnection", DatabaseConnection.class); //$NON-NLS-1$
xstream.alias("accessType", DatabaseAccessType.class); //$NON-NLS-1$
String xml = xstream.toXML(databaseConnection);
System.out.println(xml);
return null;
}
protected Connection getResponseConnection( Element rootNode ) throws XMLStreamException, AxisFault {
return (Connection) getResponseObject( Connection.class, rootNode );
}
protected DatabaseConnection getResponseDatabaseConnection( Element rootNode ) throws XMLStreamException, AxisFault {
DatabaseConnection result = (DatabaseConnection) getResponseObject( DatabaseConnection.class, rootNode );
String accessTypeValue = result.getAccessTypeValue();
DatabaseAccessType accessType = result.getAccessType();
if( accessType == null || accessType.getValue() != accessTypeValue ) {
result.setAccessType(DatabaseAccessType.valueOf(accessTypeValue));
}
return result;
}
protected Object getResponseObject( Class clazz, Element rootNode ) throws XMLStreamException, AxisFault{
Object results[] = getResponseObjects( new Object[] { clazz }, rootNode );
if( results == null || results.length == 0 ) {
return null;
}
return results[0];
}
protected Object[] getResponseArray( Class clazz, Element rootNode ) throws XMLStreamException, AxisFault {
// find out how many responses there are
List nodes = rootNode.selectNodes( "*/*/*" ); //$NON-NLS-1$
int count = nodes.size();
Object types[] = new Object[count];
for( int idx=0; idx<count; idx++ ) {
types[idx] = clazz;
}
return getResponseObjects( types, rootNode );
}
protected Object[] getResponseObjects( Object[] types, Element rootNode ) throws XMLStreamException, AxisFault {
ByteArrayInputStream in = new ByteArrayInputStream( rootNode.asXML().getBytes() );
XMLStreamReader parser = XMLInputFactory.newInstance().createXMLStreamReader(in);
StAXOMBuilder builder = new StAXOMBuilder(parser);
//get the root element (in this case the envelope)
OMElement omElement = builder.getDocumentElement();
OMElement bodyElement = omElement.getFirstElement();
OMElement responseElement = bodyElement.getFirstElement();
Object results[] = BeanUtil.deserialize( responseElement, types, this );
return results;
}
public DatabaseConnection deserializeDatabaseConnection(Element rootNode) throws XMLStreamException, AxisFault {
System.out.println(rootNode.asXML());
ByteArrayInputStream in = new ByteArrayInputStream( rootNode.asXML().getBytes() );
XMLStreamReader parser = XMLInputFactory.newInstance().createXMLStreamReader(in);
StAXOMBuilder builder = new StAXOMBuilder(parser);
//get the root element (in this case the envelope)
OMElement omElement = builder.getDocumentElement();
OMElement bodyElement = omElement.getFirstElement();
OMElement responseElement = bodyElement.getFirstElement();
Object types[] = new Object[] { DatabaseConnection.class };
Object results[] = BeanUtil.deserialize( responseElement, types, this );
if( results == null || results.length == 0 ) {
return null;
}
return (DatabaseConnection) results[0];
}
public IConnection convertToConnection(IDatabaseConnection arg0) throws ConnectionServiceException {
// TODO Auto-generated method stub
return null;
}
/**
* Deletes a connection from the server's configuration.
* Returns true if the attempt was successful.
* setHost(), setUserId() and setPassword() must be called before this
* method is called.
* @param connection The connection to be deleted
* @return True if the deletion was successful
*/
public boolean deleteConnection(IConnection connection) throws ConnectionServiceException {
return deleteConnection( connection.getName() );
}
/**
* Deletes a connection from the server's configuration.
* Returns true if the attempt was successful.
* setHost(), setUserId() and setPassword() must be called before this
* method is called.
* @param connectionName The name of the connection to be deleted
* @return True if the deletion was successful
*/
public boolean deleteConnection(String connectionName) throws ConnectionServiceException {
GetMethod callMethod = new GetMethod( serviceUrl+"/deleteConnectionByName" ); //$NON-NLS-1$
callMethod.setQueryString("name="+connectionName); //$NON-NLS-1$
// get the result and parse de-serialize it
Node node = getResultNode( callMethod );
return node != null && Boolean.parseBoolean( this.getNodeText(node) );
}
/**
* Returns a specified connection. Returns null is the connection is not
* defined in the server's configuration.
* setHost(), setUserId() and setPassword() must be called before this
* method is called.
* @param connectionName The name of the connection to be returned
* @return The connection requested
*/
public IConnection getConnectionByName(String connectionName) throws ConnectionServiceException {
PostMethod callMethod = new PostMethod( serviceUrl+"/getConnectionByName" ); //$NON-NLS-1$
// add the xml to the request entity
String xml = getRequestXml( new Parameter( "name", connectionName ) ); //$NON-NLS-1$
RequestEntity requestEntity = new StringRequestEntity( xml );
callMethod.setRequestEntity( requestEntity );
// get the result and parse de-serialize it
Document resultDoc = getResultDocument( callMethod );
try {
return getResponseConnection( resultDoc.getRootElement() );
} catch ( XMLStreamException e ) {
e.printStackTrace();
throw new ConnectionServiceException(e);
} catch ( AxisFault e ) {
e.printStackTrace();
throw new ConnectionServiceException(e);
} catch ( Exception e ) {
e.printStackTrace();
throw new ConnectionServiceException(e);
}
}
/**
* Generates a SOAP request for an Axis service.
* @param params
* @return
*/
protected String getRequestXml( Parameter... params ) {
Document doc = DocumentHelper.createDocument();
Element envelopeNode = DocumentHelper.createElement( "soapenv:Envelope" ); //$NON-NLS-1$
envelopeNode.addAttribute("xmlns:soapenv", "http://www.w3.org/2003/05/soap-envelope"); //$NON-NLS-1$ //$NON-NLS-2$
envelopeNode.addAttribute("xmlns:wsa", "http://www.w3.org/2005/08/addressing"); //$NON-NLS-1$ //$NON-NLS-2$
envelopeNode.addAttribute("xmlns:pho", "http://impl.service.wizard.datasource.dataaccess.platform.pentaho.org"); //$NON-NLS-1$ //$NON-NLS-2$
doc.add( envelopeNode );
// create a Body node
Element bodyNode = DocumentHelper.createElement( "soapenv:Body" ); //$NON-NLS-1$
envelopeNode.add( bodyNode );
if( params == null || params.length == 0 ) {
return doc.asXML();
}
// create a parameter called 'parameters'
Element parametersNode = DocumentHelper.createElement( "pho:parameters" ); //$NON-NLS-1$
bodyNode.add(parametersNode);
for( Parameter param : params ) {
// create a parameter called 'name'
Element nameNode = DocumentHelper.createElement( "pho:"+param.getName() ); //$NON-NLS-1$
parametersNode.add(nameNode);
nameNode.setText(param.getValue().toString());
nameNode.addAttribute("type", param.getValue().getClass().getCanonicalName()); //$NON-NLS-1$
}
return doc.asXML();
}
/**
* Returns a list of connections known to the server. Each one is an IConnection object.
* Returns an empty list if no connections are defined.
* setHost(), setUserId() and setPassword() must be called before this
* method is called.
* @return List of the connections
*/
public List<IConnection> getConnections() throws ConnectionServiceException {
PostMethod callMethod = new PostMethod( serviceUrl+"/getConnections" ); //$NON-NLS-1$
String xml = getRequestXml( );
RequestEntity requestEntity = new StringRequestEntity( xml );
callMethod.setRequestEntity( requestEntity );
List<IConnection> connections = new ArrayList<IConnection>();
// get the result and parse de-serialize it
Document resultDoc = getResultDocument( callMethod );
try {
Object connectionArray[] = new Object[0];
connectionArray = getResponseArray(Connection.class, resultDoc.getRootElement() );
for( Object connection : connectionArray ) {
connections.add( (Connection) connection );
}
} catch ( XMLStreamException e ) {
e.printStackTrace();
throw new ConnectionServiceException(e);
} catch ( AxisFault e ) {
e.printStackTrace();
throw new ConnectionServiceException(e);
} catch ( Exception e ) {
e.printStackTrace();
throw new ConnectionServiceException(e);
}
return connections;
}
/**
* Returns the text with the provided node. If the node is null, a null String is returned.
* @param node Dom4J node
* @return Text within the node
*/
protected String getNodeText( Node node ) {
if( node == null ) {
return null;
} else {
return node.getText();
}
}
/**
* Returns XML for a connection object suitable for submitting to the connection web service
* @param connection Connection object to be encoded as XML
* @return XML serialization of the connection object
*/
protected String getConnectionXml( IConnection connection ) {
Document doc = DocumentHelper.createDocument();
// create a SOAP envelope and specify namespaces
Element envelopeNode = DocumentHelper.createElement( "soapenv:Envelope" ); //$NON-NLS-1$
envelopeNode.addAttribute("xmlns:soapenv", "http://www.w3.org/2003/05/soap-envelope"); //$NON-NLS-1$ //$NON-NLS-2$
envelopeNode.addAttribute("xmlns:wsa", "http://www.w3.org/2005/08/addressing"); //$NON-NLS-1$ //$NON-NLS-2$
envelopeNode.addAttribute("xmlns:pho", "http://impl.service.wizard.datasource.dataaccess.platform.pentaho.org"); //$NON-NLS-1$ //$NON-NLS-2$
doc.add( envelopeNode );
// create a Body node
Element bodyNode = DocumentHelper.createElement( "soapenv:Body" ); //$NON-NLS-1$
envelopeNode.add( bodyNode );
// create a parameter called 'connection'
Element parameterNode = DocumentHelper.createElement( "pho:connection" ); //$NON-NLS-1$
bodyNode.add(parameterNode);
// create a Connection node with a type of org.pentaho.platform.dataaccess.datasource.beans.Connection
Element connectionNode = DocumentHelper.createElement( "pho:Connection" ); //$NON-NLS-1$
connectionNode.addAttribute("type", "org.pentaho.platform.dataaccess.datasource.beans.Connection"); //$NON-NLS-1$ //$NON-NLS-2$
parameterNode.add(connectionNode);
// add the driver class of the connection
Element node = DocumentHelper.createElement( "driverClass" ); //$NON-NLS-1$
node.setText( connection.getDriverClass() );
connectionNode.add(node);
// add the name of the connection
node = DocumentHelper.createElement( "name" ); //$NON-NLS-1$
node.setText( connection.getName() );
connectionNode.add(node);
// add the password for the connection
node = DocumentHelper.createElement( "password" ); //$NON-NLS-1$
node.setText( connection.getPassword() );
connectionNode.add(node);
// add the url of the connection
node = DocumentHelper.createElement( "url" ); //$NON-NLS-1$
node.setText( connection.getUrl() );
connectionNode.add(node);
// add the user name for the connection
node = DocumentHelper.createElement( "username" ); //$NON-NLS-1$
node.setText( connection.getUsername() );
connectionNode.add(node);
// return the XML
return doc.asXML();
}
/**
* Tests a provided connection on the server. This does not store the
* connection on the server, just validates that the connection works
* in the server's environment.
* @param connection The connection to be tested
*/
public boolean testConnection(IConnection connection) throws ConnectionServiceException {
String xml = getConnectionXml( connection );
PostMethod callMethod = new PostMethod( serviceUrl+"/testConnection" ); //$NON-NLS-1$
// add the xml to the request entity
RequestEntity requestEntity = new StringRequestEntity( xml );
callMethod.setRequestEntity( requestEntity );
// get the result and parse de-serialize it
Node node = getResultNode( callMethod );
return node != null && Boolean.parseBoolean( this.getNodeText(node) );
}
public boolean updateConnection(IConnection connection) throws ConnectionServiceException {
String xml = getConnectionXml( connection );
PostMethod callMethod = new PostMethod( serviceUrl+"/updateConnection" ); //$NON-NLS-1$
// add the xml to the request entity
RequestEntity requestEntity = new StringRequestEntity( xml );
callMethod.setRequestEntity( requestEntity );
// get the result and parse de-serialize it
Node node = getResultNode( callMethod );
return node != null && Boolean.parseBoolean( this.getNodeText(node) );
}
/**
* Sets the host server, port, and context. For example
* http://server:port/pentaho
* @param host The host address and context
*/
public void setHost(String host) {
// create the service url
serviceUrl = host+"/content/ws-run/soapConnectionService"; //$NON-NLS-1$
}
/**
* Sets the user id to use to connect to the server
* @param userId
*/
public void setUserId(String userId) {
this.userId = userId;
}
/**
* Sets the password to use to connect to the server
* @param userId
*/
public void setPassword(String password) {
this.password = password;
}
protected List getResultNodes( HttpMethod callMethod ) throws ConnectionServiceException {
Document doc = getResultDocument( callMethod );
return doc.selectNodes( "//return" ); //$NON-NLS-1$
}
protected Node getResultNode( HttpMethod callMethod ) throws ConnectionServiceException {
Document doc = getResultDocument( callMethod );
return doc.selectSingleNode( "//return" ); //$NON-NLS-1$
}
/**
* Submits an HTTP result with the provided HTTPMethod and returns a dom4j document of the
* response
* @param callMethod
* @return
* @throws ConnectionServiceException
*/
protected Document getResultDocument( HttpMethod callMethod ) throws ConnectionServiceException {
try {
HttpClient client = getClient();
// execute the HTTP call
int status = client.executeMethod(callMethod);
if( status != HttpStatus.SC_OK) {
throw new ConnectionServiceException("Web service call failed with code "+status); //$NON-NLS-1$
}
// get the result as a string
InputStream in = callMethod.getResponseBodyAsStream();
byte buffer[] = new byte[2048];
int n = in.read(buffer);
StringBuilder sb = new StringBuilder();
while( n != -1 ) {
sb.append( new String( buffer, 0, n ) );
n = in.read(buffer);
}
String result = sb.toString();
// convert to XML
return DocumentHelper.parseText( result );
} catch (IOException e) {
throw new ConnectionServiceException(e);
} catch (DocumentException e) {
throw new ConnectionServiceException(e);
}
}
/**
* Returns an object for the specified class.
* This is in the ObjectSupplier interface used by
* the BeanUtil deserialize methods.
*/
public Object getObject(Class clazz) throws AxisFault {
try {
System.out.println( clazz.getCanonicalName() );
Constructor[] cons = clazz.getConstructors();
if( cons.length > 0 ) {
return clazz.newInstance();
}
return null;
} catch (IllegalAccessException e) {
throw new AxisFault( e.getLocalizedMessage(), e );
} catch (InstantiationException e) {
throw new AxisFault( e.getLocalizedMessage(), e );
}
}
private class Parameter {
public String name;
public Object value;
public Parameter( String name, Object value ) {
this.name = name;
this.value = value;
}
public Object getValue() {
return value;
}
public void setValue(Object value) {
this.value = value;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
}