/*
 * $Id: XinsClientInterceptor.java,v 1.6 2010/10/25 20:36:51 agoubard Exp $
 *
 * See the COPYRIGHT file for redistribution and use restrictions.
 */
/*
 * Copyright 2002-2006 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 org.xins.common.spring;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.net.MalformedURLException;
import java.util.Map;
import java.util.Properties;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;

import org.springframework.beans.factory.BeanCreationException;
import org.springframework.remoting.RemoteAccessException;
import org.springframework.remoting.RemoteConnectFailureException;
import org.springframework.remoting.RemoteLookupFailureException;
import org.springframework.remoting.RemoteProxyFailureException;
import org.springframework.remoting.support.UrlBasedRemoteAccessor;

import org.xins.client.AbstractCAPI;
import org.xins.client.XINSCallException;
import org.xins.client.XINSServiceCaller;
import org.xins.common.collections.MapStringUtils;

import org.xins.common.service.Descriptor;
import org.xins.common.service.DescriptorBuilder;
import org.xins.common.service.GenericCallException;
import org.xins.common.service.TargetDescriptor;

/**
 * Interceptor for accessing a specific XINS API.
 * This class requires the Spring library.
 *
 * @version $Revision: 1.6 $ $Date: 2010/10/25 20:36:51 $
 * @author <a href="mailto:anthony.goubard@japplis.com">Anthony Goubard</a>
 *
 * @since XINS 2.0
 */
public class XinsClientInterceptor extends UrlBasedRemoteAccessor implements MethodInterceptor {

   /**
    * Properties containing the location of the API.
    */
   private Properties descriptorProperties;

   /**
    * time-out in milliseconds for a call to the API when a single target is specified.
    */
   private int timeout = -1;

   /**
    * The CAPI used to call the web service.
    */
   protected AbstractCAPI capi;

   /**
    * The name of the API to call.
    */
   private String serviceName;

   public void afterPropertiesSet() {
      super.afterPropertiesSet();
      prepare();
   }

   public void prepare() {
      try {
         capi = createCapi();
      } catch (MalformedURLException murlex) {
         throw new RemoteLookupFailureException("Service URL [" + getServiceUrl() + "] is invalid", murlex);
      } catch (Exception ex) {
         throw new BeanCreationException(ex.getMessage(), ex);
      }
   }


   /**
    * Sets the location of the API to call.
    * If this method is call, it will invalidate the previous call to
    * {@link #setServiceUrl} and {@link #setTimeout} methods.
    *
    * @param descriptorProperties
    *    the different destination as explained in
    *    <a href="http://xins.sourceforge.net/javadoc/1.5.2/org/xins/common/service/DescriptorBuilder.html">DescriptorBuilder</a>.
    */
   public void setServiceProperties(Properties descriptorProperties) {
      setServiceUrl(null);
      this.descriptorProperties = descriptorProperties;
   }

   /**
    * Sets the time-out for the call of the API.
    * This method requires that you also call {@link #setServiceUrl}.
    *
    * @param timeout
    *    the time-out for the call in milliseconds. This parameter will be used
    *    for the connection time-out, socket time-out and total time-out.
    */
   public void setTimeout(int timeout) {
      this.timeout = timeout;
   }

   /**
    * Sets the name of the API to call.
    * The name is used to detect the capi.&lt;api name&gt; in the service properties set.
    *
    * @param serviceName
    *    the name of the API to call.
    */
   public void setServiceName(String serviceName) {
      this.serviceName = serviceName;
   }

   /**
    * Gets the name of the API to call.
    *
    * @return
    *    the name of the API to call.
    */
   public String getServiceName() {
      return serviceName;
   }

   /**
    * Creates the {@link Descriptor} containing the location of the API.
    *
    * @return
    *    the specified descriptor or <code>null</code> no descriptorProperties or serviceURL is set.
    *
    * @throws Exception
    *    if the descriptorProperties or serviceURL is incorrect.
    */
   public Descriptor createDescriptor() throws Exception {
      if (descriptorProperties != null) {
         Map<String, String> reader = MapStringUtils.fromProperties(descriptorProperties);
         return DescriptorBuilder.build(reader, "capi." + getServiceName());
      } else if (getServiceUrl() != null) {
         if (timeout > -1) {
            return new TargetDescriptor(getServiceUrl(), timeout);
         } else {
            return new TargetDescriptor(getServiceUrl());
         }
      }
      return null;
   }

   /**
    * Creates the CAPI to call the API.
    *
    * @return
    *    the created CAPI.
    *
    * @throws Exception
    *    if the CAPI class is not found or the descriptor cannot be created.
    */
   public AbstractCAPI createCapi() throws Exception {
      Descriptor descriptor = createDescriptor();
      // Creates the CAPI (Client API) based on the class provided to the service interface.
      Constructor constCAPI = getServiceInterface().getConstructor(new Class[] {Descriptor.class});
      AbstractCAPI capi = (AbstractCAPI) constCAPI.newInstance(new Object[]{descriptor});
      return capi;
   }

   /**
    * Creates the XINSServiceCaller to call the API.
    *
    * @return
    *    the service caller to call the API.
    *
    * @throws Exception
    *    if the descriptor cannot be created or is incorrect.
    */
   public XINSServiceCaller createXinsServiceCaller() throws Exception {
      XINSServiceCaller caller = new XINSServiceCaller(createDescriptor());
      return caller;
   }

   public Object invoke(MethodInvocation invocation) throws Throwable {
      if (this.capi == null) {
         throw new IllegalStateException("XinsClientInterceptor is not properly initialized - " +
               "invoke 'prepare' before attempting any operations");
      }

      try {
         return invocation.getMethod().invoke(this.capi, invocation.getArguments());
      } catch (InvocationTargetException ex) {
         if (ex.getTargetException() instanceof XINSCallException) {
            XINSCallException callEx = (XINSCallException) ex.getTargetException();
            throw convertXinsAccessException(callEx);
         }
         throw ex.getTargetException();
      } catch (Throwable ex) {
         throw new RemoteProxyFailureException(
               "Failed to invoke XINS API for remote service [" + getServiceUrl() + "]", ex);
      }
   }

   /**
    * Convert the given XINS exception to an appropriate Spring RemoteAccessException.
    *
    * @param ex
    *    the exception to convert.
    *
    * @return
    *    the RemoteAccessException to throw.
    */
   protected RemoteAccessException convertXinsAccessException(Throwable ex) {
      if (ex instanceof GenericCallException) {
         throw new RemoteConnectFailureException(
               "Cannot connect to Burlap remote service at [" + getServiceUrl() + "]", ex);
      } else {
         throw new RemoteAccessException(
               "Cannot access Burlap remote service at [" + getServiceUrl() + "]", ex);
      }
   }
}