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.io.InputStream;
025import java.io.InputStreamReader;
026import java.io.Reader;
027import java.io.StringReader;
028import java.nio.charset.Charset;
029import java.nio.file.Files;
030import java.nio.file.Paths;
031
032import org.apache.directory.api.dsmlv2.request.BatchRequestDsml;
033import org.apache.directory.api.dsmlv2.request.Dsmlv2Grammar;
034import org.apache.directory.api.i18n.I18n;
035import org.apache.directory.api.ldap.model.message.Request;
036import org.apache.directory.api.util.Strings;
037import org.xmlpull.v1.XmlPullParser;
038import org.xmlpull.v1.XmlPullParserException;
039import org.xmlpull.v1.XmlPullParserFactory;
040
041
042/**
043 * This class represents the DSMLv2 Parser.
044 * It can be used to parse a plain DSMLv2 Request input document or the one inside a SOAP envelop.
045 *
046 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
047 */
048public class Dsmlv2Parser
049{
050    /** The associated DSMLv2 container */
051    private Dsmlv2Container container;
052
053    /**
054     * flag to indicate if the batch request should maintain a list of all the
055     * operation request objects present in the DSML document. Default is true
056     */
057    private boolean storeMsgInBatchReq = true;
058
059    /** The thread safe DSMLv2 Grammar */
060    private Dsmlv2Grammar grammar;
061
062
063    /**
064     * Creates a new instance of Dsmlv2Parser.
065     *
066     * @throws XmlPullParserException if an error occurs during the initialization of the parser
067     */
068    public Dsmlv2Parser() throws XmlPullParserException
069    {
070        this( true );
071    }
072
073
074    /**
075     * Creates a new instance of Dsmlv2Parser.
076     *
077     * @param storeMsgInBatchReq flag to set if the parsed requests should b stored
078     * @throws XmlPullParserException if an error occurs during the initialization of the parser
079     */
080    public Dsmlv2Parser( boolean storeMsgInBatchReq ) throws XmlPullParserException
081    {
082        this.storeMsgInBatchReq = storeMsgInBatchReq;
083
084        this.grammar = new Dsmlv2Grammar();
085        this.container = new Dsmlv2Container( grammar.getLdapCodecService() );
086
087        this.container.setGrammar( grammar );
088
089        XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
090        factory.setNamespaceAware( true );
091        XmlPullParser xpp = factory.newPullParser();
092
093        container.setParser( xpp );
094    }
095
096
097    /**
098     * Creates a new instance of Dsmlv2Parser.
099     *
100     * @param grammar The grammar in use
101     * @throws XmlPullParserException if an error occurs during the initialization of the parser
102     */
103    public Dsmlv2Parser( Dsmlv2Grammar grammar ) throws XmlPullParserException
104    {
105        this.container = new Dsmlv2Container( grammar.getLdapCodecService() );
106        this.container.setGrammar( grammar );
107        this.grammar = grammar;
108
109        XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
110        factory.setNamespaceAware( true );
111        XmlPullParser xpp = factory.newPullParser();
112
113        container.setParser( xpp );
114    }
115
116
117    /**
118     * Sets the input file the parser is going to parse. Default charset is used.
119     *
120     * @param fileName the name of the file
121     * @throws FileNotFoundException if the file does not exist
122     * @throws XmlPullParserException if an error occurs in the parser
123     */
124    public void setInputFile( String fileName ) throws IOException, XmlPullParserException
125    {
126        try ( Reader reader = new InputStreamReader( Files.newInputStream( Paths.get( ( fileName ) ) ), 
127            Charset.defaultCharset() ) )
128        {
129            container.getParser().setInput( reader );
130        }
131    }
132
133
134    /**
135     * Sets the input stream the parser is going to process
136     *
137     * @param inputStream contains a raw byte input stream of possibly unknown encoding (when inputEncoding is null)
138     * @param inputEncoding if not null it MUST be used as encoding for inputStream
139     * @throws XmlPullParserException if an error occurs in the parser
140     */
141    public void setInput( InputStream inputStream, String inputEncoding ) throws XmlPullParserException
142    {
143        container.getParser().setInput( inputStream, inputEncoding );
144    }
145
146
147    /**
148     * Sets the input string the parser is going to parse
149     *
150     * @param str the string the parser is going to parse
151     * @throws XmlPullParserException if an error occurs in the parser
152     */
153    public void setInput( String str ) throws XmlPullParserException
154    {
155        container.getParser().setInput( new StringReader( str ) );
156    }
157
158
159    /**
160     * Launches the parsing on the input
161     * This method will parse the whole DSML document, without considering the flag storeMsgInBatchReq
162     * @throws XmlPullParserException when an unrecoverable error occurs
163     * @throws IOException when an IO execption occurs
164     */
165    public void parse() throws XmlPullParserException, IOException
166    {
167        grammar.executeAction( container );
168    }
169
170
171    /**
172     * Launches the parsing of the Batch Request only
173     *
174     * @throws XmlPullParserException if an error occurs in the parser
175     */
176    public void parseBatchRequest() throws XmlPullParserException
177    {
178        XmlPullParser xpp = container.getParser();
179
180        int eventType = xpp.getEventType();
181
182        do
183        {
184            switch ( eventType )
185            {
186                case XmlPullParser.START_DOCUMENT:
187                    container.setState( Dsmlv2StatesEnum.INIT_GRAMMAR_STATE );
188                    break;
189
190                case XmlPullParser.END_DOCUMENT:
191                    container.setState( Dsmlv2StatesEnum.GRAMMAR_END );
192                    break;
193
194                case XmlPullParser.START_TAG:
195                    processTag( container, Tag.START );
196                    break;
197
198                case XmlPullParser.END_TAG:
199                    processTag( container, Tag.END );
200                    break;
201
202                default:
203                    break;
204            }
205
206            try
207            {
208                eventType = xpp.next();
209            }
210            catch ( IOException ioe )
211            {
212                throw new XmlPullParserException( I18n.err( I18n.ERR_03037, ioe.getLocalizedMessage() ), xpp, ioe );
213            }
214        }
215        while ( container.getState() != Dsmlv2StatesEnum.BATCHREQUEST_START_TAG );
216
217        BatchRequestDsml br = container.getBatchRequest();
218
219        if ( br != null )
220        {
221            br.setStoreReq( storeMsgInBatchReq );
222        }
223    }
224
225
226    /**
227     * Processes the task required in the grammar to the given tag type
228     *
229     * @param container the DSML container
230     * @param tagType the tag type
231     * @throws XmlPullParserException when an error occurs during the parsing
232     */
233    private static void processTag( Dsmlv2Container container, int tagType ) throws XmlPullParserException
234    {
235        XmlPullParser xpp = container.getParser();
236
237        String tagName = Strings.lowerCase( xpp.getName() );
238
239        GrammarTransition transition = container.getTransition( container.getState(), new Tag( tagName, tagType ) );
240
241        if ( transition != null )
242        {
243            container.setState( transition.getNextState() );
244
245            if ( transition.hasAction() )
246            {
247                transition.getAction().action( container );
248            }
249        }
250        else
251        {
252            throw new XmlPullParserException( I18n.err( I18n.ERR_03036, new Tag( tagName, tagType ) ), xpp, null );
253        }
254    }
255
256
257    /**
258     * Gets the Batch Request or null if the it has not been parsed yet
259     *
260     * @return the Batch Request or null if the it has not been parsed yet
261     */
262    public BatchRequestDsml getBatchRequest()
263    {
264        return container.getBatchRequest();
265    }
266
267
268    /**
269     * Gets the next Request or null if there's no more request
270     * @return the next Request or null if there's no more request
271     * @throws XmlPullParserException when an error occurs during the parsing
272     */
273    public DsmlDecorator<? extends Request> getNextRequest() throws XmlPullParserException
274    {
275        if ( container.getBatchRequest() == null )
276        {
277            parseBatchRequest();
278        }
279
280        XmlPullParser xpp = container.getParser();
281
282        int eventType = xpp.getEventType();
283        do
284        {
285            while ( eventType == XmlPullParser.TEXT )
286            {
287                try
288                {
289                    xpp.next();
290                }
291                catch ( IOException ioe )
292                {
293                    throw new XmlPullParserException( I18n.err( I18n.ERR_03037, ioe.getLocalizedMessage() ), xpp, ioe );
294                }
295                eventType = xpp.getEventType();
296            }
297
298            switch ( eventType )
299            {
300                case XmlPullParser.START_DOCUMENT:
301                    container.setState( Dsmlv2StatesEnum.INIT_GRAMMAR_STATE );
302                    break;
303
304                case XmlPullParser.END_DOCUMENT:
305                    container.setState( Dsmlv2StatesEnum.GRAMMAR_END );
306                    return null;
307
308                case XmlPullParser.START_TAG:
309                    processTag( container, Tag.START );
310                    break;
311
312                case XmlPullParser.END_TAG:
313                    processTag( container, Tag.END );
314                    break;
315
316                default:
317                    break;
318            }
319
320            try
321            {
322                eventType = xpp.next();
323            }
324            catch ( IOException ioe )
325            {
326                throw new XmlPullParserException( I18n.err( I18n.ERR_03037, ioe.getLocalizedMessage() ), xpp, ioe );
327            }
328        }
329        while ( container.getState() != Dsmlv2StatesEnum.BATCHREQUEST_LOOP );
330
331        return container.getBatchRequest().getCurrentRequest();
332    }
333
334
335    /**
336     * Parses all the requests
337     *
338     * @throws XmlPullParserException when an error occurs during the parsing
339     */
340    public void parseAllRequests() throws XmlPullParserException
341    {
342        while ( getNextRequest() != null )
343        {
344            continue;
345        }
346    }
347}