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.ldap.model.schema.parsers;
021
022
023import java.io.StringReader;
024import java.text.ParseException;
025import java.util.List;
026
027import org.apache.directory.api.i18n.I18n;
028import org.apache.directory.api.ldap.model.constants.MetaSchemaConstants;
029import org.apache.directory.api.ldap.model.schema.SchemaObject;
030import org.apache.directory.api.util.Strings;
031import org.slf4j.Logger;
032import org.slf4j.LoggerFactory;
033
034import antlr.RecognitionException;
035import antlr.TokenStreamException;
036import antlr.TokenStreamRecognitionException;
037
038
039/**
040 * Base class of all schema parsers.
041 * 
042 * @param <T> The type of SchemaObject
043 *
044 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
045 */
046public abstract class AbstractSchemaParser<T extends SchemaObject>
047{
048    /** The LoggerFactory used by this class */
049    protected static final Logger LOG = LoggerFactory.getLogger( AbstractSchemaParser.class );
050
051    /** the monitor to use for this parser */
052    protected ParserMonitor monitor = new ParserMonitorAdapter();
053
054    /** the antlr generated parser being wrapped */
055    protected ReusableAntlrSchemaParser parser;
056
057    /** the antlr generated lexer being wrapped */
058    protected ReusableAntlrSchemaLexer lexer;
059
060    /** the schema object sub-type */
061    private Class<T> schemaObjectType;
062
063    /** error code used when schema descritpion is null */
064    private I18n errorCodeOnNull;
065
066    /** error code used on parse error when position is known */
067    private I18n errorCodeOnParseExceptionWithPosition;
068
069    /** error code used on parse error when position is unknown */
070    private I18n errorCodeOnParseException;
071
072
073    /**
074     * Instantiates a new abstract schema parser.
075     * 
076     * @param schemaObjectType The Schema object type
077     * @param errorCodeOnNull error code used when schema element is null
078     * @param errorCodeOnParseExceptionWithPosition error code used on parse error when position is known
079     * @param errorCodeOnParseException error code used on parse error when position is unknown
080     */
081    protected AbstractSchemaParser( Class<T> schemaObjectType, I18n errorCodeOnNull,
082        I18n errorCodeOnParseExceptionWithPosition,
083        I18n errorCodeOnParseException )
084    {
085        this.schemaObjectType = schemaObjectType;
086        this.errorCodeOnNull = errorCodeOnNull;
087        this.errorCodeOnParseExceptionWithPosition = errorCodeOnParseExceptionWithPosition;
088        this.errorCodeOnParseException = errorCodeOnParseException;
089        lexer = new ReusableAntlrSchemaLexer( new StringReader( "" ) );
090        parser = new ReusableAntlrSchemaParser( lexer );
091    }
092
093
094    /**
095     * Initializes the plumbing by creating a pipe and coupling the parser/lexer
096     * pair with it. param spec the specification to be parsed
097     *
098     * @param spec the spec
099     */
100    protected void reset( String spec )
101    {
102        StringReader in = new StringReader( spec );
103        lexer.prepareNextInput( in );
104        parser.resetState();
105    }
106
107
108    /**
109     * Sets the parser monitor.
110     * 
111     * @param parserMonitor the new parser monitor
112     */
113    public void setParserMonitor( ParserMonitor parserMonitor )
114    {
115        this.monitor = parserMonitor;
116        parser.setParserMonitor( parserMonitor );
117    }
118
119
120    /**
121     * Sets the quirks mode. 
122     * 
123     * If enabled the parser accepts non-numeric OIDs and some 
124     * special characters in descriptions.
125     * 
126     * @param enabled the new quirks mode
127     */
128    public void setQuirksMode( boolean enabled )
129    {
130        parser.setQuirksMode( enabled );
131    }
132
133
134    /**
135     * Checks if quirks mode is enabled.
136     * 
137     * @return true, if is quirks mode is enabled
138     */
139    public boolean isQuirksMode()
140    {
141        return parser.isQuirksMode();
142    }
143
144
145    /**
146     * Parse a SchemaObject description and returns back an instance of SchemaObject.
147     * 
148     * @param schemaDescription The SchemaObject description
149     * @return A SchemaObject instance
150     * @throws ParseException If the parsing failed
151     */
152    public synchronized T parse( String schemaDescription ) throws ParseException
153    {
154        LOG.debug( "Parsing a {} : {}", schemaObjectType.getClass().getSimpleName(), schemaDescription );
155
156        if ( schemaDescription == null )
157        {
158            LOG.error( I18n.err( errorCodeOnNull ) );
159            throw new ParseException( "Null", 0 );
160        }
161
162        // reset and initialize the parser / lexer pair
163        reset( schemaDescription );
164
165        try
166        {
167            T schemaObject = doParse();
168            schemaObject.setSpecification( schemaDescription );
169
170            // Update the schemaName
171            updateSchemaName( schemaObject );
172
173            return schemaObject;
174        }
175        catch ( RecognitionException re )
176        {
177            throw wrapRecognitionException( schemaDescription, re );
178        }
179        catch ( TokenStreamRecognitionException tsre )
180        {
181            if ( tsre.recog != null )
182            {
183                throw wrapRecognitionException( schemaDescription, tsre.recog );
184            }
185            else
186            {
187                throw wrapTokenStreamException( schemaDescription, tsre );
188            }
189        }
190        catch ( TokenStreamException tse )
191        {
192            throw wrapTokenStreamException( schemaDescription, tse );
193        }
194    }
195
196
197    private ParseException wrapRecognitionException( String schemaDescription, RecognitionException re )
198    {
199        String msg = I18n.err( errorCodeOnParseExceptionWithPosition, schemaDescription, re.getMessage(),
200            re.getColumn() );
201        LOG.error( msg );
202        ParseException parseException = new ParseException( msg, re.getColumn() );
203        parseException.initCause( re );
204        return parseException;
205    }
206
207
208    private ParseException wrapTokenStreamException( String schemaDescription, TokenStreamException tse )
209    {
210        String msg = I18n.err( errorCodeOnParseException, schemaDescription, tse.getMessage() );
211        LOG.error( msg );
212        ParseException parseException = new ParseException( msg, 0 );
213        parseException.initCause( tse );
214        return parseException;
215    }
216
217
218    /**
219     * Parse a SchemaObject description and returns back an instance of SchemaObject.
220     * 
221     * @return A SchemaObject instance
222     * @throws RecognitionException the native antlr exception
223     * @throws TokenStreamException the native antlr exception
224     */
225    protected abstract T doParse() throws RecognitionException, TokenStreamException;
226
227
228    /**
229     * Update the schemaName for the given SchemaObject, accordingly to the X-SCHEMA parameter. If
230     * not present, default to 'other'
231     *
232     * @param schemaObject the schema object where the name should be updated
233     */
234    private void updateSchemaName( SchemaObject schemaObject )
235    {
236        // Update the Schema if we have the X-SCHEMA extension
237        List<String> schemaExtension = schemaObject.getExtension( MetaSchemaConstants.X_SCHEMA_AT );
238
239        if ( schemaExtension != null )
240        {
241            String schemaName = schemaExtension.get( 0 );
242
243            if ( Strings.isEmpty( schemaName ) )
244            {
245                schemaObject.setSchemaName( MetaSchemaConstants.SCHEMA_OTHER );
246            }
247            else
248            {
249                schemaObject.setSchemaName( schemaName );
250            }
251        }
252        else
253        {
254            schemaObject.setSchemaName( MetaSchemaConstants.SCHEMA_OTHER );
255        }
256    }
257}