/*
 * This file is part of Nuts Framework.
 * Copyright (C) 2009 Nuts Develop Team.
 *
 * Nuts Framework is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License any later version.
 * 
 * Nuts Framework 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 General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Nuts Framework. If not, see <http://www.gnu.org/licenses/>.
 */
package nuts.exts.struts2.dispatcher;

import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.List;

import javax.xml.transform.ErrorListener;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import nuts.core.oxm.adapter.AdapterFactory;
import nuts.core.oxm.adapter.WildcardPropertyFilter;
import nuts.core.oxm.adapter.filters.ClassPropertyFilter;
import nuts.core.oxm.adapter.filters.MultiPropertyFilter;
import nuts.core.oxm.adapter.filters.NullPropertyFilter;
import nuts.core.util.Filter;
import nuts.core.util.Pager;
import nuts.core.util.Query;
import nuts.core.util.Sorter;
import nuts.exts.fileupload.UploadFile;
import nuts.exts.fileupload.UploadImage;
import nuts.exts.oxm.adapter.filters.FilterPropertyFilter;
import nuts.exts.oxm.adapter.filters.PagerPropertyFilter;
import nuts.exts.oxm.adapter.filters.QueryPropertyFilter;
import nuts.exts.oxm.adapter.filters.SorterPropertyFilter;
import nuts.exts.oxm.adapter.filters.UploadFilePropertyFilter;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.Document;

/**
 * <!-- START SNIPPET: description -->
 * 
 * A result that send the content out as plain json text.
 * 
 * <!-- END SNIPPET: description -->
 * 
 * 
 * <!-- START SNIPPET: params -->
 * 
 * <ul>
 * <li>circleStrategy (optional) = circleStrategy to be used. [strict, lenient,
 * noprop] (default: strict)</li>
 * <li>dateFormat (optional) = date format to be used. (default: long -
 * Date.getTime())</li>
 * <li>filterTypes (optional) = filter specified property types when serialize.
 * (default: java.io.*, java.awt.*, [B, [Ljava.lang.Byte;)</li>
 * <li>noCache (optional) = no cache to set. (default: true)</li>
 * <li>contentType (optional) = content type to be used. (default:
 * text/javascript)</li>
 * <li>encoding (optional) = character encoding to be used. This character
 * encoding will be used to set. (default: UTF-8)</li>
 * <li>properties (default) = extra properties to be output. { "user": ... }</li>
 * </ul>
 * 
 * <!-- END SNIPPET: params -->
 * 
 * 
 * <pre>
 * <!-- START SNIPPET: example -->
 * 
 * &lt;action name="displayJsonContent" &gt;
 *   &lt;result type="json"&gt;username, password, other&lt;/result&gt;
 * &lt;/action&gt;
 * 
 * &lt;action name="displayJsonContent" &gt;
 *   &lt;result type="json"&gt;
 *   	&lt;param name="properties"&gt;username, password, other&lt;/param&gt;
 *   &lt;/result&gt;
 * &lt;/action&gt;
 * 
 * <!-- END SNIPPET: example -->
 * </pre>
 * 
 */
@SuppressWarnings("serial")
public class XmlResult extends AbstractOMResult {

	protected static final Log log = LogFactory.getLog(XmlResult.class);

	private static final String DEFAULT_CONTENT_TYPE = "text/xml";

	private String rootName = "result";
	
	/**
	 * Constructor.
	 */
	public XmlResult() {
		super();
		setContentType(DEFAULT_CONTENT_TYPE);
	}

	/**
	 * @return the rootName
	 */
	public String getRootName() {
		return rootName;
	}

	/**
	 * @param rootName the rootName to set
	 */
	public void setRootName(String rootName) {
		this.rootName = rootName;
	}

	private static class TransformerErrorListener implements ErrorListener {
		public void error(TransformerException exception)
				throws TransformerException {
			throw exception;
		}

		public void fatalError(TransformerException exception)
				throws TransformerException {
			throw exception;
		}

		public void warning(TransformerException exception)
				throws TransformerException {
			log.warn(exception.getMessage(), exception);
		}
	}

	/**
	 * write result
	 * 
	 * @param writer response writer
	 * @param result result object
	 * @throws Exception
	 */
	protected void writeResult(PrintWriter writer, Object result) throws Exception {
		if (result != null) {
			Document xml = resolveXml(result);

			Transformer transformer = TransformerFactory.newInstance().newTransformer();
			transformer.setErrorListener(new TransformerErrorListener());
			transformer.setOutputProperty(OutputKeys.ENCODING, encoding);
			
			DOMSource src = new DOMSource(xml);
			if (log.isDebugEnabled()) {
				StringWriter sw = new StringWriter();
				transformer.transform(src, new StreamResult(sw));
				log.debug(sw);
			}

			transformer.transform(src, new StreamResult(writer));
		}
	}

	protected Document resolveXml(Object result) throws Exception {
		AdapterFactory adapterFactory = new AdapterFactory();
		if (cycleDetect != null) {
			if (CYCLE_DETECT_STRICT.equals(cycleDetect)) {
				adapterFactory.setCycleDetect(AdapterFactory.CYCLE_DETECT_STRICT);
			}
			else if (CYCLE_DETECT_LENIENT.equals(cycleDetect)) {
				adapterFactory.setCycleDetect(AdapterFactory.CYCLE_DETECT_LENIENT);
			}
			else if (CYCLE_DETECT_NOPROP.equals(cycleDetect)) {
				adapterFactory.setCycleDetect(AdapterFactory.CYCLE_DETECT_NOPROP);
			}
		}

		MultiPropertyFilter mpf = new MultiPropertyFilter();
		adapterFactory.setPropertyFilter(mpf);

		//addNullPropertyFilter(mpf);
		addBeanPropertyFilter(mpf);
		addTypePropertyFilter(mpf);
		
		return adapterFactory.adaptDocument(rootName, result);
	}
	
	protected void addNullPropertyFilter(MultiPropertyFilter mpf) {
		mpf.addFilter(new NullPropertyFilter());
	}
	
	protected void addBeanPropertyFilter(MultiPropertyFilter mpf) {
		ClassPropertyFilter cpf = new ClassPropertyFilter();

		cpf.addFilter(Filter.class, new FilterPropertyFilter());
		cpf.addFilter(Pager.class, new PagerPropertyFilter());
		cpf.addFilter(Query.class, new QueryPropertyFilter());
		cpf.addFilter(Sorter.class, new SorterPropertyFilter());
		cpf.addFilter(UploadFile.class, new UploadFilePropertyFilter());
		cpf.addFilter(UploadImage.class, new UploadFilePropertyFilter());
		
		mpf.addFilter(cpf);
	}

	protected void addTypePropertyFilter(MultiPropertyFilter mpf) {
		List<String> fts = toList(filterTypes);
		if (!fts.isEmpty()) {
			WildcardPropertyFilter wpf = new WildcardPropertyFilter();
			wpf.setFilterTypes(fts);
			mpf.addFilter(wpf);
		}
	}
}
