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