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.response.BatchResponseDsml;
033import org.apache.directory.api.dsmlv2.response.Dsmlv2ResponseGrammar;
034import org.apache.directory.api.i18n.I18n;
035import org.apache.directory.api.ldap.codec.api.LdapApiService;
036import org.apache.directory.api.ldap.model.message.Response;
037import org.apache.directory.api.util.Strings;
038import org.xmlpull.v1.XmlPullParser;
039import org.xmlpull.v1.XmlPullParserException;
040import org.xmlpull.v1.XmlPullParserFactory;
041
042
043/**
044 * This class represents the DSMLv2 Parser.
045 * It can be used to parse a DSMLv2 Response input.
046 *
047 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
048 */
049public class Dsmlv2ResponseParser
050{
051    /** The associated DSMLv2 container */
052    private Dsmlv2Container container;
053
054
055    /**
056     * Creates a new instance of Dsmlv2ResponseParser.
057     *
058     * @param codec The Ldap Service to use
059     * @throws XmlPullParserException if an error occurs while the initialization of the parser
060     */
061    public Dsmlv2ResponseParser( LdapApiService codec ) throws XmlPullParserException
062    {
063        this.container = new Dsmlv2Container( codec );
064
065        this.container.setGrammar( Dsmlv2ResponseGrammar.getInstance() );
066
067        XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
068        factory.setNamespaceAware( true );
069        XmlPullParser xpp = factory.newPullParser();
070
071        container.setParser( xpp );
072    }
073
074
075    /**
076     * Sets the input string the parser is going to parse
077     *
078     * @param str the string the parser is going to parse
079     * @throws XmlPullParserException if an error occurs in the parser
080     */
081    public void setInput( String str ) throws XmlPullParserException
082    {
083        container.getParser().setInput( new StringReader( str ) );
084    }
085
086
087    /**
088     * Sets the input file the parser is going to parse. Default charset is used.
089     *
090     * @param fileName the name of the file
091     * @throws IOException if the file does not exist
092     * @throws XmlPullParserException if an error occurs in the parser
093     */
094    public void setInputFile( String fileName ) throws IOException, XmlPullParserException
095    {
096        try ( Reader reader = new InputStreamReader( Files.newInputStream( Paths.get( fileName ) ), 
097            Charset.defaultCharset() ) )
098        {
099            container.getParser().setInput( reader );
100        }
101    }
102
103
104    /**
105     * Sets the input stream the parser is going to process
106     *
107     * @param inputStream contains a raw byte input stream of possibly unknown encoding (when inputEncoding is null)
108     * @param inputEncoding if not null it MUST be used as encoding for inputStream
109     * @throws XmlPullParserException if an error occurs in the parser
110     */
111    public void setInput( InputStream inputStream, String inputEncoding ) throws XmlPullParserException
112    {
113        container.getParser().setInput( inputStream, inputEncoding );
114    }
115
116
117    /**
118     * Launches the parsing on the input
119     * 
120     * @throws XmlPullParserException when an unrecoverable error occurs
121     * @throws IOException when an IO exception occurs
122     */
123    public void parse() throws XmlPullParserException, IOException
124    {
125        Dsmlv2ResponseGrammar grammar = Dsmlv2ResponseGrammar.getInstance();
126
127        grammar.executeAction( container );
128    }
129
130
131    /**
132     * Launches the parsing of the Batch Response only
133     *
134     * @throws XmlPullParserException if an error occurs in the parser
135     */
136    public void parseBatchResponse() throws XmlPullParserException
137    {
138        XmlPullParser xpp = container.getParser();
139
140        int eventType = xpp.getEventType();
141        
142        do
143        {
144            switch ( eventType )
145            {
146                case XmlPullParser.START_DOCUMENT :
147                    container.setState( Dsmlv2StatesEnum.INIT_GRAMMAR_STATE );
148                    break;
149
150                case XmlPullParser.END_DOCUMENT :
151                    container.setState( Dsmlv2StatesEnum.GRAMMAR_END );
152                    break;
153
154                case XmlPullParser.START_TAG :
155                    processTag( container, Tag.START );
156                    break;
157
158                case XmlPullParser.END_TAG :
159                    processTag( container, Tag.END );
160                    break;
161
162                default:
163                    break;
164            }
165            
166            try
167            {
168                eventType = xpp.next();
169            }
170            catch ( IOException ioe )
171            {
172                throw new XmlPullParserException( I18n.err( I18n.ERR_03019_IO_EXCEPTION_OCCURED, ioe.getLocalizedMessage() ), xpp, ioe );
173            }
174        }
175        while ( container.getState() != Dsmlv2StatesEnum.BATCH_RESPONSE_LOOP );
176    }
177
178
179    /**
180     * Processes the task required in the grammar to the given tag type
181     *
182     * @param container the DSML container
183     * @param tagType the tag type
184     * @throws XmlPullParserException when an error occurs during the parsing
185     */
186    private static void processTag( Dsmlv2Container container, int tagType ) throws XmlPullParserException
187    {
188        XmlPullParser xpp = container.getParser();
189
190        String tagName = Strings.lowerCase( xpp.getName() );
191
192        GrammarTransition transition = container.getTransition( container.getState(), new Tag( tagName, tagType ) );
193
194        if ( transition != null )
195        {
196            container.setState( transition.getNextState() );
197
198            if ( transition.hasAction() )
199            {
200                transition.getAction().action( container );
201            }
202        }
203        else
204        {
205            throw new XmlPullParserException( I18n.err( I18n.ERR_03036_MISSING_TAG, new Tag( tagName, tagType ) ), xpp, null );
206        }
207    }
208
209
210    /**
211     * Gets the Batch Response or null if the it has not been parsed yet
212     *
213     * @return the Batch Response or null if the it has not been parsed yet
214     */
215    public BatchResponseDsml getBatchResponse()
216    {
217        return container.getBatchResponse();
218    }
219
220
221    /**
222     * Returns the next Request or null if there's no more request
223     * @return the next Request or null if there's no more request
224     * @throws XmlPullParserException when an error occurs during the parsing
225     */
226    public DsmlDecorator<? extends Response> getNextResponse() throws XmlPullParserException
227    {
228        if ( container.getBatchResponse() == null )
229        {
230            parseBatchResponse();
231        }
232
233        XmlPullParser xpp = container.getParser();
234
235        int eventType = xpp.getEventType();
236        do
237        {
238            while ( eventType == XmlPullParser.TEXT )
239            {
240                try
241                {
242                    xpp.next();
243                }
244                catch ( IOException ioe )
245                {
246                    throw new XmlPullParserException( I18n.err( I18n.ERR_03019_IO_EXCEPTION_OCCURED, ioe.getLocalizedMessage() ), xpp, ioe );
247                }
248                eventType = xpp.getEventType();
249            }
250
251            switch ( eventType )
252            {
253                case XmlPullParser.START_DOCUMENT :
254                    container.setState( Dsmlv2StatesEnum.INIT_GRAMMAR_STATE );
255                    break;
256
257                case XmlPullParser.END_DOCUMENT :
258                    container.setState( Dsmlv2StatesEnum.GRAMMAR_END );
259                    return null;
260
261                case XmlPullParser.START_TAG :
262                    processTag( container, Tag.START );
263                    break;
264
265                case XmlPullParser.END_TAG :
266                    processTag( container, Tag.END );
267                    break;
268
269                default:
270                    break;
271            }
272            
273            try
274            {
275                eventType = xpp.next();
276            }
277            catch ( IOException ioe )
278            {
279                throw new XmlPullParserException( I18n.err( I18n.ERR_03019_IO_EXCEPTION_OCCURED, ioe.getLocalizedMessage() ), xpp, ioe );
280            }
281        }
282        while ( container.getState() != Dsmlv2StatesEnum.BATCH_RESPONSE_LOOP );
283
284        return container.getBatchResponse().getCurrentResponse();
285    }
286
287
288    /**
289     * Parses all the responses
290     *
291     * @throws XmlPullParserException when an error occurs during the parsing
292     */
293    public void parseAllResponses() throws XmlPullParserException
294    {
295        while ( getNextResponse() != null )
296        {
297            continue;
298        }
299    }
300}