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 * https://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.util.Arrays; 024import java.util.Collection; 025 026import javax.xml.transform.Transformer; 027import javax.xml.transform.TransformerConfigurationException; 028import javax.xml.transform.TransformerException; 029import javax.xml.transform.TransformerFactory; 030import javax.xml.transform.stream.StreamSource; 031 032import org.apache.directory.api.asn1.util.Asn1Buffer; 033import org.apache.directory.api.dsmlv2.actions.ReadSoapHeader; 034import org.apache.directory.api.dsmlv2.request.BatchRequestDsml; 035import org.apache.directory.api.dsmlv2.request.BatchRequestDsml.Processing; 036import org.apache.directory.api.dsmlv2.request.BatchRequestDsml.ResponseOrder; 037import org.apache.directory.api.i18n.I18n; 038import org.apache.directory.api.ldap.codec.api.LdapApiService; 039import org.apache.directory.api.ldap.model.entry.Value; 040import org.apache.directory.api.ldap.model.ldif.LdifUtils; 041import org.apache.directory.api.ldap.model.message.Control; 042import org.apache.directory.api.util.Base64; 043import org.apache.directory.api.util.Strings; 044import org.dom4j.Document; 045import org.dom4j.Element; 046import org.dom4j.Namespace; 047import org.dom4j.QName; 048import org.dom4j.io.DocumentResult; 049import org.dom4j.io.DocumentSource; 050import org.slf4j.Logger; 051import org.slf4j.LoggerFactory; 052import org.xmlpull.v1.XmlPullParser; 053import org.xmlpull.v1.XmlPullParserException; 054 055 056/** 057 * This class is a Helper class for the DSML Parser 058 * 059 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> 060 */ 061public final class ParserUtils 062{ 063 /** W3C XML Schema URI. */ 064 public static final String XML_SCHEMA_URI = "http://www.w3.org/2001/XMLSchema"; 065 066 /** W3C XML Schema Instance URI. */ 067 public static final String XML_SCHEMA_INSTANCE_URI = "http://www.w3.org/2001/XMLSchema-instance"; 068 069 /** Base-64 identifier. */ 070 public static final String BASE64BINARY = "base64Binary"; 071 072 /** XSI namespace prefix. */ 073 public static final String XSI = "xsi"; 074 075 /** XSD namespace prefix. */ 076 public static final String XSD = "xsd"; 077 078 /** The DSML namespace */ 079 public static final Namespace DSML_NAMESPACE = new Namespace( null, "urn:oasis:names:tc:DSML:2:0:core" ); 080 081 /** The XSD namespace */ 082 public static final Namespace XSD_NAMESPACE = new Namespace( XSD, XML_SCHEMA_URI ); 083 084 /** The XSI namespace */ 085 public static final Namespace XSI_NAMESPACE = new Namespace( XSI, XML_SCHEMA_INSTANCE_URI ); 086 087 /** A logger for this class */ 088 private static final Logger LOG = LoggerFactory.getLogger( ParserUtils.class ); 089 090 /** 091 * GrammarAction that reads the SOAP header data 092 */ 093 public static final GrammarAction READ_SOAP_HEADER = new ReadSoapHeader(); 094 095 private ParserUtils() 096 { 097 } 098 099 100 /** 101 * Returns the value of the attribute 'type' of the "XMLSchema-instance' namespace if it exists 102 * 103 * @param xpp the XPP parser to use 104 * @return the value of the attribute 'type' of the "XMLSchema-instance' namespace if it exists 105 */ 106 public static String getXsiTypeAttributeValue( XmlPullParser xpp ) 107 { 108 String type = null; 109 int nbAttributes = xpp.getAttributeCount(); 110 111 for ( int i = 0; i < nbAttributes; i++ ) 112 { 113 // Checking if the attribute 'type' from XML Schema Instance namespace is used. 114 if ( "type".equals( xpp.getAttributeName( i ) ) 115 && xpp.getNamespace( xpp.getAttributePrefix( i ) ).equals( XML_SCHEMA_INSTANCE_URI ) ) 116 { 117 type = xpp.getAttributeValue( i ); 118 break; 119 } 120 } 121 122 return type; 123 } 124 125 126 /** 127 * Tells is the given value is a Base64 binary value 128 * 129 * @param parser the XPP parser to use 130 * @param attrValue the attribute value 131 * @return true if the value of the current tag is Base64BinaryEncoded, false if not 132 */ 133 public static boolean isBase64BinaryValue( XmlPullParser parser, String attrValue ) 134 { 135 if ( attrValue == null ) 136 { 137 return false; 138 } 139 140 // We are looking for something that should look like that: "aNameSpace:base64Binary" 141 // We split the String. The first element should be the namespace prefix and the second "base64Binary" 142 String[] splitedString = attrValue.split( ":" ); 143 144 return ( splitedString.length == 2 ) && ( XML_SCHEMA_URI.equals( parser.getNamespace( splitedString[0] ) ) ) 145 && ( BASE64BINARY.equals( splitedString[1] ) ); 146 } 147 148 149 /** 150 * Indicates if the value needs to be encoded as Base64 151 * 152 * @param value the value to check 153 * @return true if the value needs to be encoded as Base64 154 */ 155 public static boolean needsBase64Encoding( Object value ) 156 { 157 if ( value instanceof Value ) 158 { 159 return false; 160 } 161 else if ( value instanceof byte[] ) 162 { 163 return true; 164 } 165 else if ( value instanceof String ) 166 { 167 return !LdifUtils.isLDIFSafe( ( String ) value ); 168 } 169 170 return true; 171 } 172 173 174 /** 175 * Encodes the value as a Base64 String 176 * 177 * @param value the value to encode 178 * @return the value encoded as a Base64 String 179 */ 180 public static String base64Encode( Object value ) 181 { 182 if ( value instanceof byte[] ) 183 { 184 return new String( Base64.encode( ( byte[] ) value ) ); 185 } 186 else if ( value instanceof String ) 187 { 188 return new String( Base64.encode( Strings.getBytesUtf8( ( String ) value ) ) ); 189 } 190 191 return ""; 192 } 193 194 195 /** 196 * Parses and verify the parsed value of the requestID 197 * 198 * @param attributeValue the value of the attribute 199 * @param xpp the XmlPullParser 200 * @return the int value of the resquestID 201 * @throws XmlPullParserException if RequestID isn't an Integer and if requestID is below 0 202 */ 203 public static int parseAndVerifyRequestID( String attributeValue, XmlPullParser xpp ) throws XmlPullParserException 204 { 205 try 206 { 207 int requestID = Integer.parseInt( attributeValue ); 208 209 if ( requestID < 0 ) 210 { 211 throw new XmlPullParserException( I18n.err( I18n.ERR_03016_BELOW_0_REQUEST_ID, requestID ), xpp, null ); 212 } 213 214 return requestID; 215 } 216 catch ( NumberFormatException nfe ) 217 { 218 throw new XmlPullParserException( I18n.err( I18n.ERR_03012_REQUEST_ID_NOT_INTEGER ), xpp, nfe ); 219 } 220 } 221 222 223 /** 224 * Adds Controls to the given Element. 225 * 226 * @param codec The LDAP Service to use 227 * @param element the element to add the Controls to 228 * @param controls a List of Controls 229 * @param isRequest A flag set to <tt>true</tt> if teh LDapMessage is a request 230 */ 231 public static void addControls( LdapApiService codec, Element element, Collection<Control> controls, boolean isRequest ) 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 Asn1Buffer asn1Buffer = new Asn1Buffer(); 250 251 if ( isRequest ) 252 { 253 codec.getRequestControlFactories().get( control.getOid() ).encodeValue( asn1Buffer, control ); 254 } 255 else 256 { 257 codec.getResponseControlFactories().get( control.getOid() ).encodeValue( asn1Buffer, control ); 258 } 259 260 byte[] value = asn1Buffer.getBytes().array(); 261 262 if ( value != null ) 263 { 264 if ( ParserUtils.needsBase64Encoding( value ) ) 265 { 266 element.getDocument().getRootElement().add( XSD_NAMESPACE ); 267 element.getDocument().getRootElement().add( XSI_NAMESPACE ); 268 269 Element valueElement = controlElement.addElement( "controlValue" ).addText( 270 ParserUtils.base64Encode( value ) ); 271 valueElement.addAttribute( new QName( "type", XSI_NAMESPACE ), ParserUtils.XSD + ":" 272 + ParserUtils.BASE64BINARY ); 273 } 274 else 275 { 276 controlElement.addElement( "controlValue" ).setText( Arrays.toString( value ) ); 277 } 278 } 279 } 280 } 281 } 282 283 284 /** 285 * Indicates if a request ID is needed. 286 * 287 * @param container the associated container 288 * @return true if a request ID is needed (ie Processing=Parallel and ResponseOrder=Unordered) 289 * @throws XmlPullParserException if the batch request has not been parsed yet 290 */ 291 public static boolean isRequestIdNeeded( Dsmlv2Container container ) throws XmlPullParserException 292 { 293 BatchRequestDsml batchRequest = container.getBatchRequest(); 294 295 if ( batchRequest == null ) 296 { 297 throw new XmlPullParserException( I18n.err( I18n.ERR_03003_UNABLE_TO_FIND_BATCH_REQUEST ), container.getParser(), null ); 298 } 299 300 return ( batchRequest.getProcessing() == Processing.PARALLEL ) && ( batchRequest.getResponseOrder() == ResponseOrder.UNORDERED ); 301 } 302 303 304 /** 305 * XML Pretty Printer XSLT Transformation 306 * 307 * @param document the Dom4j Document 308 * @return the transformed document 309 */ 310 public static Document styleDocument( Document document ) 311 { 312 // load the transformer using JAXP 313 TransformerFactory factory = TransformerFactory.newInstance(); 314 Transformer transformer = null; 315 316 try 317 { 318 factory.setFeature( javax.xml.XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE ); 319 try 320 { 321 factory.setAttribute( javax.xml.XMLConstants.ACCESS_EXTERNAL_DTD, "" ); 322 factory.setAttribute( javax.xml.XMLConstants.ACCESS_EXTERNAL_STYLESHEET, "" ); 323 } 324 catch ( IllegalArgumentException ex ) 325 { 326 // ignore 327 } 328 transformer = factory.newTransformer( new StreamSource( ParserUtils.class 329 .getResourceAsStream( "/org/apache/directory/shared/dsmlv2/DSMLv2.xslt" ) ) ); 330 } 331 catch ( TransformerConfigurationException e1 ) 332 { 333 if ( LOG.isWarnEnabled() ) 334 { 335 LOG.warn( I18n.msg( I18n.MSG_3000_FAILED_TO_CREATE_XSLT_TRANSFORMER ), e1 ); 336 } 337 338 // return original document 339 return document; 340 } 341 342 // now lets style the given document 343 DocumentSource source = new DocumentSource( document ); 344 DocumentResult result = new DocumentResult(); 345 346 try 347 { 348 transformer.transform( source, result ); 349 } 350 catch ( TransformerException e ) 351 { 352 // return original document 353 return document; 354 } 355 356 // return the transformed document 357 return result.getDocument(); 358 } 359}