/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software 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.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.ws.tools.config;

// $Id: ToolsSchemaConfigReader.java 8006 2008-08-04 13:08:09Z darran.lofthouse@jboss.com $

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import org.jboss.logging.Logger;
import org.jboss.ws.WSException;
import org.jboss.ws.tools.Configuration;
import org.jboss.ws.tools.Configuration.GlobalConfig;
import org.jboss.ws.tools.Configuration.JavaToWSDLConfig;
import org.jboss.ws.tools.Configuration.OperationConfig;
import org.jboss.ws.tools.Configuration.ParameterConfig;
import org.jboss.ws.tools.Configuration.WSDLToJavaConfig;
import org.jboss.xb.binding.JBossXBException;
import org.jboss.xb.binding.ObjectModelFactory;
import org.jboss.xb.binding.Unmarshaller;
import org.jboss.xb.binding.UnmarshallerFactory;
import org.jboss.xb.binding.UnmarshallingContext;
import org.xml.sax.Attributes;

/**
 *  Reads the XML configuration file passed to jbossws
 *  @author <mailto:Anil.Saldhana@jboss.org>Anil Saldhana
 *  @author Thomas.Diesler@jboss.org
 *  @since   11-Aug-2005
 */
public class ToolsSchemaConfigReader implements ObjectModelFactory
{
   // Tags
   private static final String PARAMETER_TAG = "parameter";
   private static final String WEBSERVICES_TAG = "webservices";
   private static final String MAPPING_TAG = "mapping";
   private static final String PACKAGE_NAMESPACE_TAG = "package-namespace";
   private static final String NAMESPACES_TAG = "namespaces";
   private static final String SERVICE_TAG = "service";
   private static final String GLOBAL_TAG = "global";
   private static final String JAVA_WSDL_TAG = "java-wsdl";
   private static final String WSDL_JAVA_TAG = "wsdl-java";
   private static final String OPERATION_TAG = "operation";

   // Attributes
   private static final String HEADER_ATTRIBUTE = "header";
   private static final String MODE_ATTRIBUTE = "mode";
   private static final String XML_NAME_ATTRIBUTE = "xml-name";
   private static final String TYPE_ATTRIBUTE = "type";
   private static final String NAMESPACE_ATTRIBUTE = "namespace";
   private static final String PACKAGE_ATTRIBUTE = "package";
   private static final String EJB_LINK_ATTRIBUTE = "ejb-link";
   private static final String SERVLET_LINK_ATTRIBUTE = "servlet-link";
   private static final String APPEND_ATTRIBUTE = "append";
   private static final String TYPE_NAMESPACE_ATTRIBUTE = "type-namespace";
   private static final String TARGET_NAMESPACE_ATTRIBUTE = "target-namespace";
   private static final String RETURN_XML_NAME_ATTRIBUTE = "return-xml-name";
   private static final String ONEWAY_ATTRIBUTE = "one-way";
   private static final String PARAMETER_STYLE_ATTRIBUTE = "parameter-style";
   private static final String SERIALIZABLE_TYPES_ATTRIBUTE = "serializable-types";
   private static final String STYLE_ATTRIBUTE = "style";
   private static final String ENDPOINT_ATTRIBUTE = "endpoint";
   private static final String NAME_ATTRIBUTE = "name";
   private static final String LOCATION_ATTRIBUTE = "location";
   private static final String FILE_ATTRIBUTE = "file";

   // provide logging
   private static final Logger log = Logger.getLogger(ToolsSchemaConfigReader.class);

   public ToolsSchemaConfigReader()
   {
   }

   public Configuration readConfig(String configLocation) throws IOException
   {
      log.trace("Inside readConfig: " + configLocation);
      if (configLocation == null)
         throw new IllegalArgumentException("Config URL passed is null");

      URL configURL = null;
      try
      {
         configURL = new URL(configLocation);
      }
      catch (MalformedURLException e)
      {
         // ignore
      }

      if (configURL == null)
      {
         File configFile = new File(configLocation);
         if (configFile.exists())
            configURL = configFile.toURL();
      }

      if (configURL == null)
      {
         ClassLoader ctxLoader = Thread.currentThread().getContextClassLoader();
         configURL = ctxLoader.getResource(configLocation);
      }

      if (configURL == null)
         throw new IllegalArgumentException("Cannot load config from: " + configLocation);

      Configuration config = new Configuration();
      InputStream is = configURL.openStream();
      try
      {
         Unmarshaller unmarshaller = UnmarshallerFactory.newInstance().newUnmarshaller();
         unmarshaller.setNamespaceAware(true);
         unmarshaller.setSchemaValidation(true);
         unmarshaller.setValidation(true);
         unmarshaller.unmarshal(is, this, config);
      }
      catch (JBossXBException ex)
      {
         IOException ioex = new IOException("Cannot parse config: " + ex.getMessage());
         ioex.initCause(ex);
         throw ioex;
      }
      finally
      {
         is.close();
      }

      if (config.getJavaToWSDLConfig(false) == null && config.getWSDLToJavaConfig(false) == null)
         throw new WSException("Invalid configuration file, either " + JAVA_WSDL_TAG + ", or " + WSDL_JAVA_TAG + " must be present");

      log.trace("Exit readConfig");
      return config;
   }

   public Object newRoot(Object root, UnmarshallingContext ctx, String namespaceURI, String localName, Attributes attrs)
   {
      if (root instanceof Configuration)
         return root;
      else return null;
   }

   /**
    * This method is called by the object model factory and returns the root of the object graph.
    */
   public Object newChild(Configuration config, UnmarshallingContext navigator, String namespaceURI, String localName, Attributes attrs)
   {
      log.trace("Inside newChild:localName=" + localName);
      if (JAVA_WSDL_TAG.equals(localName))
      {
         JavaToWSDLConfig j2wsdlc = config.getJavaToWSDLConfig(true);
         return j2wsdlc;
      }
      else if (WSDL_JAVA_TAG.equals(localName))
      {
         WSDLToJavaConfig wsdl2jc = config.getWSDLToJavaConfig(true);
         wsdl2jc.wsdlLocation = attrs.getValue(LOCATION_ATTRIBUTE);
         String paramStyle = attrs.getValue(PARAMETER_STYLE_ATTRIBUTE);
         if (paramStyle != null)
            wsdl2jc.parameterStyle = paramStyle;
         String serializableTypes = attrs.getValue(SERIALIZABLE_TYPES_ATTRIBUTE);
         if ("true".equals(serializableTypes) || "1".equals(serializableTypes))
            wsdl2jc.serializableTypes = true;         
            
         return wsdl2jc;
      }
      else if (GLOBAL_TAG.equals(localName))
      {
         GlobalConfig globalc = config.getGlobalConfig(true);
         return globalc;
      }
      return config;
   }

   /**
    * This method is called by the object model factory and returns the root of the object graph.
    */
   public Object newChild(JavaToWSDLConfig j2wsdlc, UnmarshallingContext navigator, String namespaceURI, String localName, Attributes attrs)
   {
      String errorStr = "Problem parsing tag:" + JAVA_WSDL_TAG;

      if (SERVICE_TAG.equals(localName))
      {
         j2wsdlc.serviceName = attrs.getValue(NAME_ATTRIBUTE);
         j2wsdlc.endpointName = attrs.getValue(ENDPOINT_ATTRIBUTE);
         j2wsdlc.wsdlStyle = getOptionalAttribute(attrs, STYLE_ATTRIBUTE, "document");
         j2wsdlc.parameterStyle = getOptionalAttribute(attrs, PARAMETER_STYLE_ATTRIBUTE, "wrapped");
      }
      else if (OPERATION_TAG.equals(localName))
      {
         OperationConfig operation = j2wsdlc.createOperationConfig();
         operation.name = attrs.getValue(NAME_ATTRIBUTE);
         String oneWay = attrs.getValue(ONEWAY_ATTRIBUTE);
         operation.isOneWay = "true".equals(oneWay) || "1".equals(oneWay);
         String returnXmlName = attrs.getValue(RETURN_XML_NAME_ATTRIBUTE);
         if (returnXmlName != null)
            operation.returnXmlName = navigator.resolveQName(returnXmlName);

         return operation;
      }
      else if (NAMESPACES_TAG.equals(localName))
      {
         errorStr += NAMESPACES_TAG;
         j2wsdlc.targetNamespace = getNamespace(navigator, TARGET_NAMESPACE_ATTRIBUTE, errorStr, attrs);
         j2wsdlc.typeNamespace = getNamespace(navigator, TYPE_NAMESPACE_ATTRIBUTE, errorStr, attrs);
         if (j2wsdlc.typeNamespace == null)
            j2wsdlc.typeNamespace = j2wsdlc.targetNamespace;
      }
      else if (MAPPING_TAG.equals(localName))
      {
         j2wsdlc.mappingFileNeeded = true;
         j2wsdlc.mappingFileName = getOptionalAttribute(attrs, FILE_ATTRIBUTE, "jaxrpc-mapping.xml");
      }
      else if (WEBSERVICES_TAG.equals(localName))
      {
         j2wsdlc.wsxmlFileNeeded = true;
         j2wsdlc.servletLink = getOptionalAttribute(attrs, SERVLET_LINK_ATTRIBUTE, null);
         j2wsdlc.ejbLink = getOptionalAttribute(attrs, EJB_LINK_ATTRIBUTE, null);
         if (j2wsdlc.ejbLink == null && j2wsdlc.servletLink == null)
            throw new WSException("Either servletLink or ejbLink should be specified");
         String wsxmlFileAppend = attrs.getValue(APPEND_ATTRIBUTE);
         j2wsdlc.wsxmlFileAppend = "true".equals(wsxmlFileAppend) || "1".equals(wsxmlFileAppend);
      }
      return j2wsdlc;
   }

   /**
    * This method is called by the object model factory and returns the root of the object graph.
    */
   public Object newChild(GlobalConfig globalc, UnmarshallingContext navigator, String namespaceURI, String localName, Attributes attrs)
   {
      if (PACKAGE_NAMESPACE_TAG.equals(localName))
      {
         String pkgname = attrs.getValue(PACKAGE_ATTRIBUTE);
         String ns = attrs.getValue(NAMESPACE_ATTRIBUTE);
         globalc.packageNamespaceMap.put(ns, pkgname);
      }
      return globalc;
   }

   /**
    * This method is called by the object model factory and returns the root of the object graph.
    */
   public Object newChild(WSDLToJavaConfig wsdl2jc, UnmarshallingContext navigator, String namespaceURI, String localName, Attributes attrs)
   {
      if (MAPPING_TAG.equals(localName))
      {
         wsdl2jc.mappingFileNeeded = true;
         wsdl2jc.mappingFileName = getOptionalAttribute(attrs, FILE_ATTRIBUTE, "jaxrpc-mapping.xml");
      }
      else if (WEBSERVICES_TAG.equals(localName))
      {
         wsdl2jc.wsxmlFileNeeded = true;
         wsdl2jc.servletLink = getOptionalAttribute(attrs, SERVLET_LINK_ATTRIBUTE, null);
         wsdl2jc.ejbLink = getOptionalAttribute(attrs, EJB_LINK_ATTRIBUTE, null);
         if (wsdl2jc.ejbLink == null && wsdl2jc.servletLink == null)
            throw new WSException("Either servletLink or ejbLink should be specified");
      }
      return wsdl2jc;
   }

   /**
    * This method is called by the object model factory and returns the root of the object graph.
    */
   public Object newChild(OperationConfig op, UnmarshallingContext navigator, String namespaceURI, String localName, Attributes attrs)
   {
      if (PARAMETER_TAG.equals(localName))
      {
         ParameterConfig parameter = op.createParameterConfig();
         parameter.javaType = attrs.getValue(TYPE_ATTRIBUTE);
         String xmlName = attrs.getValue(XML_NAME_ATTRIBUTE);
         if (xmlName != null)
            parameter.xmlName = navigator.resolveQName(xmlName);
         parameter.mode = attrs.getValue(MODE_ATTRIBUTE);
         String header = attrs.getValue(HEADER_ATTRIBUTE);
         if (header != null)
            parameter.header = "true".equals(header) || "1".equals(header);

         return parameter;
      }

      return null;
   }

   /**
    */
   public Object addChild(Configuration config, WSDLToJavaConfig wsdl2jc, UnmarshallingContext navigator, String namespaceURI, String localName)
   {
      config.setWSDLToJavaConfig(wsdl2jc);
      return config;
   }

   /**
    */
   public Object addChild(Configuration config, GlobalConfig global, UnmarshallingContext navigator, String namespaceURI, String localName)
   {
      config.setGlobalConfig(global);
      return config;
   }

   /**
    */
   public Object addChild(Configuration config, JavaToWSDLConfig j2wc, UnmarshallingContext navigator, String namespaceURI, String localName)
   {
      config.setJavaToWSDLConfig(j2wc);
      return config;
   }

   /**
    */
   public Object addChild(JavaToWSDLConfig j2wc, OperationConfig opc, UnmarshallingContext navigator, String namespaceURI, String localName)
   {
      List<OperationConfig> list = j2wc.operations.get(opc.name);
      if (list == null)
      {
         list = new ArrayList<OperationConfig>();
         list.add(opc);
         j2wc.operations.put(opc.name, list);
      }
      else
      {
         list.add(opc);
      }

      return j2wc;
   }

   public Object addChild(OperationConfig opc, ParameterConfig pc, UnmarshallingContext navigator, String namespaceURI, String localName)
   {
      opc.params.add(pc);
      return opc;
   }

   public Object completeRoot(Object root, UnmarshallingContext ctx, String namespaceURI, String localName)
   {
      return root;
   }

   //PRIVATE METHODS
   private String getOptionalAttribute(Attributes attrs, String attribName, String defaultValue)
   {
      String value = attrs.getValue(attribName);
      if (value == null)
         return defaultValue;

      return value;
   }

   private String getNamespace(UnmarshallingContext navigator, String attribName, String errorStr, Attributes attrs)
   {
      try
      {
         return attrs.getValue(attribName);
      }
      catch (RuntimeException e)
      {
         throw new WSException(errorStr + " attribute=" + attribName);
      }
   }
}
