XmlTransformer.java

  1. /*
  2.  * Copyright (c) 2002-2022, City of Paris
  3.  * All rights reserved.
  4.  *
  5.  * Redistribution and use in source and binary forms, with or without
  6.  * modification, are permitted provided that the following conditions
  7.  * are met:
  8.  *
  9.  *  1. Redistributions of source code must retain the above copyright notice
  10.  *     and the following disclaimer.
  11.  *
  12.  *  2. Redistributions in binary form must reproduce the above copyright notice
  13.  *     and the following disclaimer in the documentation and/or other materials
  14.  *     provided with the distribution.
  15.  *
  16.  *  3. Neither the name of 'Mairie de Paris' nor 'Lutece' nor the names of its
  17.  *     contributors may be used to endorse or promote products derived from
  18.  *     this software without specific prior written permission.
  19.  *
  20.  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  21.  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  22.  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  23.  * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR CONTRIBUTORS BE
  24.  * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  25.  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  26.  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  27.  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  28.  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  29.  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  30.  * POSSIBILITY OF SUCH DAMAGE.
  31.  *
  32.  * License 1.0
  33.  */
  34. package fr.paris.lutece.util.xml;

  35. import fr.paris.lutece.portal.service.util.AppLogService;
  36. import fr.paris.lutece.portal.service.util.AppPropertiesService;

  37. import java.io.StringWriter;

  38. import java.util.ArrayList;
  39. import java.util.List;
  40. import java.util.Map;
  41. import java.util.Map.Entry;
  42. import java.util.Properties;
  43. import java.util.concurrent.ConcurrentHashMap;
  44. import java.util.concurrent.ConcurrentMap;

  45. import javax.xml.transform.Result;
  46. import javax.xml.transform.Source;
  47. import javax.xml.transform.Templates;
  48. import javax.xml.transform.Transformer;
  49. import javax.xml.transform.TransformerConfigurationException;
  50. import javax.xml.transform.TransformerException;
  51. import javax.xml.transform.TransformerFactory;
  52. import javax.xml.transform.TransformerFactoryConfigurationError;
  53. import javax.xml.transform.stream.StreamResult;

  54. /**
  55.  * This class provides methods to transform XML documents using XSLT with cache
  56.  */
  57. public final class XmlTransformer
  58. {
  59.     private static final String ERROR_MESSAGE_XLST = "Error transforming document XSLT : ";
  60.     public static final String PROPERTY_TRANSFORMER_POOL_SIZE = "service.xmlTransformer.transformerPoolSize";
  61.     public static final int TRANSFORMER_POOL_SIZE = AppPropertiesService.getPropertyInt( PROPERTY_TRANSFORMER_POOL_SIZE, 2 );
  62.     public static final int MAX_TRANSFORMER_SIZE = 1000;
  63.     private static final List<ConcurrentMap<String, Templates>> transformersPoolList = new ArrayList<>( TRANSFORMER_POOL_SIZE );

  64.     static
  65.     {
  66.         for ( int i = 0; i < TRANSFORMER_POOL_SIZE; i++ )
  67.         {
  68.             transformersPoolList.add( new ConcurrentHashMap<String, Templates>( MAX_TRANSFORMER_SIZE ) );
  69.         }
  70.     }

  71.     /**
  72.      * This method try to get a templates instance from cache or create a new one if can't.
  73.      *
  74.      * Previously (before 6.0.0) it returned directly a transformer, now it returns a templates which can create transformers cheaply.
  75.      *
  76.      * @param stylesheet
  77.      *            The XML document content
  78.      * @param strStyleSheetId
  79.      *            The StyleSheet Id
  80.      * @return XmlTransformer object
  81.      * @throws TransformerException
  82.      */
  83.     private Templates getTemplates( Source stylesheet, String strStyleSheetId ) throws TransformerException
  84.     {
  85.         Templates result = null;

  86.         if ( TRANSFORMER_POOL_SIZE > 0 )
  87.         {
  88.             int nTransformerListIndex = 0;

  89.             do
  90.             {
  91.                 result = transformersPoolList.get( nTransformerListIndex ).remove( strStyleSheetId );
  92.                 nTransformerListIndex++;
  93.             }
  94.             while ( ( result == null ) && ( nTransformerListIndex < TRANSFORMER_POOL_SIZE ) );
  95.         }

  96.         if ( result == null )
  97.         {
  98.             // only one thread can use transformer
  99.             try
  100.             {
  101.                 result = TransformerFactory.newInstance( ).newTemplates( stylesheet );
  102.                 AppLogService.debug( " --  XML Templates instantiation : strStyleSheetId= {}", strStyleSheetId );
  103.             }
  104.             catch( TransformerConfigurationException e )
  105.             {
  106.                 String strMessage = e.getMessage( );

  107.                 if ( e.getLocationAsString( ) != null )
  108.                 {
  109.                     strMessage += ( "- location : " + e.getLocationAsString( ) );
  110.                 }

  111.                 throw new TransformerException( ERROR_MESSAGE_XLST + strMessage, e.getCause( ) );
  112.             }
  113.             catch( TransformerFactoryConfigurationError e )
  114.             {
  115.                 throw new TransformerException( ERROR_MESSAGE_XLST + e.getMessage( ), e );
  116.             }
  117.         }

  118.         return result;
  119.     }

  120.     /**
  121.      * Remove all Templates instance from cache. Previously (before 6.0.0) the cache stored transformers, now it stores templates.
  122.      */
  123.     public static void cleanTransformerList( )
  124.     {
  125.         for ( ConcurrentMap<String, Templates> transformerList : transformersPoolList )
  126.         {
  127.             transformerList.clear( );
  128.         }
  129.     }

  130.     /**
  131.      * Gets the number of templates. Previously (before 6.0.0) the cache stored transformers, now it stores templates.
  132.      *
  133.      * @return the transformers count
  134.      */
  135.     public static int getTransformersCount( )
  136.     {
  137.         int nCount = 0;

  138.         for ( ConcurrentMap<String, Templates> transformerList : transformersPoolList )
  139.         {
  140.             nCount += transformerList.size( );
  141.         }

  142.         return nCount;
  143.     }

  144.     /**
  145.      * Release Transformer instance in cache. Previously (before 6.0.0) the cache stored transformers, now it stores templates.
  146.      *
  147.      * @param templates
  148.      *            The XML templates
  149.      * @param strStyleSheetId
  150.      *            The StyleSheet Id
  151.      */
  152.     private void releaseTemplates( Templates templates, String strStyleSheetId )
  153.     {
  154.         if ( TRANSFORMER_POOL_SIZE > 0 )
  155.         {
  156.             Templates result = null;
  157.             ConcurrentMap<String, Templates> transformerList = null;
  158.             int nTransformerListIndex = 0;

  159.             do
  160.             {
  161.                 transformerList = transformersPoolList.get( nTransformerListIndex );
  162.                 nTransformerListIndex++;

  163.                 // This set of action is not performed atomically but it can not cause problems
  164.                 if ( transformerList.size( ) < MAX_TRANSFORMER_SIZE )
  165.                 {
  166.                     result = transformerList.putIfAbsent( strStyleSheetId, templates );
  167.                 }
  168.                 else
  169.                 {
  170.                     // Aggressive release ( speed up GC )
  171.                     transformerList.clear( );

  172.                     AppLogService.info( "XmlTransformer : cache is full, you may need to increase cache size." );
  173.                 }
  174.             }
  175.             while ( ( result != null ) && ( nTransformerListIndex < TRANSFORMER_POOL_SIZE ) );
  176.         }
  177.     }

  178.     /**
  179.      * Transform XML documents using XSLT with cache
  180.      *
  181.      * @param source
  182.      *            The XML document content
  183.      * @param stylesheet
  184.      *            The XSL source
  185.      * @param strStyleSheetId
  186.      *            The StyleSheet Id
  187.      * @param params
  188.      *            Parameters that can be used by the XSL StyleSheet
  189.      * @param outputProperties
  190.      *            Properties to use for the XSL transform. Will overload the XSL output definition.
  191.      * @return The output document
  192.      * @throws TransformerException
  193.      *             The exception
  194.      */
  195.     public String transform( Source source, Source stylesheet, String strStyleSheetId, Map<String, String> params, Properties outputProperties )
  196.             throws TransformerException
  197.     {
  198.         Templates templates = this.getTemplates( stylesheet, strStyleSheetId );
  199.         Transformer transformer = templates.newTransformer( );

  200.         if ( outputProperties != null )
  201.         {
  202.             transformer.setOutputProperties( outputProperties );
  203.         }

  204.         if ( params != null )
  205.         {
  206.             transformer.clearParameters( );

  207.             for ( Entry<String, String> entry : params.entrySet( ) )
  208.             {
  209.                 transformer.setParameter( entry.getKey( ), entry.getValue( ) );
  210.             }
  211.         }

  212.         StringWriter sw = new StringWriter( );
  213.         Result result = new StreamResult( sw );

  214.         try
  215.         {
  216.             transformer.transform( source, result );
  217.         }
  218.         catch( TransformerException e )
  219.         {
  220.             String strMessage = "strStyleSheetId = " + strStyleSheetId + " " + e.getMessage( );

  221.             if ( e.getLocationAsString( ) != null )
  222.             {
  223.                 strMessage += ( " - location : " + e.getLocationAsString( ) );
  224.             }

  225.             throw new TransformerException( ERROR_MESSAGE_XLST + strMessage, e.getCause( ) );
  226.         }
  227.         finally
  228.         {
  229.             this.releaseTemplates( templates, strStyleSheetId );
  230.         }

  231.         return sw.toString( );
  232.     }
  233. }