/** * 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.cxf.jca.outbound; import java.io.PrintWriter; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import java.net.MalformedURLException; import java.net.URL; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import javax.resource.NotSupportedException; import javax.resource.ResourceException; import javax.resource.spi.ConnectionEvent; import javax.resource.spi.ConnectionEventListener; import javax.resource.spi.ConnectionRequestInfo; import javax.resource.spi.LocalTransaction; import javax.resource.spi.ManagedConnection; import javax.resource.spi.ManagedConnectionMetaData; import javax.security.auth.Subject; import javax.transaction.xa.XAResource; import javax.xml.ws.BindingProvider; import org.apache.cxf.Bus; import org.apache.cxf.BusFactory; import org.apache.cxf.bus.spring.SpringBusFactory; import org.apache.cxf.common.logging.LogUtils; import org.apache.cxf.configuration.Configurer; import org.apache.cxf.endpoint.Client; import org.apache.cxf.frontend.ClientProxy; import org.apache.cxf.frontend.ClientProxyFactoryBean; import org.apache.cxf.jaxws.EndpointUtils; import org.apache.cxf.jaxws.JaxWsProxyFactoryBean; import org.apache.cxf.jca.core.logging.LoggerHelper; /** * Represents a "physical" connection to EIS, which provides access to target * web service. ManagedConnectionImpl creates connection handles for * applications to use the connection backed by this object. */ public class ManagedConnectionImpl implements ManagedConnection { private static final Logger LOG = LogUtils.getL7dLogger(ManagedConnectionImpl.class); private Set<ConnectionEventListener> listeners = Collections.synchronizedSet(new HashSet<>()); private Map<Object, Subject> handles = Collections.synchronizedMap(new HashMap<Object, Subject>()); private PrintWriter printWriter; private ManagedConnectionFactoryImpl mcf; private ConnectionRequestInfo connReqInfo; private boolean isClosed; private Bus bus; private Object associatedHandle; private Object clientProxy; public ManagedConnectionImpl(ManagedConnectionFactoryImpl mcf, ConnectionRequestInfo connReqInfo, Subject subject) { this.mcf = mcf; this.connReqInfo = connReqInfo; } /* ------------------------------------------------------------------- * ManagedConnection Methods */ public void addConnectionEventListener(ConnectionEventListener listener) { if (LOG.isLoggable(Level.FINER)) { LOG.finer("add listener : " + listener); } listeners.add(listener); } public void associateConnection(Object connection) throws ResourceException { if (LOG.isLoggable(Level.FINER)) { LOG.finer("associate handle : " + connection); } associatedHandle = connection; // nothing needs to be done as app gets a copy of client proxy } public void cleanup() throws ResourceException { if (LOG.isLoggable(Level.FINER)) { LOG.finer("cleanup"); } handles.clear(); isClosed = false; } public void destroy() throws ResourceException { if (LOG.isLoggable(Level.FINER)) { LOG.finer("destroy"); } Client client = ClientProxy.getClient(clientProxy); client.destroy(); handles.clear(); isClosed = false; bus = null; connReqInfo = null; } public Object getConnection(Subject subject, ConnectionRequestInfo cxRequestInfo) throws ResourceException { if (LOG.isLoggable(Level.FINER)) { LOG.finer("get handle for subject=" + subject + " cxRequestInfo=" + cxRequestInfo); } if (isClosed) { throw new ResourceException("connection has been closed"); } // check request info if (!connReqInfo.equals(cxRequestInfo)) { throw new ResourceException("connection request info: " + cxRequestInfo + " does not match " + connReqInfo); } CXFConnectionSpec spec = (CXFConnectionSpec)cxRequestInfo; Object handle = createConnectionHandle(spec); handles.put(handle, subject); associatedHandle = handle; return handle; } public LocalTransaction getLocalTransaction() throws ResourceException { throw new NotSupportedException("LocalTransaction is not supported."); } public PrintWriter getLogWriter() throws ResourceException { return printWriter; } public ManagedConnectionMetaData getMetaData() throws ResourceException { return new CXFManagedConnectionMetaData(getUserName()); } public XAResource getXAResource() throws ResourceException { throw new NotSupportedException("XAResource is not supported."); } public void removeConnectionEventListener(ConnectionEventListener listener) { listeners.remove(listener); } public void setLogWriter(PrintWriter out) throws ResourceException { printWriter = out; if (printWriter != null) { LoggerHelper.initializeLoggingOnWriter(printWriter); } } /* ------------------------------------------------------------------- * Public Methods */ public ConnectionRequestInfo getRequestInfo() { return connReqInfo; } public ManagedConnectionFactoryImpl getManagedConnectionFactoryImpl() { return mcf; } /* ------------------------------------------------------------------- * Private Methods */ private void sendEvent(final ConnectionEvent coEvent) { synchronized (listeners) { Iterator<ConnectionEventListener> iterator = listeners.iterator(); while (iterator.hasNext()) { sendEventToListener(iterator.next(), coEvent); } } } private void sendEventToListener(ConnectionEventListener listener, ConnectionEvent coEvent) { if (coEvent.getId() == ConnectionEvent.CONNECTION_CLOSED) { listener.connectionClosed(coEvent); } if (coEvent.getId() == ConnectionEvent.LOCAL_TRANSACTION_COMMITTED) { listener.localTransactionCommitted(coEvent); } if (coEvent.getId() == ConnectionEvent.LOCAL_TRANSACTION_ROLLEDBACK) { listener.localTransactionRolledback(coEvent); } if (coEvent.getId() == ConnectionEvent.LOCAL_TRANSACTION_STARTED) { listener.localTransactionStarted(coEvent); } if (coEvent.getId() == ConnectionEvent.CONNECTION_ERROR_OCCURRED) { listener.connectionErrorOccurred(coEvent); } } private String getUserName() { if (associatedHandle != null) { Subject subject = handles.get(associatedHandle); if (subject != null) { return subject.toString(); } } return null; } private Object createConnectionHandle(final CXFConnectionSpec spec) { Class<?> interfaces[] = {CXFConnection.class, BindingProvider.class, spec.getServiceClass()}; return Proxy.newProxyInstance(spec.getServiceClass().getClassLoader(), interfaces, new ConnectionInvocationHandler( createClientProxy(spec), spec)); } private synchronized Object createClientProxy(final CXFConnectionSpec spec) { if (clientProxy == null) { validateConnectionSpec(spec); ClientProxyFactoryBean factory = null; if (EndpointUtils.hasWebServiceAnnotation(spec.getServiceClass())) { factory = new JaxWsProxyFactoryBean(); } else { factory = new ClientProxyFactoryBean(); } factory.setBus(getBus(spec.getBusConfigURL())); factory.setServiceClass(spec.getServiceClass()); factory.getServiceFactory().setEndpointName(spec.getEndpointName()); factory.getServiceFactory().setServiceName(spec.getServiceName()); factory.getServiceFactory().setWsdlURL(spec.getWsdlURL()); if (spec.getAddress() != null) { factory.setAddress(spec.getAddress()); } configureObject(spec.getEndpointName().toString() + ".jaxws-client.proxyFactory", factory); clientProxy = factory.create(); } return clientProxy; } private void validateConnectionSpec(CXFConnectionSpec spec) { if (spec.getServiceClass() == null) { throw new IllegalArgumentException("no serviceClass in connection spec"); } if (spec.getEndpointName() == null) { throw new IllegalArgumentException("no endpointName in connection spec"); } if (spec.getServiceName() == null) { throw new IllegalArgumentException("no serviceName in connection spec"); } if (spec.getWsdlURL() == null) { throw new IllegalArgumentException("no wsdlURL in connection spec"); } } private void configureObject(String name, Object instance) { Configurer configurer = bus.getExtension(Configurer.class); if (null != configurer) { configurer.configureBean(name, instance); } } private synchronized Bus getBus(URL busConfigLocation) { if (bus == null) { if (busConfigLocation != null) { if (LOG.isLoggable(Level.FINE)) { LOG.fine("Create bus from location " + busConfigLocation); } bus = new SpringBusFactory().createBus(busConfigLocation); } else if (mcf.getBusConfigURL() != null) { if (LOG.isLoggable(Level.FINE)) { LOG.fine("Create bus from URL " + mcf.getBusConfigURL()); } URL url = null; try { url = new URL(mcf.getBusConfigURL()); } catch (MalformedURLException e) { LOG.warning("Malformed URL " + mcf.getBusConfigURL()); } if (url != null) { bus = new SpringBusFactory().createBus(url); } } if (bus == null) { if (LOG.isLoggable(Level.FINE)) { LOG.fine("Create default bus"); } bus = BusFactory.getDefaultBus(); } } return bus; } private class ConnectionInvocationHandler implements InvocationHandler { private Object target; private CXFConnectionSpec spec; ConnectionInvocationHandler(Object target, CXFConnectionSpec spec) { this.target = target; this.spec = spec; } public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (LOG.isLoggable(Level.FINEST)) { LOG.finest("invoke connection spec:" + spec + " method=" + method); } if ("hashCode".equals(method.getName())) { return method.invoke(Proxy.getInvocationHandler(proxy), args); } if ("equals".equals(method.getName())) { // These are proxies. We don't really care if their targets are equal. // We do care if these are the same proxy instances that we created. // Therefore, if their proxy and invocation handler are consistent, // we believe they are equal. boolean result = false; try { result = proxy == args[0] && this == Proxy.getInvocationHandler(args[0]); } catch (Exception e) { // ignore and assume not equal } return result; } if ("toString".equals(method.getName())) { return "ManagedConnection: " + spec; } if (!handles.containsKey(proxy)) { throw new IllegalArgumentException("Stale connection"); } if ("getService".equals(method.getName())) { return handleGetServiceMethod(proxy, method, args); } else if ("close".equals(method.getName())) { return handleCloseMethod(proxy, method, args); } else { throw new IllegalArgumentException("Unhandled method " + method); } } private Object handleGetServiceMethod(Object proxy, Method method, Object[] args) { if (!spec.getServiceClass().equals(args[0])) { throw new IllegalArgumentException("serviceClass " + args[0] + " does not match " + spec.getServiceClass()); } return target; } private Object handleCloseMethod(Object proxy, Method method, Object[] args) { handles.remove(proxy); associatedHandle = null; ConnectionEvent event = new ConnectionEvent(ManagedConnectionImpl.this, ConnectionEvent.CONNECTION_CLOSED); event.setConnectionHandle(proxy); sendEvent(event); return null; } } }