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