/*!
* Copyright 2010 - 2015 Pentaho Corporation. All rights reserved.
*
* Licensed 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.pentaho.di.repository.pur;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import javax.xml.namespace.QName;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.Service;
import javax.xml.ws.handler.MessageContext;
import javax.xml.ws.soap.SOAPBinding;
import org.apache.commons.lang.StringUtils;
import org.pentaho.di.core.util.ExecutorUtil;
import org.pentaho.di.repository.pur.WebServiceSpecification.ServiceType;
import org.pentaho.platform.repository2.unified.webservices.jaxws.IUnifiedRepositoryJaxwsWebService;
import org.pentaho.platform.security.policy.rolebased.ws.IAuthorizationPolicyWebService;
import org.pentaho.platform.security.policy.rolebased.ws.IRoleAuthorizationPolicyRoleBindingDaoWebService;
import org.pentaho.platform.security.userrole.ws.IUserRoleListWebService;
import org.pentaho.platform.security.userroledao.ws.IUserRoleWebService;
import com.pentaho.di.services.PentahoDiPlugin;
import com.pentaho.pdi.ws.IRepositorySyncWebService;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.client.filter.HTTPBasicAuthFilter;
import com.sun.xml.ws.developer.JAXWSProperties;
/**
* Web service factory. Not a true factory in that the things that this factory can create are not configurable. But it
* does cache the services.
*
* @author mlowery
*/
public class WebServiceManager implements ServiceManager {
/**
* Header name must match that specified in ProxyTrustingFilter. Note that an header has the following form: initial
* capital letter followed by all lowercase letters.
*/
private static final String TRUST_USER = "_trust_user_"; //$NON-NLS-1$
private static final String NAMESPACE_URI = "http://www.pentaho.org/ws/1.0"; //$NON-NLS-1$
private static final ExecutorService executor = ExecutorUtil.getExecutor();
private final Map<String, Future<Object>> serviceCache = new HashMap<String, Future<Object>>();
private final Map<Class<?>, WebServiceSpecification> serviceNameMap;
private final String baseUrl;
private final String lastUsername;
private Map<Class<?>, WebServiceSpecification> tempServiceNameMap; // hold the map while building
public WebServiceManager( String baseUrl, String username ) {
this.baseUrl = baseUrl;
this.lastUsername = username;
tempServiceNameMap = new HashMap<Class<?>, WebServiceSpecification>();
registerWsSpecification( IUnifiedRepositoryJaxwsWebService.class, "unifiedRepository" ); //$NON-NLS-1$
registerWsSpecification( IRepositorySyncWebService.class, "repositorySync" ); //$NON-NLS-1$
registerWsSpecification( IUserRoleListWebService.class, "userRoleListService" ); //$NON-NLS-1$
registerWsSpecification( IUserRoleWebService.class, "userRoleService" ); //$NON-NLS-1$
registerWsSpecification( IRoleAuthorizationPolicyRoleBindingDaoWebService.class, "roleBindingDao" ); //$NON-NLS-1$
registerWsSpecification( IAuthorizationPolicyWebService.class, "authorizationPolicy" ); //$NON-NLS-1$
registerRestSpecification( PentahoDiPlugin.PurRepositoryPluginApiRevision.class, "purRepositoryPluginApiRevision" ); //$NON-NLS-1$
this.serviceNameMap = Collections.unmodifiableMap( tempServiceNameMap );
tempServiceNameMap = null;
}
@Override
@SuppressWarnings( "unchecked" )
public <T> T createService( final String username, final String password, final Class<T> clazz )
throws MalformedURLException {
final Future<Object> resultFuture;
synchronized ( serviceCache ) {
// if this is true, a coder did not make sure that clearServices was called on disconnect
if ( lastUsername != null && !lastUsername.equals( username ) ) {
throw new IllegalStateException();
}
final WebServiceSpecification webServiceSpecification = serviceNameMap.get( clazz );
final String serviceName = webServiceSpecification.getServiceName();
if ( serviceName == null ) {
throw new IllegalStateException();
}
if ( webServiceSpecification.getServiceType().equals( ServiceType.JAX_WS ) ) {
// build the url handling whether or not baseUrl ends with a slash
// String baseUrl = repositoryMeta.getRepositoryLocation().getUrl();
final URL url =
new URL( baseUrl + ( baseUrl.endsWith( "/" ) ? "" : "/" ) + "webservices/" + serviceName + "?wsdl" ); //$NON-NLS-1$ //$NON-NLS-2$
String key = url.toString() + '_' + serviceName + '_' + clazz.getName();
if ( !serviceCache.containsKey( key ) ) {
resultFuture = executor.submit( new Callable<Object>() {
@Override
public Object call() throws Exception {
Service service = Service.create( url, new QName( NAMESPACE_URI, serviceName ) );
T port = service.getPort( clazz );
// add TRUST_USER if necessary
if ( StringUtils.isNotBlank( System.getProperty( "pentaho.repository.client.attemptTrust" ) ) ) {
( (BindingProvider) port ).getRequestContext().put( MessageContext.HTTP_REQUEST_HEADERS,
Collections.singletonMap( TRUST_USER, Collections.singletonList( username ) ) );
} else {
// http basic authentication
( (BindingProvider) port ).getRequestContext().put( BindingProvider.USERNAME_PROPERTY, username );
( (BindingProvider) port ).getRequestContext().put( BindingProvider.PASSWORD_PROPERTY, password );
}
// accept cookies to maintain session on server
( (BindingProvider) port ).getRequestContext().put( BindingProvider.SESSION_MAINTAIN_PROPERTY, true );
// support streaming binary data
// TODO mlowery this is not portable between JAX-WS implementations (uses com.sun)
( (BindingProvider) port ).getRequestContext().put( JAXWSProperties.HTTP_CLIENT_STREAMING_CHUNK_SIZE,
8192 );
SOAPBinding binding = (SOAPBinding) ( (BindingProvider) port ).getBinding();
binding.setMTOMEnabled( true );
return port;
}
} );
serviceCache.put( key, resultFuture );
} else {
resultFuture = serviceCache.get( key );
}
} else {
if ( webServiceSpecification.getServiceType().equals( ServiceType.JAX_RS ) ) {
String key = baseUrl.toString() + '_' + serviceName + '_' + clazz.getName();
if ( !serviceCache.containsKey( key ) ) {
resultFuture = executor.submit( new Callable<Object>() {
@Override
public Object call() throws Exception {
ClientConfig clientConfig = new DefaultClientConfig();
Client client = Client.create( clientConfig );
client.addFilter( new HTTPBasicAuthFilter( username, password ) );
Class<?>[] parameterTypes = new Class<?>[] { Client.class, URI.class };
String factoryClassName = webServiceSpecification.getServiceClass().getName();
factoryClassName = factoryClassName.substring( 0, factoryClassName.lastIndexOf( "$" ) );
Class<?> factoryClass = Class.forName( factoryClassName );
Method method =
factoryClass.getDeclaredMethod( webServiceSpecification.getServiceName(), parameterTypes );
T port = (T) method.invoke( null, new Object[] { client, new URI( baseUrl + "/plugin" ) } );
return port;
}
} );
serviceCache.put( key, resultFuture );
} else {
resultFuture = serviceCache.get( key );
}
} else {
resultFuture = null;
}
}
try {
if ( clazz.isInterface() ) {
return UnifiedRepositoryInvocationHandler.forObject( (T) resultFuture.get(), clazz );
} else {
return (T) resultFuture.get();
}
} catch ( InterruptedException e ) {
throw new RuntimeException( e );
} catch ( ExecutionException e ) {
Throwable cause = e.getCause();
if ( cause != null ) {
if ( cause instanceof RuntimeException ) {
throw (RuntimeException) cause;
} else if ( cause instanceof MalformedURLException ) {
throw (MalformedURLException) cause;
}
}
throw new RuntimeException( e );
}
}
}
@Override
public synchronized void close() {
serviceCache.clear();
}
private void registerWsSpecification( Class<?> serviceClass, String serviceName ) {
registerSpecification( WebServiceSpecification.getWsServiceSpecification( serviceClass, serviceName ) );
}
private void registerRestSpecification( Class<?> serviceClass, String serviceName ) {
try {
registerSpecification( WebServiceSpecification.getRestServiceSpecification( serviceClass, serviceName ) );
} catch ( NoSuchMethodException e ) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch ( SecurityException e ) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
private void registerSpecification( WebServiceSpecification webServiceSpecification ) {
tempServiceNameMap.put( webServiceSpecification.getServiceClass(), webServiceSpecification );
}
}