/**
 * JOnAS: Java(TM) Open Application Server
 * Copyright (C) 2011 Bull S.A.S.
 * Contact: jonas-team@ow2.org
 *
 * This library 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 library 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 library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */

package org.ow2.jonas.web.jetty8;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.List;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;

import org.apache.jasper.JasperException;
import org.apache.jasper.runtime.TldScanner;

/**
 * This TLD scanner is delegating all the public methods to the delegating
 * scanner. But before answering, it will initialize the delegating scanner with
 * new TLD that were registered dynamically.
 * @author Florent Benoit
 */
public class JOnASTldScanner extends TldScanner {

    /**
     * Delegating scanner.
     */
    private TldScanner delegateTldScanner = null;

    /**
     * Already initialized ?
     */
    private boolean initialized = false;

    /**
     * List of resources to analyze.
     */
    private List<URL> resourcesToMonitor = null;

    /**
     * Create a new scanner with the delegating scanner and the list of Tld
     * resources to monitor.
     * @param delegateTldScanner the previous registered scanner
     * @param resourcesToMonitor the TLD to monitor
     */
    public JOnASTldScanner(final TldScanner delegateTldScanner, final List<URL> resourcesToMonitor) {
        this.delegateTldScanner = delegateTldScanner;
        this.resourcesToMonitor = resourcesToMonitor;
    }

    /**
     * Gets the 'location' of the TLD associated with the given taglib 'uri'.
     * @param uri the given taglib uri
     * @return the location (which is an array)
     * @throws JasperException if location can't be obtained
     */
    @Override
    @SuppressWarnings("unchecked")
    public String[] getLocation(final String uri) throws JasperException {

        if (!initialized) {

            // Init delegating code by asking it a first time
            delegateTldScanner.getLocation(uri);

            // Get the method
            Method scanTldMethod;
            try {
                scanTldMethod = TldScanner.class.getDeclaredMethod("scanTld", String.class, String.class, InputStream.class);
            } catch (SecurityException e) {
                throw new JasperException("Unable to get scanTld method", e);
            } catch (NoSuchMethodException e) {
                throw new JasperException("Unable to get scanTld method", e);
            }
            scanTldMethod.setAccessible(true);

            // Get mappings field
            Field mappingsField = null;
            try {
                mappingsField = TldScanner.class.getDeclaredField("mappings");
            } catch (SecurityException e) {
                throw new JasperException("Unable to get mappings field", e);
            } catch (NoSuchFieldException e) {
                throw new JasperException("Unable to get mappings field", e);
            }
            mappingsField.setAccessible(true);
            HashMap<String, String[]> mappings;
            try {
                mappings = (HashMap<String, String[]>) mappingsField.get(delegateTldScanner);
            } catch (IllegalArgumentException e) {
                throw new JasperException("Unable to get mappings field", e);
            } catch (IllegalAccessException e) {
                throw new JasperException("Unable to get mappings field", e);
            }

            // Add each taglib into the cache
            if (resourcesToMonitor != null) {
                for (URL url : resourcesToMonitor) {

                    Object tldInfo = null;
                    try {
                        URLConnection urlConnection = url.openConnection();
                        urlConnection.setDefaultUseCaches(false);

                        tldInfo = scanTldMethod
                                .invoke(delegateTldScanner, url.toString(), null, urlConnection.getInputStream());
                    } catch (IOException e) {
                        throw new IllegalStateException("Unable to add the TLD", e);
                    } catch (IllegalAccessException e) {
                        throw new IllegalStateException("Unable to add the TLD", e);
                    } catch (InvocationTargetException e) {
                        throw new IllegalStateException("Unable to add the TLD", e);
                    }

                    // Not found
                    if (tldInfo == null) {
                        continue;
                    }

                    // Get URI
                    Field uriField = null;
                    try {
                        uriField = tldInfo.getClass().getDeclaredField("uri");
                    } catch (SecurityException e) {
                        throw new JasperException("Unable to get uri field", e);
                    } catch (NoSuchFieldException e) {
                        throw new JasperException("Unable to get uri field", e);
                    }
                    uriField.setAccessible(true);
                    String uriStr = null;
                    try {
                        uriStr = (String) uriField.get(tldInfo);
                    } catch (IllegalArgumentException e) {
                        throw new JasperException("Unable to get listeners field", e);
                    } catch (IllegalAccessException e) {
                        throw new JasperException("Unable to get listeners field", e);
                    }

                    // Add URI into the mapping
                    mappings.put(uriStr, new String[] {url.toString(), null});
                }
            }

            initialized = true;
        }

        return delegateTldScanner.getLocation(uri);

    }

    /**
     * Only delegate the method.
     * @param c the set of classes
     * @param ctxt the servlet context
     * @throws ServletException if it fails
     */
    @Override
    public void onStartup(final java.util.Set<java.lang.Class<?>> c, final ServletContext ctxt) throws ServletException {
        delegateTldScanner.onStartup(c, ctxt);
    }

}
