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