View Javadoc
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  
36  import fr.paris.lutece.portal.service.util.AppLogService;
37  import fr.paris.lutece.portal.service.util.AppPropertiesService;
38  
39  import java.io.StringWriter;
40  
41  import java.util.ArrayList;
42  import java.util.List;
43  import java.util.Map;
44  import java.util.Map.Entry;
45  import java.util.Properties;
46  import java.util.concurrent.ConcurrentHashMap;
47  import java.util.concurrent.ConcurrentMap;
48  
49  import javax.xml.transform.Result;
50  import javax.xml.transform.Source;
51  import javax.xml.transform.Templates;
52  import javax.xml.transform.Transformer;
53  import javax.xml.transform.TransformerConfigurationException;
54  import javax.xml.transform.TransformerException;
55  import javax.xml.transform.TransformerFactory;
56  import javax.xml.transform.TransformerFactoryConfigurationError;
57  import javax.xml.transform.stream.StreamResult;
58  
59  /**
60   * This class provides methods to transform XML documents using XSLT with cache
61   */
62  public final class XmlTransformer
63  {
64      private static final String ERROR_MESSAGE_XLST = "Error transforming document XSLT : ";
65      public static final String PROPERTY_TRANSFORMER_POOL_SIZE = "service.xmlTransformer.transformerPoolSize";
66      public static final int TRANSFORMER_POOL_SIZE = AppPropertiesService.getPropertyInt( PROPERTY_TRANSFORMER_POOL_SIZE, 2 );
67      public static final int MAX_TRANSFORMER_SIZE = 1000;
68      private static final List<ConcurrentMap<String, Templates>> transformersPoolList = new ArrayList<>( TRANSFORMER_POOL_SIZE );
69  
70      static
71      {
72          for ( int i = 0; i < TRANSFORMER_POOL_SIZE; i++ )
73          {
74              transformersPoolList.add( new ConcurrentHashMap<String, Templates>( MAX_TRANSFORMER_SIZE ) );
75          }
76      }
77  
78      /**
79       * This method try to get a templates instance from cache or create a new one if can't.
80       *
81       * Previously (before 6.0.0) it returned directly a transformer, now it returns a templates which can create transformers cheaply.
82       * 
83       * @param stylesheet
84       *            The XML document content
85       * @param strStyleSheetId
86       *            The StyleSheet Id
87       * @return XmlTransformer object
88       * @throws TransformerException
89       */
90      private Templates getTemplates( Source stylesheet, String strStyleSheetId ) throws TransformerException
91      {
92          Templates result = null;
93  
94          if ( TRANSFORMER_POOL_SIZE > 0 )
95          {
96              int nTransformerListIndex = 0;
97  
98              do
99              {
100                 result = transformersPoolList.get( nTransformerListIndex ).remove( strStyleSheetId );
101                 nTransformerListIndex++;
102             }
103             while ( ( result == null ) && ( nTransformerListIndex < TRANSFORMER_POOL_SIZE ) );
104         }
105 
106         if ( result == null )
107         {
108             // only one thread can use transformer
109             try
110             {
111                 result = TransformerFactory.newInstance( ).newTemplates( stylesheet );
112                 AppLogService.debug( " --  XML Templates instantiation : strStyleSheetId= {}", strStyleSheetId );
113             }
114             catch( TransformerConfigurationException e )
115             {
116                 String strMessage = e.getMessage( );
117 
118                 if ( e.getLocationAsString( ) != null )
119                 {
120                     strMessage += ( "- location : " + e.getLocationAsString( ) );
121                 }
122 
123                 throw new TransformerException( ERROR_MESSAGE_XLST + strMessage, e.getCause( ) );
124             }
125             catch( TransformerFactoryConfigurationError e )
126             {
127                 throw new TransformerException( ERROR_MESSAGE_XLST + e.getMessage( ), e );
128             }
129         }
130 
131         return result;
132     }
133 
134     /**
135      * Remove all Templates instance from cache. Previously (before 6.0.0) the cache stored transformers, now it stores templates.
136      */
137     public static void cleanTransformerList( )
138     {
139         for ( ConcurrentMap<String, Templates> transformerList : transformersPoolList )
140         {
141             transformerList.clear( );
142         }
143     }
144 
145     /**
146      * Gets the number of templates. Previously (before 6.0.0) the cache stored transformers, now it stores templates.
147      * 
148      * @return the transformers count
149      */
150     public static int getTransformersCount( )
151     {
152         int nCount = 0;
153 
154         for ( ConcurrentMap<String, Templates> transformerList : transformersPoolList )
155         {
156             nCount += transformerList.size( );
157         }
158 
159         return nCount;
160     }
161 
162     /**
163      * Release Transformer instance in cache. Previously (before 6.0.0) the cache stored transformers, now it stores templates.
164      * 
165      * @param templates
166      *            The XML templates
167      * @param strStyleSheetId
168      *            The StyleSheet Id
169      */
170     private void releaseTemplates( Templates templates, String strStyleSheetId )
171     {
172         if ( TRANSFORMER_POOL_SIZE > 0 )
173         {
174             Templates result = null;
175             ConcurrentMap<String, Templates> transformerList = null;
176             int nTransformerListIndex = 0;
177 
178             do
179             {
180                 transformerList = transformersPoolList.get( nTransformerListIndex );
181                 nTransformerListIndex++;
182 
183                 // This set of action is not performed atomically but it can not cause problems
184                 if ( transformerList.size( ) < MAX_TRANSFORMER_SIZE )
185                 {
186                     result = transformerList.putIfAbsent( strStyleSheetId, templates );
187                 }
188                 else
189                 {
190                     // Aggressive release ( speed up GC )
191                     transformerList.clear( );
192 
193                     AppLogService.info( "XmlTransformer : cache is full, you may need to increase cache size." );
194                 }
195             }
196             while ( ( result != null ) && ( nTransformerListIndex < TRANSFORMER_POOL_SIZE ) );
197         }
198     }
199 
200     /**
201      * Transform XML documents using XSLT with cache
202      * 
203      * @param source
204      *            The XML document content
205      * @param stylesheet
206      *            The XSL source
207      * @param strStyleSheetId
208      *            The StyleSheet Id
209      * @param params
210      *            Parameters that can be used by the XSL StyleSheet
211      * @param outputProperties
212      *            Properties to use for the XSL transform. Will overload the XSL output definition.
213      * @return The output document
214      * @throws TransformerException
215      *             The exception
216      */
217     public String transform( Source source, Source stylesheet, String strStyleSheetId, Map<String, String> params, Properties outputProperties )
218             throws TransformerException
219     {
220         Templates templates = this.getTemplates( stylesheet, strStyleSheetId );
221         Transformer transformer = templates.newTransformer( );
222 
223         if ( outputProperties != null )
224         {
225             transformer.setOutputProperties( outputProperties );
226         }
227 
228         if ( params != null )
229         {
230             transformer.clearParameters( );
231 
232             for ( Entry<String, String> entry : params.entrySet( ) )
233             {
234                 transformer.setParameter( entry.getKey( ), entry.getValue( ) );
235             }
236         }
237 
238         StringWriter sw = new StringWriter( );
239         Result result = new StreamResult( sw );
240 
241         try
242         {
243             transformer.transform( source, result );
244         }
245         catch( TransformerException e )
246         {
247             String strMessage = "strStyleSheetId = " + strStyleSheetId + " " + e.getMessage( );
248 
249             if ( e.getLocationAsString( ) != null )
250             {
251                 strMessage += ( " - location : " + e.getLocationAsString( ) );
252             }
253 
254             throw new TransformerException( ERROR_MESSAGE_XLST + strMessage, e.getCause( ) );
255         }
256         finally
257         {
258             this.releaseTemplates( templates, strStyleSheetId );
259         }
260 
261         return sw.toString( );
262     }
263 }