/*
 * 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.wicket.markup.transformer;

import java.io.FileNotFoundException;
import java.io.StringReader;
import java.io.StringWriter;

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;

import org.apache.wicket.Application;
import org.apache.wicket.Component;
import org.apache.wicket.util.resource.IResourceStream;
import org.apache.wicket.core.util.resource.locator.IResourceStreamLocator;


/**
 * A processor to XSLT transform the output generated by a Component.
 * 
 * @see org.apache.wicket.markup.transformer.XsltOutputTransformerContainer
 * @see org.apache.wicket.markup.transformer.XsltTransformerBehavior
 * 
 * @author Juergen Donnerstag
 */
public class XsltTransformer implements ITransformer
{
	private final static String extension = "xsl";

	/** an optional XSL file */
	private final String xslFile;

	/**
	 * Construct.
	 */
	public XsltTransformer()
	{
		xslFile = null;
	}

	/**
	 * Instead of using the default mechanism to determine the associated XSL file, it is given by
	 * the user.
	 * 
	 * @param xslFile
	 *            XSL input file path relative to the component's package. If the path does not end
	 *            with <tt>.xsl</tt>, then it is considered as a basename and will be passed as-is
	 *            to
	 *            {@link IResourceStreamLocator#locate(Class, String, String, String, java.util.Locale, String, boolean)}
	 *            . All stylesheets must have the <tt>.xsl</tt> extension.
	 */
	public XsltTransformer(final String xslFile)
	{
		if ((xslFile != null) && xslFile.endsWith(extension))
		{
			this.xslFile = xslFile.substring(0, xslFile.length() - extension.length() - 1);
		}
		else
		{
			this.xslFile = xslFile;
		}
	}

	/**
	 * Apply a XSL transformation to the markup generated by a component. The *.xsl resource must be
	 * located in the same path as the nearest parent with an associated markup and must have a
	 * filename equal to the component's id.
	 */
	@Override
	public CharSequence transform(final Component component, final CharSequence output)
		throws Exception
	{
		IResourceStream resourceStream = getResourceStream(component);

		if (resourceStream == null)
		{
			throw new FileNotFoundException("Unable to find XSLT resource for " +
				component.toString());
		}

		try
		{
			// 1. Instantiate a TransformerFactory.
			TransformerFactory tFactory = TransformerFactory.newInstance();

			// 2. Use the TransformerFactory to process the stylesheet Source
			// and
			// generate a Transformer.
			Transformer transformer = tFactory.newTransformer(new StreamSource(
				resourceStream.getInputStream()));

			// 3. Use the Transformer to transform an XML Source and send the
			// output to a Result object.
			StringWriter writer = new StringWriter();
			transformer.transform(new StreamSource(new StringReader(output.toString())),
				new StreamResult(writer));

			return writer.getBuffer();
		}
		finally
		{
			resourceStream.close();
		}
	}

	/**
	 * Get the XSL resource stream
	 * 
	 * @param component
	 * 
	 * @return The XSLT file resource stream
	 */
	private IResourceStream getResourceStream(final Component component)
	{
		final IResourceStream resourceStream;

		String filePath = xslFile;
		if (filePath == null)
		{
			filePath = component.findParentWithAssociatedMarkup()
				.getClass()
				.getPackage()
				.getName()
				.replace('.', '/') +
				"/" + component.getId();
		}

		resourceStream = Application.get()
			.getResourceSettings()
			.getResourceStreamLocator()
			.locate(getClass(), filePath, component.getStyle(), component.getVariation(),
				component.getLocale(), XsltTransformer.extension, false);

		return resourceStream;
	}
}
