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}