/*
 * 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.uima.adapter.soap;

import java.util.HashMap;
import java.util.Map;

import org.apache.axis.AxisFault;
import org.apache.axis.Handler;
import org.apache.axis.MessageContext;
import org.apache.uima.UIMAFramework;
import org.apache.uima.analysis_engine.AnalysisEngine;
import org.apache.uima.analysis_engine.service.impl.AnalysisEngineService_impl;
import org.apache.uima.resource.ResourceSpecifier;
import org.apache.uima.resource.service.impl.ResourceService_impl;
import org.apache.uima.util.Level;
import org.apache.uima.util.Logger;
import org.apache.uima.util.XMLInputSource;

/**
 * Utility class for deploying Resources as Axis (SOAP) services. This class mantains a map between
 * Axis service names and the {@link ResourceService_impl} classes that provide the implementation
 * for those service names. This map is maintained as static data on this class so that it persists
 * between invocations of the Axis services.
 * <P>
 * SOAP service implementation classes call the static {@link #getServiceImpl(Class)} method from
 * their constructors. The service's name and configuration parameters will be read from the Axis
 * MessageContext. If a {@link ResourceService_impl} instance has already been registered under this
 * service's name, that instance will be returned. Otherwise, a new
 * <code>ResourceService_impl</code> will be created.
 */
public class AxisResourceServiceManager {

  /**
   * The name of the deployment parameter whose value is the path to an XML resource specifier. This
   * resource specifier is used to constuct Resource instances that process the requests received by
   * this service. A value for this option must be speciifed in the deployment descriptor for this
   * service.
   */
  public static final String PARAM_RESOURCE_SPECIFIER_PATH = "resourceSpecifierPath";

  /**
   * The name of the deployment parameter whose value is the number of instances of the Resource
   * (specified by {@link #PARAM_RESOURCE_SPECIFIER_PATH}) to be created. The Resources are kept in
   * a pool and used to service requests. A value for this option must be speciifed in the
   * deployment descriptor for this service.
   */
  public static final String PARAM_NUM_INSTANCES = "numInstances";

  /**
   * The name of the deployment parameter whose value is a boolean indicating whether to write log
   * messages during each service invocation. This currently applies only to Analysis Engine
   * services.
   */
  public static final String PARAM_ENABLE_LOGGING = "enableLogging";

  /**
   * Map from service names to ResourceService_impl objects.
   */
  private static Map mResourceServiceImplMap = new HashMap();

  /**
   * Gets a {@link ResourceService_impl} class to be used to process an request.This method
   * retrieves the service name and configuration parameters from the Axis MessageContext. If a
   * {@link ResourceService_impl} object already exists for that service name, that object will be
   * returned. Otherwise, a new <code>ResourceService_impl</code> object will be created from the
   * information in the MessageContext.
   * 
   * @param aServiceImplClass
   *          the class that will be instantiated when a new <code>ResourceService_impl</code> is
   *          to be created. This must be a subclass of ResourceService_impl.
   * @throws AxisFault
   *           if the configuration information could not be read
   */
  public static ResourceService_impl getServiceImpl(Class aServiceImplClass) throws AxisFault {
    try {
      MessageContext ctx = MessageContext.getCurrentContext();
      if (ctx == null) {
        throw new Exception("MessageContext = NULL");
      }

      Handler self = ctx.getService();
      if (self == null) {
        throw new Exception("Handler = NULL");
      }

      // Get service name
      String serviceName = self.getName();

      // see if we have a ResourceService_impl registered for that name
      ResourceService_impl serviceImpl = (ResourceService_impl) mResourceServiceImplMap
              .get(serviceName);
      if (serviceImpl != null) {
        return serviceImpl;
      }

      // No service impl registered for this service name, attempt to
      // create a new one

      // Get the Resource Specifier Path
      String resourceSpecifierPath = (String) self.getOption(PARAM_RESOURCE_SPECIFIER_PATH);
      if (resourceSpecifierPath == null || resourceSpecifierPath.trim().length() == 0) {
        throw new Exception("Invalid Configuration - " + PARAM_RESOURCE_SPECIFIER_PATH
                + " not Defined.  Check your deployment descriptor file (WSDD)");
      }
      // parse ResourceSpecifier
      ResourceSpecifier resourceSpecifier = UIMAFramework.getXMLParser().parseResourceSpecifier(
              new XMLInputSource(resourceSpecifierPath));

      // Get the number of instances to create
      String numInstancesStr = (String) self.getOption(PARAM_NUM_INSTANCES);
      int numInstances;
      try {
        numInstances = Integer.parseInt(numInstancesStr);
      } catch (NumberFormatException e) {
        throw new Exception("Invalid Configuration - " + PARAM_NUM_INSTANCES
                + " not valid.  Check your deployment descriptor file (WSDD)");
      }

      // Get whether to enable logging
      String enableLogStr = (String) self.getOption(PARAM_ENABLE_LOGGING);
      boolean enableLog = "true".equalsIgnoreCase(enableLogStr);

      // create and initialize the service implementation
      serviceImpl = (ResourceService_impl) aServiceImplClass.newInstance();
      HashMap initParams = new HashMap();
      initParams.put(AnalysisEngine.PARAM_NUM_SIMULTANEOUS_REQUESTS, new Integer(numInstances));
      serviceImpl.initialize(resourceSpecifier, initParams);

      // disable logging for Analysis Engines if deployer so indicated
      if (!enableLog && serviceImpl instanceof AnalysisEngineService_impl) {
        Logger nullLogger = UIMAFramework.newLogger();
        nullLogger.setOutputStream(null);

        ((AnalysisEngineService_impl) serviceImpl).getAnalysisEngine().setLogger(nullLogger);
      }

      mResourceServiceImplMap.put(serviceName, serviceImpl);
      return serviceImpl;
    } catch (Exception e) {
      UIMAFramework.getLogger().log(Level.SEVERE, e.getMessage(), e);
      throw AxisFault.makeFault(e);
    }
  }
}
