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.w3c.org/2001/XMLSchema";
66  
67      /** W3C XML Schema Instance URI. */
68      public static final String XML_SCHEMA_INSTANCE_URI = "http://www.w3c.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       * Returns the value of the attribute 'type' of the "XMLSchema-instance' namespace if it exists
93       *
94       * @param xpp the XPP parser to use
95       * @return the value of the attribute 'type' of the "XMLSchema-instance' namespace if it exists
96       */
97      public static String getXsiTypeAttributeValue( XmlPullParser xpp )
98      {
99          String type = null;
100         int nbAttributes = xpp.getAttributeCount();
101 
102         for ( int i = 0; i < nbAttributes; i++ )
103         {
104             // Checking if the attribute 'type' from XML Schema Instance namespace is used.
105             if ( xpp.getAttributeName( i ).equals( "type" )
106                 && xpp.getNamespace( xpp.getAttributePrefix( i ) ).equals( XML_SCHEMA_INSTANCE_URI ) )
107             {
108                 type = xpp.getAttributeValue( i );
109                 break;
110             }
111         }
112 
113         return type;
114     }
115 
116 
117     /**
118      * Tells is the given value is a Base64 binary value
119      * 
120      * @param parser the XPP parser to use
121      * @param attrValue the attribute value
122      * @return true if the value of the current tag is Base64BinaryEncoded, false if not
123      */
124     public static boolean isBase64BinaryValue( XmlPullParser parser, String attrValue )
125     {
126         if ( attrValue == null )
127         {
128             return false;
129         }
130 
131         // We are looking for something that should look like that: "aNameSpace:base64Binary"
132         // We split the String. The first element should be the namespace prefix and the second "base64Binary"
133         String[] splitedString = attrValue.split( ":" );
134 
135         return ( splitedString.length == 2 ) && ( XML_SCHEMA_URI.equals( parser.getNamespace( splitedString[0] ) ) )
136             && ( BASE64BINARY.equals( splitedString[1] ) );
137     }
138 
139 
140     /**
141      * Indicates if the value needs to be encoded as Base64
142      *
143      * @param value the value to check
144      * @return true if the value needs to be encoded as Base64
145      */
146     public static boolean needsBase64Encoding( Object value )
147     {
148         if ( value instanceof StringValue )
149         {
150             return false;
151         }
152         else if ( value instanceof BinaryValue )
153         {
154             return false;
155         }
156         else if ( value instanceof byte[] )
157         {
158             return true;
159         }
160         else if ( value instanceof String )
161         {
162             return !LdifUtils.isLDIFSafe( ( String ) value );
163         }
164 
165         return true;
166     }
167 
168 
169     /**
170      * Encodes the value as a Base64 String
171      *
172      * @param value the value to encode
173      * @return the value encoded as a Base64 String
174      */
175     public static String base64Encode( Object value )
176     {
177         if ( value instanceof byte[] )
178         {
179             return new String( Base64.encode( ( byte[] ) value ) );
180         }
181         else if ( value instanceof String )
182         {
183             return new String( Base64.encode( Strings.getBytesUtf8( ( String ) value ) ) );
184         }
185 
186         return "";
187     }
188 
189 
190     /**
191      * Parses and verify the parsed value of the requestID
192      * 
193      * @param attributeValue the value of the attribute
194      * @param xpp the XmlPullParser
195      * @return the int value of the resquestID
196      * @throws XmlPullParserException if RequestID isn't an Integer and if requestID is below 0
197      */
198     public static int parseAndVerifyRequestID( String attributeValue, XmlPullParser xpp ) throws XmlPullParserException
199     {
200         try
201         {
202             int requestID = Integer.parseInt( attributeValue );
203 
204             if ( requestID < 0 )
205             {
206                 throw new XmlPullParserException( I18n.err( I18n.ERR_03038, requestID ), xpp, null );
207             }
208 
209             return requestID;
210         }
211         catch ( NumberFormatException e )
212         {
213             throw new XmlPullParserException( I18n.err( I18n.ERR_03039 ), xpp, null );
214         }
215     }
216 
217 
218     /**
219      * Adds Controls to the given Element.
220      *
221      * @param element the element to add the Controls to
222      * @param controls a List of Controls
223      */
224     public static void addControls( LdapApiService codec, Element element, Collection<Control> controls )
225     {
226         if ( controls != null )
227         {
228             for ( Control control : controls )
229             {
230                 Element controlElement = element.addElement( "control" );
231 
232                 if ( control.getOid() != null )
233                 {
234                     controlElement.addAttribute( "type", control.getOid() );
235                 }
236 
237                 if ( control.isCritical() )
238                 {
239                     controlElement.addAttribute( "criticality", "true" );
240                 }
241 
242                 byte[] value;
243 
244                 if ( control instanceof CodecControl<?> )
245                 {
246                     value = ( ( org.apache.directory.api.ldap.codec.api.CodecControl<?> ) control ).getValue();
247                 }
248                 else
249                 {
250                     value = codec.newControl( control ).getValue();
251                 }
252 
253                 if ( value != null )
254                 {
255                     if ( ParserUtils.needsBase64Encoding( value ) )
256                     {
257                         element.getDocument().getRootElement().add( XSD_NAMESPACE );
258                         element.getDocument().getRootElement().add( XSI_NAMESPACE );
259 
260                         Element valueElement = controlElement.addElement( "controlValue" ).addText(
261                             ParserUtils.base64Encode( value ) );
262                         valueElement.addAttribute( new QName( "type", XSI_NAMESPACE ), ParserUtils.XSD + ":"
263                             + ParserUtils.BASE64BINARY );
264                     }
265                     else
266                     {
267                         controlElement.addElement( "controlValue" ).setText( Arrays.toString( value ) );
268                     }
269                 }
270             }
271         }
272     }
273 
274 
275     /**
276      * Indicates if a request ID is needed.
277      *
278      * @param container the associated container
279      * @return true if a request ID is needed (ie Processing=Parallel and ResponseOrder=Unordered)
280      * @throws XmlPullParserException if the batch request has not been parsed yet
281      */
282     public static boolean isRequestIdNeeded( Dsmlv2Container container ) throws XmlPullParserException
283     {
284         BatchRequestDsml batchRequest = container.getBatchRequest();
285 
286         if ( batchRequest == null )
287         {
288             throw new XmlPullParserException( I18n.err( I18n.ERR_03040 ), container.getParser(), null );
289         }
290 
291         return ( ( batchRequest.getProcessing() == Processing.PARALLEL ) && ( batchRequest.getResponseOrder() == ResponseOrder.UNORDERED ) );
292     }
293 
294 
295     /**
296      * XML Pretty Printer XSLT Transformation
297      * 
298      * @param document the Dom4j Document
299      * @return the transformed document
300      */
301     public static Document styleDocument( Document document )
302     {
303         // load the transformer using JAXP
304         TransformerFactory factory = TransformerFactory.newInstance();
305         Transformer transformer = null;
306 
307         try
308         {
309             transformer = factory.newTransformer( new StreamSource( ParserUtils.class
310                 .getResourceAsStream( "DSMLv2.xslt" ) ) );
311         }
312         catch ( TransformerConfigurationException e1 )
313         {
314             LOG.warn( "Failed to create the XSLT transformer", e1 );
315             // return original document
316             return document;
317         }
318 
319         // now lets style the given document
320         DocumentSource source = new DocumentSource( document );
321         DocumentResult result = new DocumentResult();
322 
323         try
324         {
325             transformer.transform( source, result );
326         }
327         catch ( TransformerException e )
328         {
329             // return original document
330             return document;
331         }
332 
333         // return the transformed document
334         Document transformedDoc = result.getDocument();
335         return transformedDoc;
336     }
337 
338     /**
339      * GrammarAction that reads the SOAP header data
340      */
341     public static final GrammarAction readSoapHeader = new GrammarAction( "Reads SOAP header" )
342     {
343         public void action( Dsmlv2Container container ) throws XmlPullParserException
344         {
345             try
346             {
347                 XmlPullParser xpp = container.getParser();
348                 StringBuilder sb = new StringBuilder();
349 
350                 String startTag = xpp.getText();
351                 sb.append( startTag );
352 
353                 // string '<' and '>'
354                 startTag = startTag.substring( 1, startTag.length() - 1 );
355 
356                 int tagType = -1;
357                 String endTag = "";
358 
359                 // continue parsing till we get to the end tag of SOAP header
360                 // and match the tag values including the namespace
361                 while ( !startTag.equals( endTag ) )
362                 {
363                     tagType = xpp.next();
364                     endTag = xpp.getText();
365                     sb.append( endTag );
366 
367                     if ( tagType == XmlPullParser.END_TAG )
368                     {
369                         // strip '<', '/' and '>'
370                         endTag = endTag.substring( 2, endTag.length() - 1 );
371                     }
372                 }
373 
374                 // change the state to header end
375                 container.setState( Dsmlv2StatesEnum.SOAP_HEADER_END_TAG );
376             }
377             catch ( IOException e )
378             {
379                 e.printStackTrace();
380             }
381 
382         }
383     };
384 
385 }