/*
 *
 * The Seasar Software License, Version 1.1
 *
 * Copyright (c) 2003-2004 The Seasar Project. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 *
 * 1. Redistributions of source code must retain the above
 *    copyright notice, this list of conditions and the following
 *    disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above
 *    copyright notice, this list of conditions and the following
 *    disclaimer in the documentation and/or other materials provided
 *    with the distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgement:
 *    "This product includes software developed by the
 *    Seasar Project (http://www.seasar.org/)."
 *    Alternately, this acknowledgement may appear in the software
 *    itself, if and wherever such third-party acknowledgements
 *    normally appear.
 *
 * 4. Neither the name "The Seasar Project" nor the names of its
 *    contributors may be used to endorse or promote products derived
 *    from this software without specific prior written permission of
 *    the Seasar Project.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE SEASAR PROJECT
 * OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL,SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY,OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
package org.seasar.remoting.axis.deployer;

import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import javax.servlet.ServletContext;

import org.apache.axis.AxisEngine;
import org.apache.axis.WSDDEngineConfiguration;
import org.apache.axis.client.Service;
import org.apache.axis.deployment.wsdd.WSDDDeployment;
import org.seasar.framework.container.ComponentDef;
import org.seasar.framework.container.MetaDef;
import org.seasar.framework.container.MetaDefAware;
import org.seasar.framework.container.S2Container;
import org.seasar.framework.message.MessageFormatter;
import org.seasar.remoting.axis.DeployFailedException;
import org.seasar.remoting.axis.S2AxisConstants;
import org.seasar.remoting.common.deployer.Deployer;

/**
 * diconファイル中に記述されたコンポーネントをAxisにデプロイします。
 * 
 * @author koichik
 */
public class AxisDeployer implements Deployer {
    //class fields
    /**
     * <code>&lt;meta&gt;</code> 要素の <code>name</code>
     * 属性に指定される名前を取得するための正規表現です。 <br>
     * S2Axis-V1.0.0-RC2以降は接頭辞 <code>axis-</code> の後にローカル名が続きます。 <br>
     * S2Axis-V1.0.0-RC1以前との互換性のため、接頭辞 <code>s2-axis:</code> も使えるようにしています。
     */
    protected static final Pattern META_NAME_PATTERN = Pattern.compile("(?:s2-axis:|axis-)(.+)");

    //instance fields
    protected S2Container container;
    protected ServletContext servletContext;

    protected ItemDeployer serviceDeployer = new ServiceDeployer(this);
    protected ItemDeployer handlerDeployer = new HandlerDeployer(this);
    protected ItemDeployer wsddDeployer = new WSDDDeployer(this);

    /**
     * S2コンテナを設定します。
     * 
     * @param container
     *            S2コンテナ
     */
    public void setContainer(final S2Container container) {
        this.container = container;
    }

    /**
     * サーブレットコンテキストを設定します。
     * 
     * @param servletContext
     *            サーブレットコンテキスト
     */
    public void setServletContext(final ServletContext servletContext) {
        this.servletContext = servletContext;
    }

    /**
     * コンテナに登録されているサービスやハンドラをデプロイします。
     */
    public void deploy() {
        forEach(container.getRoot());
    }

    /**
     * コンテナの階層をたどって全てのコンテナとコンポーネント定義を走査します。 <br>
     * 走査する順序は次の通りです。
     * <ol>
     * <li>コンテナ自身</li>
     * <li>子のコンポーネント定義</li>
     * <li>子のコンテナを再起的に</li>
     * </ol>
     * 
     * @param container
     *            起点となるコンテナ
     */
    protected void forEach(final S2Container container) {
        process(container);

        final int componentDefSize = container.getComponentDefSize();
        for (int i = 0; i < componentDefSize; ++i) {
            process(container.getComponentDef(i));
        }

        final int childContainerSize = container.getChildSize();
        for (int i = 0; i < childContainerSize; ++i) {
            forEach(container.getChild(i));
        }
    }

    /**
     * S2コンテナにS2Axisのメタデータ <code>&lt;meta name="axis-deploy"&gt;</code>
     * が指定されていれば、そのWSDDをAxisにデプロイします。
     * 
     * @param container
     *            S2コンテナ
     */
    protected void process(final S2Container container) {
        final MetaDef[] metaDefs = getMetaDefs(container, S2AxisConstants.META_DEPLOY);
        for (int i = 0; metaDefs != null && i < metaDefs.length; ++i) {
            wsddDeployer.deploy(null, metaDefs[i]);
        }
    }

    /**
     * コンポーネント定義にS2Axisのメタデータ <code>&lt;meta name="axis-service"&gt;</code>
     * または <code>&lt;meta name="axis-handler"&gt;</code>
     * が指定されていれば、そのコンポーネントをサービスまたはハンドラとしてAxisにデプロイします。
     * 
     * @param componentDef
     *            コンポーネント定義
     */
    protected void process(final ComponentDef componentDef) {
        final MetaDef serviceMetaDef = getMetaDef(componentDef, S2AxisConstants.META_SERVICE);
        if (serviceMetaDef != null) {
            serviceDeployer.deploy(componentDef, serviceMetaDef);
        }

        final MetaDef handlerMetaDef = getMetaDef(componentDef, S2AxisConstants.META_HANDLER);
        if (handlerMetaDef != null) {
            handlerDeployer.deploy(componentDef, handlerMetaDef);
        }
    }

    /**
     * WSDDデプロイメントを返します。
     * 
     * @param container
     *            コンテナ
     * @return WSDDデプロイメント
     */
    protected WSDDDeployment getDeployment(final S2Container container) {
        return ((WSDDEngineConfiguration) getEngine(container).getConfig()).getDeployment();
    }

    /**
     * Axisエンジンを返します。 <br>
     * Axisエンジンは、コンテナに名前 <code>axis-engine</code> を持つ
     * <code>&lt;meta&gt;</code> 要素が指定されていれば、その内容文字列から次のように決定されます。
     * <dl>
     * <dt>未定義の場合</dt>
     * <dd><code>"default"</code> が指定されたものとしてAxisエンジンを決定します。</dd>
     * <dt><code>"default"</code></dt>
     * <dd>コンテナにサーブレットコンテキストが設定されていれば <code>"default-server"</code> 、そうでなければ
     * <code>"default-client"</code> が指定されたものとしてAxisエンジンを決定します。</dd>
     * <dt><code>"default-client"</code></dt>
     * <dd>コンテナから <code>javax.xml.rpc.Service</code>
     * を実装したコンポーネントを取得し、そのエンジンを使用します。</dd>
     * <dt><code>"default-server"</code></dt>
     * <dd>サーブレットコンテキストに設定されているAxisエンジンを使用します。 <br>
     * 最初に {@link S2AxisConstants#AXIS_SERVLET}と
     * {@link S2AxisConstants#ATTR_AXIS_ENGINE}を連結した文字列をキーとして
     * サーブレットコンテキストからAxisエンジンを取得します。 <br>
     * 見つからなかった場合は{S2AxisConstants#ATTR_AXIS_ENGINE}を
     * キーとしてサーブレットコンテキストから取得したAxisエンジンを取得します。 <br>
     * </dd>
     * <dt><code>"servlet:"</code> で始まる文字列</dt>
     * <dd><code>"servlet:"</code> の後ろの文字列をキーとしてサーブレットコンテキストから
     * 取得したAxisエンジンを使用します。
     * <dd>
     * <dt><code>"s2:"</code> で始まる文字列</dt>
     * <dd><code>"s2:"</code> の後ろの文字列をキーとしてS2コンテナから
     * 取得したコンポーネントをAxisエンジンを使用します。</dd>
     * <dt>その他</dt>
     * <dd>キーとしてS2コンテナから取得したコンポーネントをAxisエンジンとして使用します。</dd>
     * </dl>
     * 
     * @param container
     *            コンテナ
     * @return Axisエンジン
     */
    protected AxisEngine getEngine(final S2Container container) {
        String engineName = S2AxisConstants.ENGINE_DEFAULT;

        final MetaDef metadata = getMetaDef(container, S2AxisConstants.META_ENGINE);
        if (metadata != null) {
            engineName = (String) metadata.getValue();
        }

        if (S2AxisConstants.ENGINE_DEFAULT.equals(engineName)) {
            if (servletContext == null) {
                engineName = S2AxisConstants.ENGINE_DEFAULT_CLIENT;
            }
            else {
                engineName = S2AxisConstants.ENGINE_DEFAULT_SERVER;
            }
        }

        AxisEngine engine = null;
        if (S2AxisConstants.ENGINE_DEFAULT_CLIENT.equals(engineName)) {
            final Service service = (Service) container.getComponent(javax.xml.rpc.Service.class);
            engine = service.getEngine();
        }
        else if (S2AxisConstants.ENGINE_DEFAULT_SERVER.equals(engineName)) {
            engine = (AxisEngine) servletContext.getAttribute(S2AxisConstants.AXIS_SERVLET
                    + S2AxisConstants.ATTR_AXIS_ENGINE);
            if (engine == null) {
                engine = (AxisEngine) servletContext.getAttribute(S2AxisConstants.ATTR_AXIS_ENGINE);
            }
        }
        else if (engineName.startsWith(S2AxisConstants.ENGINE_FROM_SERVLET)) {
            final String servletName = engineName.substring(S2AxisConstants.ENGINE_FROM_SERVLET
                    .length());
            engine = (AxisEngine) servletContext.getAttribute(servletName
                    + S2AxisConstants.ATTR_AXIS_ENGINE);
        }
        else if (engineName.startsWith(S2AxisConstants.ENGINE_FROM_S2CONTAINER)) {
            final String componentName = engineName
                    .substring(S2AxisConstants.ENGINE_FROM_S2CONTAINER.length());
            engine = (AxisEngine) container.getComponent(componentName);
        }
        else {
            engine = (AxisEngine) container.getComponent(engineName);
        }

        if (engine == null) {
            throw new DeployFailedException(MessageFormatter.getSimpleMessage("EAXS0007", null));
        }
        return engine;
    }

    /**
     * <code>S2Container</code> または <code>ComponentDef</code> が名前
     * <code>"axis-<var>localName</var></code> の <code>&lt;meta&gt;</code>
     * 要素を持っていれば、その <code>MetaDef</code> を返します。 <br>
     * <code>S2Container</code> または <code>ComponentDef</code> に該当する
     * メタデータが複数定義されている場合は最初に見つかったメタデータを返します。
     * 
     * @param metaDefSupport
     *            <code>S2Container</code> または <code>ComponentDef</code>
     * @param localName
     *            接頭辞 <code>axis-</code> に続くメタデータの名前
     * @return 指定された名前を持つ <code>MetaDef</code> 。存在しない場合は <code>null</code>
     */
    protected MetaDef getMetaDef(final MetaDefAware metaDefSupport, final String localName) {
        for (int i = 0; i < metaDefSupport.getMetaDefSize(); ++i) {
            final MetaDef metaDef = metaDefSupport.getMetaDef(i);
            if (localName.equals(getLocalName(metaDef))) {
                return metaDef;
            }
        }
        return null;
    }

    /**
     * <code>S2Container</code> または <code>ComponentDef</code> が名前
     * <code>"axis-<var>localName</var></code> の <code>&lt;meta&gt;</code>
     * 要素を持っていれば、その <code>MetaDef</code> を全て返します。 <br>
     * 
     * @param metaDefSupport
     *            <code>S2Container</code> または <code>ComponentDef</code>
     * @param localName
     *            接頭辞 <code>axis-</code> に続くメタデータの名前
     * @return 指定された名前を持つ <code>MetaDef</code> の配列
     */
    protected MetaDef[] getMetaDefs(final MetaDefAware metaDefSupport, final String localName) {
        final List result = new ArrayList();
        for (int i = 0; i < metaDefSupport.getMetaDefSize(); ++i) {
            final MetaDef metaDef = metaDefSupport.getMetaDef(i);
            if (localName.equals(getLocalName(metaDef))) {
                result.add(metaDef);
            }
        }
        return (MetaDef[]) result.toArray(new MetaDef[result.size()]);
    }

    /**
     * メタデータの名前がS2Axisで使用する接頭辞で始まっていれば、接頭辞の後ろのローカル名を返します。
     * 
     * @param metaDef
     *            メタデータ定義
     * @return ローカル名
     */
    protected String getLocalName(final MetaDef metaDef) {
        final Matcher matcher = META_NAME_PATTERN.matcher(metaDef.getName());
        return matcher.matches() ? matcher.group(1) : null;
    }
}
