//  Copyright 2004 The Apache Software Foundation
//
// 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.apache.tapestry.engine;

import java.io.IOException;

import javax.servlet.ServletException;

import org.apache.tapestry.ApplicationRuntimeException;
import org.apache.tapestry.IComponent;
import org.apache.tapestry.IExternalPage;
import org.apache.tapestry.IRequestCycle;
import org.apache.tapestry.Tapestry;
import org.apache.tapestry.request.ResponseOutputStream;

/**
 * The external service enables external applications
 * to reference Tapestry pages via a URL. Pages which can be referenced
 * by the external service must implement the {@link IExternalPage}
 * interface. The external service enables the bookmarking of pages.
 * 
 * <p>
 * The external service may also be used by the Tapestry JSP taglibrary
 * ({@link org.apache.tapestry.jsp.ExternalURLTag} and {@link org.apache.tapestry.jsp.ExternalTag}).
 * 
 * <p> 
 * You can try and second guess the URL format used by Tapestry. 
 * The default URL format for the external service is:
 * <blockquote>
 * <tt>http://localhost/app?service=external/<i>[Page Name]</i>&amp;sp=[Param 0]&amp;sp=[Param 1]...</tt>
 * </blockquote>
 * For example to view the "ViewCustomer" page the service parameters 5056 (customer ID) and
 * 309 (company ID) the external service URL would be:
 * <blockquote>
 * <tt>http://localhost/myapp?service=external&amp;context=<b>ViewCustomer</b>&amp;sp=<b>5056</b>&amp;sp=<b>302</b></tt>
 * </blockquote>
 * In this example external service will get a "ViewCustomer" page and invoke the 
 * {@link IExternalPage#activateExternalPage(Object[], IRequestCycle)} method with the parameters:  
 * Object[] { new Integer(5056), new Integer(302) }.
 * <p>
 * Note service parameters (sp) need to be prefixed by valid
 * {@link org.apache.tapestry.util.io.DataSqueezer} adaptor char. These adaptor chars are automatically provided in
 * URL's created by the <tt>buildGesture()</tt> method. However if you hand coded an external 
 * service URL you will need to ensure valid prefix chars are present.
 * <p>
 * <table border="1" cellpadding="2">
 *  <tr>
 *   <th>Prefix char(s)</th><th>Mapped Java Type</th>
 *  </tr>
 *  <tr>
 *   <td>&nbsp;TF</td><td>&nbsp;boolean</td>
 *  </tr>
 *  <tr>
 *   <td>&nbsp;b</td><td>&nbsp;byte</td>
 *  </tr>
 *  <tr>
 *   <td>&nbsp;c</td><td>&nbsp;char</td>
 *  </tr>
 *  <tr>
 *   <td>&nbsp;d</td><td>&nbsp;double</td>
 *  </tr>
 *  <tr>
 *   <td>&nbsp;-0123456789</td><td>&nbsp;integer</td>
 *  </tr>
 *  <tr>
 *   <td>&nbsp;l</td><td>&nbsp;long</td>
 *  </tr>
 *  <tr>
 *   <td>&nbsp;S</td><td>&nbsp;String</td>
 *  </tr>
 *  <tr>
 *   <td>&nbsp;s</td><td>&nbsp;short</td>
 *  </tr>
 *  <tr>
 *   <td>&nbsp;other chars</td>
 *   <td>&nbsp;<tt>String</tt> without truncation of first char</td>
 *  </tr>
 * <table>
 *  <p>
 *  <p>
 *  A good rule of thumb is to keep the information encoded in the URL short and simple, and restrict it
 *  to just Strings and Integers.  Integers can be encoded as-is.  Prefixing all Strings with the letter 'S'
 *  will ensure that they are decoded properly.  Again, this is only relevant if an 
 *  {@link org.apache.tapestry.IExternalPage} is being referenced from static HTML or JSP and the
 *  URL must be assembled in user code ... when the URL is generated by Tapestry, it is automatically
 *  created with the correct prefixes and encodings (as with any other service).
 * 
 * @see org.apache.tapestry.IExternalPage
 * @see org.apache.tapestry.jsp.ExternalTag
 * @see org.apache.tapestry.jsp.ExternalURLTag
 *
 * @author Howard Lewis Ship
 * @author Malcolm Edgar
 * @since 2.2
 *  
 **/

public class ExternalService extends AbstractService
{

  /**
   *  Builds a URL for a service.  This is performed during the
   *  rendering phase of one request cycle and bulds URLs that will
   *  invoke activity in a subsequent request cycle.
   *
   *  @param cycle Defines the request cycle being processed.
   *  @param component The component requesting the URL.  Generally, the
   *  service context is established from the component.
   *  @param parameters Additional parameters specific to the
   *  component requesting the EngineServiceLink.
   *  @return The URL for the service.  The URL always be encoded when it is returned.
   *
   **/
  
    public ILink getLink(IRequestCycle cycle, IComponent component, Object[] parameters)
    {
        if (parameters == null || parameters.length == 0)
            throw new ApplicationRuntimeException(
                Tapestry.format("service-requires-parameters", Tapestry.EXTERNAL_SERVICE));

        String pageName = (String) parameters[0];
        String[] context = new String[] { pageName };

        Object[] pageParameters = new Object[parameters.length - 1];
        System.arraycopy(parameters, 1, pageParameters, 0, parameters.length - 1);

        return constructLink(cycle, Tapestry.EXTERNAL_SERVICE, context, pageParameters, true);
    }

    public void service(
        IEngineServiceView engine,
        IRequestCycle cycle,
        ResponseOutputStream output)
        throws ServletException, IOException
    {
        IExternalPage page = null;

        String[] context = getServiceContext(cycle.getRequestContext());

        if (context == null || context.length != 1)
            throw new ApplicationRuntimeException(
                Tapestry.format("service-single-context-parameter", Tapestry.EXTERNAL_SERVICE));

        String pageName = context[0];

        try
        {
            page = (IExternalPage) cycle.getPage(pageName);
        }
        catch (ClassCastException ex)
        {
            throw new ApplicationRuntimeException(
                Tapestry.format("ExternalService.page-not-compatible", pageName),
                ex);
        }

        Object[] parameters = getParameters(cycle);

        cycle.setServiceParameters(parameters);

        cycle.activate(page);

        page.activateExternalPage(parameters, cycle);

        // Render the response.
        engine.renderResponse(cycle, output);
    }

    public String getName()
    {
        return Tapestry.EXTERNAL_SERVICE;
    }
}