View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    * 
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   * 
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License.
18   * 
19   */
20  package org.apache.directory.api.dsmlv2;
21  
22  
23  import java.io.IOException;
24  import java.util.Arrays;
25  import java.util.Collection;
26  
27  import javax.xml.transform.Transformer;
28  import javax.xml.transform.TransformerConfigurationException;
29  import javax.xml.transform.TransformerException;
30  import javax.xml.transform.TransformerFactory;
31  import javax.xml.transform.stream.StreamSource;
32  
33  import org.apache.directory.api.dsmlv2.request.BatchRequestDsml;
34  import org.apache.directory.api.dsmlv2.request.BatchRequestDsml.Processing;
35  import org.apache.directory.api.dsmlv2.request.BatchRequestDsml.ResponseOrder;
36  import org.apache.directory.api.i18n.I18n;
37  import org.apache.directory.api.ldap.codec.api.CodecControl;
38  import org.apache.directory.api.ldap.codec.api.LdapApiService;
39  import org.apache.directory.api.ldap.model.entry.BinaryValue;
40  import org.apache.directory.api.ldap.model.entry.StringValue;
41  import org.apache.directory.api.ldap.model.ldif.LdifUtils;
42  import org.apache.directory.api.ldap.model.message.Control;
43  import org.apache.directory.api.util.Base64;
44  import org.apache.directory.api.util.Strings;
45  import org.dom4j.Document;
46  import org.dom4j.Element;
47  import org.dom4j.Namespace;
48  import org.dom4j.QName;
49  import org.dom4j.io.DocumentResult;
50  import org.dom4j.io.DocumentSource;
51  import org.slf4j.Logger;
52  import org.slf4j.LoggerFactory;
53  import org.xmlpull.v1.XmlPullParser;
54  import org.xmlpull.v1.XmlPullParserException;
55  
56  
57  /**
58   * This class is a Helper class for the DSML Parser
59   *
60   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
61   */
62  public final class ParserUtils
63  {
64      /** W3C XML Schema URI. */
65      public static final String XML_SCHEMA_URI = "http://www.w3.org/2001/XMLSchema";
66  
67      /** W3C XML Schema Instance URI. */
68      public static final String XML_SCHEMA_INSTANCE_URI = "http://www.w3.org/2001/XMLSchema-instance";
69  
70      /** Base-64 identifier. */
71      public static final String BASE64BINARY = "base64Binary";
72  
73      /** XSI namespace prefix. */
74      public static final String XSI = "xsi";
75  
76      /** XSD namespace prefix. */
77      public static final String XSD = "xsd";
78  
79      /** The DSML namespace */
80      public static final Namespace DSML_NAMESPACE = new Namespace( null, "urn:oasis:names:tc:DSML:2:0:core" );
81  
82      /** The XSD namespace */
83      public static final Namespace XSD_NAMESPACE = new Namespace( XSD, XML_SCHEMA_URI );
84  
85      /** The XSI namespace */
86      public static final Namespace XSI_NAMESPACE = new Namespace( XSI, XML_SCHEMA_INSTANCE_URI );
87  
88      /** A logger for this class */
89      private static final Logger LOG = LoggerFactory.getLogger( ParserUtils.class );
90  
91  
92      /**
93       * Returns the value of the attribute 'type' of the "XMLSchema-instance' namespace if it exists
94       *
95       * @param xpp the XPP parser to use
96       * @return the value of the attribute 'type' of the "XMLSchema-instance' namespace if it exists
97       */
98      public static String getXsiTypeAttributeValue( XmlPullParser xpp )
99      {
100         String type = null;
101         int nbAttributes = xpp.getAttributeCount();
102 
103         for ( int i = 0; i < nbAttributes; i++ )
104         {
105             // Checking if the attribute 'type' from XML Schema Instance namespace is used.
106             if ( xpp.getAttributeName( i ).equals( "type" )
107                 && xpp.getNamespace( xpp.getAttributePrefix( i ) ).equals( XML_SCHEMA_INSTANCE_URI ) )
108             {
109                 type = xpp.getAttributeValue( i );
110                 break;
111             }
112         }
113 
114         return type;
115     }
116 
117 
118     /**
119      * Tells is the given value is a Base64 binary value
120      * 
121      * @param parser the XPP parser to use
122      * @param attrValue the attribute value
123      * @return true if the value of the current tag is Base64BinaryEncoded, false if not
124      */
125     public static boolean isBase64BinaryValue( XmlPullParser parser, String attrValue )
126     {
127         if ( attrValue == null )
128         {
129             return false;
130         }
131 
132         // We are looking for something that should look like that: "aNameSpace:base64Binary"
133         // We split the String. The first element should be the namespace prefix and the second "base64Binary"
134         String[] splitedString = attrValue.split( ":" );
135 
136         return ( splitedString.length == 2 ) && ( XML_SCHEMA_URI.equals( parser.getNamespace( splitedString[0] ) ) )
137             && ( BASE64BINARY.equals( splitedString[1] ) );
138     }
139 
140 
141     /**
142      * Indicates if the value needs to be encoded as Base64
143      *
144      * @param value the value to check
145      * @return true if the value needs to be encoded as Base64
146      */
147     public static boolean needsBase64Encoding( Object value )
148     {
149         if ( value instanceof StringValue )
150         {
151             return false;
152         }
153         else if ( value instanceof BinaryValue )
154         {
155             return false;
156         }
157         else if ( value instanceof byte[] )
158         {
159             return true;
160         }
161         else if ( value instanceof String )
162         {
163             return !LdifUtils.isLDIFSafe( ( String ) value );
164         }
165 
166         return true;
167     }
168 
169 
170     /**
171      * Encodes the value as a Base64 String
172      *
173      * @param value the value to encode
174      * @return the value encoded as a Base64 String
175      */
176     public static String base64Encode( Object value )
177     {
178         if ( value instanceof byte[] )
179         {
180             return new String( Base64.encode( ( byte[] ) value ) );
181         }
182         else if ( value instanceof String )
183         {
184             return new String( Base64.encode( Strings.getBytesUtf8( ( String ) value ) ) );
185         }
186 
187         return "";
188     }
189 
190 
191     /**
192      * Parses and verify the parsed value of the requestID
193      * 
194      * @param attributeValue the value of the attribute
195      * @param xpp the XmlPullParser
196      * @return the int value of the resquestID
197      * @throws XmlPullParserException if RequestID isn't an Integer and if requestID is below 0
198      */
199     public static int parseAndVerifyRequestID( String attributeValue, XmlPullParser xpp ) throws XmlPullParserException
200     {
201         try
202         {
203             int requestID = Integer.parseInt( attributeValue );
204 
205             if ( requestID < 0 )
206             {
207                 throw new XmlPullParserException( I18n.err( I18n.ERR_03038, requestID ), xpp, null );
208             }
209 
210             return requestID;
211         }
212         catch ( NumberFormatException e )
213         {
214             throw new XmlPullParserException( I18n.err( I18n.ERR_03039 ), xpp, null );
215         }
216     }
217 
218 
219     /**
220      * Adds Controls to the given Element.
221      *
222      * @param element the element to add the Controls to
223      * @param controls a List of Controls
224      */
225     public static void addControls( LdapApiService codec, Element element, Collection<Control> controls )
226     {
227         if ( controls != null )
228         {
229             for ( Control control : controls )
230             {
231                 Element controlElement = element.addElement( "control" );
232 
233                 if ( control.getOid() != null )
234                 {
235                     controlElement.addAttribute( "type", control.getOid() );
236                 }
237 
238                 if ( control.isCritical() )
239                 {
240                     controlElement.addAttribute( "criticality", "true" );
241                 }
242 
243                 byte[] value;
244 
245                 if ( control instanceof CodecControl<?> )
246                 {
247                     value = ( ( org.apache.directory.api.ldap.codec.api.CodecControl<?> ) control ).getValue();
248                 }
249                 else
250                 {
251                     value = codec.newControl( control ).getValue();
252                 }
253 
254                 if ( value != null )
255                 {
256                     if ( ParserUtils.needsBase64Encoding( value ) )
257                     {
258                         element.getDocument().getRootElement().add( XSD_NAMESPACE );
259                         element.getDocument().getRootElement().add( XSI_NAMESPACE );
260 
261                         Element valueElement = controlElement.addElement( "controlValue" ).addText(
262                             ParserUtils.base64Encode( value ) );
263                         valueElement.addAttribute( new QName( "type", XSI_NAMESPACE ), ParserUtils.XSD + ":"
264                             + ParserUtils.BASE64BINARY );
265                     }
266                     else
267                     {
268                         controlElement.addElement( "controlValue" ).setText( Arrays.toString( value ) );
269                     }
270                 }
271             }
272         }
273     }
274 
275 
276     /**
277      * Indicates if a request ID is needed.
278      *
279      * @param container the associated container
280      * @return true if a request ID is needed (ie Processing=Parallel and ResponseOrder=Unordered)
281      * @throws XmlPullParserException if the batch request has not been parsed yet
282      */
283     public static boolean isRequestIdNeeded( Dsmlv2Container container ) throws XmlPullParserException
284     {
285         BatchRequestDsml batchRequest = container.getBatchRequest();
286 
287         if ( batchRequest == null )
288         {
289             throw new XmlPullParserException( I18n.err( I18n.ERR_03040 ), container.getParser(), null );
290         }
291 
292         return ( ( batchRequest.getProcessing() == Processing.PARALLEL ) && ( batchRequest.getResponseOrder() == ResponseOrder.UNORDERED ) );
293     }
294 
295 
296     /**
297      * XML Pretty Printer XSLT Transformation
298      * 
299      * @param document the Dom4j Document
300      * @return the transformed document
301      */
302     public static Document styleDocument( Document document )
303     {
304         // load the transformer using JAXP
305         TransformerFactory factory = TransformerFactory.newInstance();
306         Transformer transformer = null;
307 
308         try
309         {
310             transformer = factory.newTransformer( new StreamSource( ParserUtils.class
311                 .getResourceAsStream( "DSMLv2.xslt" ) ) );
312         }
313         catch ( TransformerConfigurationException e1 )
314         {
315             LOG.warn( "Failed to create the XSLT transformer", e1 );
316             // return original document
317             return document;
318         }
319 
320         // now lets style the given document
321         DocumentSource source = new DocumentSource( document );
322         DocumentResult result = new DocumentResult();
323 
324         try
325         {
326             transformer.transform( source, result );
327         }
328         catch ( TransformerException e )
329         {
330             // return original document
331             return document;
332         }
333 
334         // return the transformed document
335         Document transformedDoc = result.getDocument();
336         return transformedDoc;
337     }
338 
339     /**
340      * GrammarAction that reads the SOAP header data
341      */
342     public static final GrammarAction readSoapHeader = new GrammarAction( "Reads SOAP header" )
343     {
344         public void action( Dsmlv2Container container ) throws XmlPullParserException
345         {
346             try
347             {
348                 XmlPullParser xpp = container.getParser();
349                 StringBuilder sb = new StringBuilder();
350 
351                 String startTag = xpp.getText();
352                 sb.append( startTag );
353 
354                 // string '<' and '>'
355                 startTag = startTag.substring( 1, startTag.length() - 1 );
356 
357                 int tagType = -1;
358                 String endTag = "";
359 
360                 // continue parsing till we get to the end tag of SOAP header
361                 // and match the tag values including the namespace
362                 while ( !startTag.equals( endTag ) )
363                 {
364                     tagType = xpp.next();
365                     endTag = xpp.getText();
366                     sb.append( endTag );
367 
368                     if ( tagType == XmlPullParser.END_TAG )
369                     {
370                         // strip '<', '/' and '>'
371                         endTag = endTag.substring( 2, endTag.length() - 1 );
372                     }
373                 }
374 
375                 // change the state to header end
376                 container.setState( Dsmlv2StatesEnum.SOAP_HEADER_END_TAG );
377             }
378             catch ( IOException e )
379             {
380                 e.printStackTrace();
381             }
382 
383         }
384     };
385 
386 }