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