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.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.dsmlv2.actions.ReadSoapHeader;
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.w3.org/2001/XMLSchema";
066
067    /** W3C XML Schema Instance URI. */
068    public static final String XML_SCHEMA_INSTANCE_URI = "http://www.w3.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    private ParserUtils()
093    {
094    }
095
096
097    /**
098     * Returns the value of the attribute 'type' of the "XMLSchema-instance' namespace if it exists
099     *
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}