001/* 002 * Licensed to the Apache Software Foundation (ASF) under one 003 * or more contributor license agreements. See the NOTICE file 004 * distributed with this work for additional information 005 * regarding copyright ownership. The ASF licenses this file 006 * to you under the Apache License, Version 2.0 (the 007 * "License"); you may not use this file except in compliance 008 * with the License. You may obtain a copy of the License at 009 * 010 * http://www.apache.org/licenses/LICENSE-2.0 011 * 012 * Unless required by applicable law or agreed to in writing, 013 * software distributed under the License is distributed on an 014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 * KIND, either express or implied. See the License for the 016 * specific language governing permissions and limitations 017 * under the License. 018 * 019 */ 020package org.apache.directory.api.dsmlv2; 021 022 023import java.io.IOException; 024import java.util.Arrays; 025import java.util.Collection; 026 027import javax.xml.transform.Transformer; 028import javax.xml.transform.TransformerConfigurationException; 029import javax.xml.transform.TransformerException; 030import javax.xml.transform.TransformerFactory; 031import javax.xml.transform.stream.StreamSource; 032 033import org.apache.directory.api.dsmlv2.request.BatchRequestDsml; 034import org.apache.directory.api.dsmlv2.request.BatchRequestDsml.Processing; 035import org.apache.directory.api.dsmlv2.request.BatchRequestDsml.ResponseOrder; 036import org.apache.directory.api.i18n.I18n; 037import org.apache.directory.api.ldap.codec.api.CodecControl; 038import org.apache.directory.api.ldap.codec.api.LdapApiService; 039import org.apache.directory.api.ldap.model.entry.BinaryValue; 040import org.apache.directory.api.ldap.model.entry.StringValue; 041import org.apache.directory.api.ldap.model.ldif.LdifUtils; 042import org.apache.directory.api.ldap.model.message.Control; 043import org.apache.directory.api.util.Base64; 044import org.apache.directory.api.util.Strings; 045import org.dom4j.Document; 046import org.dom4j.Element; 047import org.dom4j.Namespace; 048import org.dom4j.QName; 049import org.dom4j.io.DocumentResult; 050import org.dom4j.io.DocumentSource; 051import org.slf4j.Logger; 052import org.slf4j.LoggerFactory; 053import org.xmlpull.v1.XmlPullParser; 054import org.xmlpull.v1.XmlPullParserException; 055 056 057/** 058 * This class is a Helper class for the DSML Parser 059 * 060 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 061 */ 062public final class ParserUtils 063{ 064 /** W3C XML Schema URI. */ 065 public static final String XML_SCHEMA_URI = "http://www.w3c.org/2001/XMLSchema"; 066 067 /** W3C XML Schema Instance URI. */ 068 public static final String XML_SCHEMA_INSTANCE_URI = "http://www.w3c.org/2001/XMLSchema-instance"; 069 070 /** Base-64 identifier. */ 071 public static final String BASE64BINARY = "base64Binary"; 072 073 /** XSI namespace prefix. */ 074 public static final String XSI = "xsi"; 075 076 /** XSD namespace prefix. */ 077 public static final String XSD = "xsd"; 078 079 /** The DSML namespace */ 080 public static final Namespace DSML_NAMESPACE = new Namespace( null, "urn:oasis:names:tc:DSML:2:0:core" ); 081 082 /** The XSD namespace */ 083 public static final Namespace XSD_NAMESPACE = new Namespace( XSD, XML_SCHEMA_URI ); 084 085 /** The XSI namespace */ 086 public static final Namespace XSI_NAMESPACE = new Namespace( XSI, XML_SCHEMA_INSTANCE_URI ); 087 088 /** A logger for this class */ 089 private static final Logger LOG = LoggerFactory.getLogger( ParserUtils.class ); 090 091 /** 092 * Returns the value of the attribute 'type' of the "XMLSchema-instance' namespace if it exists 093 * 094 * @param xpp the XPP parser to use 095 * @return the value of the attribute 'type' of the "XMLSchema-instance' namespace if it exists 096 */ 097 public static String getXsiTypeAttributeValue( XmlPullParser xpp ) 098 { 099 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}