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.File;
024import java.io.FileReader;
025import java.io.IOException;
026import java.io.InputStream;
027import java.io.InputStreamReader;
028import java.text.ParseException;
029import java.util.ArrayList;
030import java.util.HashMap;
031import java.util.List;
032import java.util.Map;
033
034import org.apache.commons.lang.exception.ExceptionUtils;
035import org.apache.directory.api.i18n.I18n;
036import org.apache.directory.api.ldap.model.schema.AttributeType;
037import org.apache.directory.api.ldap.model.schema.MutableAttributeType;
038import org.apache.directory.api.ldap.model.schema.ObjectClass;
039import org.apache.directory.api.ldap.model.schema.SchemaObject;
040import org.apache.directory.api.ldap.model.schema.syntaxCheckers.OpenLdapObjectIdentifierMacro;
041
042import antlr.RecognitionException;
043import antlr.TokenStreamException;
044
045
046/**
047 * A reusable wrapper for antlr generated OpenLDAP schema parsers.
048 *
049 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
050 */
051public class OpenLdapSchemaParser extends AbstractSchemaParser<SchemaObject>
052{
053
054    /** The list of parsed schema descriptions */
055    private List<Object> schemaDescriptions;
056
057    /** The list of attribute type, initialized by splitParsedSchemaDescriptions() */
058    private List<MutableAttributeType> attributeTypes;
059
060    /** The list of object classes, initialized by splitParsedSchemaDescriptions()*/
061    private List<ObjectClass> objectClasses;
062
063    /** The map of object identifier macros, initialized by splitParsedSchemaDescriptions()*/
064    private Map<String, OpenLdapObjectIdentifierMacro> objectIdentifierMacros;
065
066    /** Flag whether object identifier macros should be resolved. */
067    private boolean isResolveObjectIdentifierMacros;
068
069
070    /**
071     * Creates a reusable instance of an OpenLdapSchemaParser.
072     *
073     * @throws IOException if the pipe cannot be formed
074     */
075    public OpenLdapSchemaParser() throws IOException
076    {
077        super( null, null, null, null );
078        isResolveObjectIdentifierMacros = true;
079        super.setQuirksMode( true );
080    }
081
082
083    @Override
084    protected SchemaObject doParse() throws RecognitionException, TokenStreamException
085    {
086        throw new UnsupportedOperationException( "OpenLdapSchemaParser is not a normal schema parser" );
087    }
088
089
090    /**
091     * Reset the parser
092     */
093    public void clear()
094    {
095    }
096
097
098    /**
099     * Gets the attribute types.
100     * 
101     * @return the attribute types
102     */
103    public List<MutableAttributeType> getAttributeTypes()
104    {
105        return attributeTypes;
106    }
107
108
109    /**
110     * Gets the object class types.
111     * 
112     * @return the object class types
113     */
114    public List<ObjectClass> getObjectClassTypes()
115    {
116        return objectClasses;
117    }
118
119
120    /**
121     * Gets the object identifier macros.
122     * 
123     * @return the object identifier macros
124     */
125    public Map<String, OpenLdapObjectIdentifierMacro> getObjectIdentifierMacros()
126    {
127        return objectIdentifierMacros;
128    }
129
130
131    /**
132     * Splits parsed schema descriptions and resolved
133     * object identifier macros.
134     * 
135     * @throws ParseException the parse exception
136     */
137    private void afterParse() throws ParseException
138    {
139        objectClasses = new ArrayList<ObjectClass>();
140        attributeTypes = new ArrayList<MutableAttributeType>();
141        objectIdentifierMacros = new HashMap<String, OpenLdapObjectIdentifierMacro>();
142
143        // split parsed schema descriptions
144        for ( Object obj : schemaDescriptions )
145        {
146            if ( obj instanceof OpenLdapObjectIdentifierMacro )
147            {
148                OpenLdapObjectIdentifierMacro oid = ( OpenLdapObjectIdentifierMacro ) obj;
149                objectIdentifierMacros.put( oid.getName(), oid );
150            }
151            else if ( obj instanceof AttributeType )
152            {
153                MutableAttributeType attributeType = ( MutableAttributeType ) obj;
154
155                attributeTypes.add( attributeType );
156            }
157            else if ( obj instanceof ObjectClass )
158            {
159                ObjectClass objectClass = ( ObjectClass ) obj;
160
161                objectClasses.add( objectClass );
162            }
163        }
164
165        if ( isResolveObjectIdentifierMacros() )
166        {
167            // resolve object identifier macros
168            for ( OpenLdapObjectIdentifierMacro oid : objectIdentifierMacros.values() )
169            {
170                resolveObjectIdentifierMacro( oid );
171            }
172
173            // apply object identifier macros to object classes
174            for ( ObjectClass objectClass : objectClasses )
175            {
176                objectClass.setOid( getResolveOid( objectClass.getOid() ) );
177            }
178
179            // apply object identifier macros to attribute types
180            for ( MutableAttributeType attributeType : attributeTypes )
181            {
182                attributeType.setOid( getResolveOid( attributeType.getOid() ) );
183                attributeType.setSyntaxOid( getResolveOid( attributeType.getSyntaxOid() ) );
184            }
185
186        }
187    }
188
189
190    private String getResolveOid( String oid )
191    {
192        if ( oid != null && oid.indexOf( ':' ) != -1 )
193        {
194            // resolve OID
195            String[] nameAndSuffix = oid.split( ":" );
196            if ( objectIdentifierMacros.containsKey( nameAndSuffix[0] ) )
197            {
198                OpenLdapObjectIdentifierMacro macro = objectIdentifierMacros.get( nameAndSuffix[0] );
199                return macro.getResolvedOid() + "." + nameAndSuffix[1];
200            }
201        }
202        return oid;
203    }
204
205
206    private void resolveObjectIdentifierMacro( OpenLdapObjectIdentifierMacro macro ) throws ParseException
207    {
208        String rawOidOrNameSuffix = macro.getRawOidOrNameSuffix();
209
210        if ( macro.isResolved() )
211        {
212            // finished
213            return;
214        }
215        else if ( rawOidOrNameSuffix.indexOf( ':' ) != -1 )
216        {
217            // resolve OID
218            String[] nameAndSuffix = rawOidOrNameSuffix.split( ":" );
219            if ( objectIdentifierMacros.containsKey( nameAndSuffix[0] ) )
220            {
221                OpenLdapObjectIdentifierMacro parentMacro = objectIdentifierMacros.get( nameAndSuffix[0] );
222                resolveObjectIdentifierMacro( parentMacro );
223                macro.setResolvedOid( parentMacro.getResolvedOid() + "." + nameAndSuffix[1] );
224            }
225            else
226            {
227                throw new ParseException( I18n.err( I18n.ERR_04257, nameAndSuffix[0] ), 0 );
228            }
229
230        }
231        else
232        {
233            // no :suffix,
234            if ( objectIdentifierMacros.containsKey( rawOidOrNameSuffix ) )
235            {
236                OpenLdapObjectIdentifierMacro parentMacro = objectIdentifierMacros.get( rawOidOrNameSuffix );
237                resolveObjectIdentifierMacro( parentMacro );
238                macro.setResolvedOid( parentMacro.getResolvedOid() );
239            }
240            else
241            {
242                macro.setResolvedOid( rawOidOrNameSuffix );
243            }
244        }
245    }
246
247
248    /**
249     * Parses an OpenLDAP schemaObject element/object.
250     *
251     * @param schemaObject the String image of a complete schema object
252     * @return the schema object
253     * @throws ParseException If the schemaObject can't be parsed
254     */
255    public SchemaObject parse( String schemaObject ) throws ParseException
256    {
257        if ( schemaObject == null || schemaObject.trim().equals( "" ) )
258        {
259            throw new ParseException( I18n.err( I18n.ERR_04258 ), 0 );
260        }
261
262        reset( schemaObject ); // reset and initialize the parser / lexer pair
263        invokeParser( schemaObject );
264
265        if ( !schemaDescriptions.isEmpty() )
266        {
267            for ( Object obj : schemaDescriptions )
268            {
269                if ( obj instanceof SchemaObject )
270                {
271                    return ( SchemaObject ) obj;
272                }
273            }
274        }
275        return null;
276    }
277
278
279    private void invokeParser( String subject ) throws ParseException
280    {
281        try
282        {
283            monitor.startedParse( "starting parse on:\n" + subject );
284            schemaDescriptions = parser.openLdapSchema();
285            afterParse();
286            monitor.finishedParse( "Done parsing!" );
287        }
288        catch ( RecognitionException e )
289        {
290            String msg = "Parser failure on:\n\t" + subject;
291            msg += "\nAntlr exception trace:\n" + ExceptionUtils.getFullStackTrace( e );
292            throw new ParseException( msg, e.getColumn() );
293        }
294        catch ( TokenStreamException e2 )
295        {
296            String msg = "Parser failure on:\n\t" + subject;
297            msg += "\nAntlr exception trace:\n" + ExceptionUtils.getFullStackTrace( e2 );
298            throw new ParseException( msg, 0 );
299        }
300    }
301
302
303    /**
304     * Parses a stream of OpenLDAP schemaObject elements/objects.
305     *
306     * @param schemaIn a stream of schema objects
307     * @throws IOException If the schemaObject can't be transformed to a byteArrayInputStream
308     * @throws ParseException If the schemaObject can't be parsed
309     */
310    public void parse( InputStream schemaIn ) throws IOException, ParseException
311    {
312        InputStreamReader in = new InputStreamReader( schemaIn );
313        lexer.prepareNextInput( in );
314        parser.resetState();
315
316        invokeParser( "schema input stream ==> " + schemaIn.toString() );
317    }
318
319
320    /**
321     * Parses a file of OpenLDAP schemaObject elements/objects.
322     *
323     * @param schemaFile a file of schema objects
324     * @throws IOException If the schemaObject can't be transformed to a byteArrayInputStream
325     * @throws ParseException If the schemaObject can't be parsed
326     */
327    public void parse( File schemaFile ) throws IOException, ParseException
328    {
329        FileReader in = new FileReader( schemaFile );
330        lexer.prepareNextInput( in );
331        parser.resetState();
332
333        invokeParser( "schema file ==> " + schemaFile.getAbsolutePath() );
334    }
335
336
337    /**
338     * Checks if object identifier macros should be resolved.
339     * 
340     * @return true, object identifier macros should be resolved.
341     */
342    public boolean isResolveObjectIdentifierMacros()
343    {
344        return isResolveObjectIdentifierMacros;
345    }
346
347
348    /**
349     * Sets if object identifier macros should be resolved.
350     * 
351     * @param resolveObjectIdentifierMacros true if object identifier macros should be resolved
352     */
353    public void setResolveObjectIdentifierMacros( boolean resolveObjectIdentifierMacros )
354    {
355        this.isResolveObjectIdentifierMacros = resolveObjectIdentifierMacros;
356    }
357
358}