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