/*
 * Copyright 2002-2005 the original author or authors.
 *
 * 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 za.net.kay;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.target.AbstractPrototypeBasedTargetSource;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.remoting.RemoteAccessException;
import org.springframework.remoting.jaxrpc.JaxRpcPortClientInterceptor;

/**
 * A target source to be used in conjunction with Spring's JaxRpcPortClientInterceptor to
 * provide "reconnect on failure" functionality which the standard JaxRpcPortFactoryBean
 * does not.
 * <p/>
 * Use this target source to inject your components with a proxy to the actual JAX-RPC port.
 * If any exceptions are thrown which are part of the Spring RemoteAccessException hierarchy,
 * a reconnect will be forced on the following call.
 * <p/>
 * You need to define a JaxRpcPortClientInterceptor <strong>prototype</strong> bean in your
 * bean factory, and inject this class with its bean name. The port will be instantiated
 * lazily as needed, and the reference reused until a reconnect is deemed necessary.
 * <p/>
 * <strong>Note:</strong> it is up to you to provide the actual handling of the
 * RemoteAccessException and retry the call if you need to.
 * <p/>
 * Sample configuration:
 * <xmp>
 * <beans>
 *
 *      <bean id="webservicePort" class="org.springframework.remoting.jaxrpc.JaxRpcPortClientInterceptor" singleton="false">
 *          <property name="serviceInterface" value="com.example.service.IService"/>
 *          <property name="namespaceUri" value="..."/>
 *          <property name="portName" value="..."/>
 *          <property name="wsdlDocumentUrl" value="..."/>
 *      </bean>
 *
 *      <bean id="reliableWebservice" class="org.springframework.aop.framework.ProxyFactoryBean">
 *          <property name="targetSource">
 *              <bean class="za.net.kay.JaxRpcPortTargetSource">
 *                  <property name="targetBeanName" value="webservicePort"/>
 *              </bean>
 *          </property>
 *      </bean>
 *
 *      <bean id="myBusinessComponent" class="com.example.Component">
 *          <property name="bizService" ref="reliableWebservice"/>
 *      </bean>
 *
 * </beans>
 * </xmp>
 *
 * @author <a href="mailto:pcholakov@gmail.com">Pavel Tcholakov</a>
 * @see JaxRpcPortClientInterceptor
 * @see RemoteAccessException
 */
public class JaxRpcPortTargetSource extends AbstractPrototypeBasedTargetSource implements MethodInterceptor, InitializingBean {
    private volatile Object target;

    /**
     * Will verify that a target bean name is set, and that the target is indeed a valid JaxRpcPortClientInterceptor.
     *
     * @throws Exception if misconfiguration is detected
     */
    public void afterPropertiesSet() throws Exception {
        String targetBeanName = getTargetBeanName();
        BeanFactory beanFactory = getBeanFactory();

        if (targetBeanName == null) {
            throw new IllegalArgumentException("The targetBeanName property must be set.");
        }

        if (!beanFactory.containsBean(targetBeanName)) {
            throw new IllegalArgumentException("No bean defined with name: '" + targetBeanName + "'.");
        }

        if (beanFactory.isSingleton(targetBeanName)) {
            throw new IllegalArgumentException("The bean '" + targetBeanName + "' is not a prototype.");
        }

        if (!JaxRpcPortClientInterceptor.class.isAssignableFrom(beanFactory.getType(getTargetBeanName()))) {
            throw new IllegalArgumentException("The target bean must be of type org.springframework.remoting.jaxrpc.JaxRpcPortClientInterceptor");
        }
    }

    /**
     * Creates a new JAX-RPC port using the Bean Factory. The creation is synchronized to ensure
     *
     * @return a new JAX-RPC port interceptor
     * @throws Exception
     */
    public Object getTarget() throws Exception {
        synchronized (this) {
            if (target == null) {
                JaxRpcPortClientInterceptor port = (JaxRpcPortClientInterceptor) getBeanFactory().getBean(getTargetBeanName());
                ProxyFactory targetFactory = new ProxyFactory();
                targetFactory.addInterface(port.getPortInterface());
                targetFactory.addAdvice(this);
                targetFactory.addAdvice(port);
                target = targetFactory.getProxy();
            }
        }

        return target;
    }

    /**
     * If a RemoteAccessException is detected, it will be re-thrown and the reference to the
     * JAX-RPC port released.
     *
     * @param methodInvocation this method invocation
     * @return result of invocation
     * @throws Throwable if any exception is thrown
     */
    public Object invoke(MethodInvocation methodInvocation) throws Throwable {
        try {
            return methodInvocation.proceed();
        }
        catch (Throwable t) {
            if (t.getClass().isAssignableFrom(RemoteAccessException.class)) synchronized (this) {
                target = null;
            }
            throw t;
        }
    }
}
