content: /**
   * 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.solr.response;
  
  import java.io.BufferedReader;
  import java.io.CharArrayReader;
  import java.io.CharArrayWriter;
  import java.io.IOException;
  import java.io.Reader;
  import java.io.Writer;
  import java.util.Map;
  import org.slf4j.Logger;
  import org.slf4j.LoggerFactory;
  
  import javax.xml.transform.Transformer;
  import javax.xml.transform.TransformerException;
  import javax.xml.transform.stream.StreamResult;
  import javax.xml.transform.stream.StreamSource;
  
  import org.apache.solr.core.SolrConfig;
  import org.apache.solr.common.params.SolrParams;
  import org.apache.solr.common.util.NamedList;
  import org.apache.solr.common.util.XMLErrorLogger;
  import org.apache.solr.request.SolrQueryRequest;
  import org.apache.solr.util.xslt.TransformerProvider;
  
  /** QueryResponseWriter which captures the output of the XMLWriter
   *  (in memory for now, not optimal performancewise), and applies an XSLT transform
   *  to it.
   */
  public class XSLTResponseWriter implements QueryResponseWriter {
  
    public static final String DEFAULT_CONTENT_TYPE = "application/xml";
    public static final String TRANSFORM_PARAM = "tr";
    public static final String CONTEXT_TRANSFORMER_KEY = "xsltwriter.transformer";
    
    private Integer xsltCacheLifetimeSeconds = null; 
    public static final int XSLT_CACHE_DEFAULT = 60;
    private static final String XSLT_CACHE_PARAM = "xsltCacheLifetimeSeconds"; 
  
    private static final Logger log = LoggerFactory.getLogger(XSLTResponseWriter.class);
    private static final XMLErrorLogger xmllog = new XMLErrorLogger(log);
    
    public void init(NamedList n) {
        final SolrParams p = SolrParams.toSolrParams(n);
        xsltCacheLifetimeSeconds = p.getInt(XSLT_CACHE_PARAM,XSLT_CACHE_DEFAULT);
        log.info("xsltCacheLifetimeSeconds=" + xsltCacheLifetimeSeconds);
    }
  
    
    public String getContentType(SolrQueryRequest request, SolrQueryResponse response) {
      Transformer t = null;
      try {
        t = getTransformer(request);
      } catch(Exception e) {
        // TODO should our parent interface throw (IO)Exception?
        throw new RuntimeException("getTransformer fails in getContentType",e);
      }
      
      String mediaType = t.getOutputProperty("media-type");
      if (mediaType == null || mediaType.length()==0) {
        // This did not happen in my tests, mediaTypeFromXslt is set to "text/xml"
        // if the XSLT transform does not contain an xsl:output element. Not sure
        // if this is standard behavior or if it's just my JVM/libraries
        mediaType = DEFAULT_CONTENT_TYPE;
      }
      
      if (!mediaType.contains("charset")) {
        String encoding = t.getOutputProperty("encoding");
        if (encoding == null || encoding.length()==0) {
          encoding = "UTF-8";
        }
        mediaType = mediaType + "; charset=" + encoding;
      }
      
      return mediaType;
    }
  
    public void write(Writer writer, SolrQueryRequest request, SolrQueryResponse response) throws IOException {
      final Transformer t = getTransformer(request);
      
      // capture the output of the XMLWriter
      final CharArrayWriter w = new CharArrayWriter();
      XMLWriter.writeResponse(w,request,response);
      
      // and write transformed result to our writer
      final Reader r = new BufferedReader(new CharArrayReader(w.toCharArray()));
      final StreamSource source = new StreamSource(r);
      final StreamResult result = new StreamResult(writer);
      try {
        t.transform(source, result);
      } catch(TransformerException te) {
        final IOException ioe = new IOException("XSLT transformation error");
        ioe.initCause(te);
        throw ioe;
      }
    }
    
    /** Get Transformer from request context, or from TransformerProvider.
     *  This allows either getContentType(...) or write(...) to instantiate the Transformer,
     *  depending on which one is called first, then the other one reuses the same Transformer
     */
    protected Transformer getTransformer(SolrQueryRequest request) throws IOException {
      final String xslt = request.getParams().get(TRANSFORM_PARAM,null);
      if(xslt==null) {
        throw new IOException("'" + TRANSFORM_PARAM + "' request parameter is required to use the XSLTResponseWriter");
      }
      // not the cleanest way to achieve this
      SolrConfig solrConfig = request.getCore().getSolrConfig();
      // no need to synchronize access to context, right? 
      // Nothing else happens with it at the same time
      final Map<Object,Object> ctx = request.getContext();
      Transformer result = (Transformer)ctx.get(CONTEXT_TRANSFORMER_KEY);
      if(result==null) {
        result = TransformerProvider.instance.getTransformer(solrConfig, xslt,xsltCacheLifetimeSeconds.intValue());
        result.setErrorListener(xmllog);
        ctx.put(CONTEXT_TRANSFORMER_KEY,result);
      }
      return result;
    }
  }
  