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 */
020
021package org.apache.directory.api.ldap.schema.loader;
022
023
024import java.io.IOException;
025import java.io.InputStream;
026import java.net.URL;
027import java.nio.file.Files;
028import java.nio.file.Paths;
029import java.util.ArrayList;
030import java.util.HashMap;
031import java.util.List;
032import java.util.Map;
033import java.util.regex.Pattern;
034
035import org.apache.directory.api.i18n.I18n;
036import org.apache.directory.api.ldap.model.entry.Entry;
037import org.apache.directory.api.ldap.model.exception.LdapException;
038import org.apache.directory.api.ldap.model.ldif.LdifEntry;
039import org.apache.directory.api.ldap.model.ldif.LdifReader;
040import org.apache.directory.api.ldap.model.schema.registries.AbstractSchemaLoader;
041import org.apache.directory.api.ldap.model.schema.registries.Schema;
042import org.slf4j.Logger;
043import org.slf4j.LoggerFactory;
044
045
046/**
047 * A schema loader based on a single monolithic ldif file containing all the schema partition elements
048 * 
049 * Performs better than any other existing LDIF schema loaders. NOT DOCUMENTED atm
050 * 
051 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
052 */
053public class SingleLdifSchemaLoader extends AbstractSchemaLoader
054{
055    /** 
056     * Pattern for start of schema Dn.
057     * java.util.regex.Pattern is immutable so only one instance is needed for all uses.
058     */
059    private static final Pattern SCHEMA_START_PATTERN = Pattern
060        .compile( "cn\\s*=\\s*[a-z0-9-_]*\\s*,\\s*ou\\s*=\\s*schema" );
061
062    /** The logger. */
063    private static final Logger LOG = LoggerFactory.getLogger( SingleLdifSchemaLoader.class );
064
065    /** The schema object Rdn attribute types. */
066    private String[] schemaObjectTypeRdns = new String[]
067        { "attributetypes", "comparators", "ditContentRules", "ditStructureRules", "matchingRules", "matchingRuleUse",
068            "nameForms", "normalizers", "objectClasses", "syntaxes", "syntaxCheckers" };
069
070    /** The map containing ... */
071    private Map<String, Map<String, List<Entry>>> scObjEntryMap = new HashMap<>();
072
073
074    /**
075     * Instantiates a new single LDIF schema loader.
076     */
077    public SingleLdifSchemaLoader()
078    {
079        try
080        {
081            URL resource = getClass().getClassLoader().getResource( "schema-all.ldif" );
082
083            if ( LOG.isDebugEnabled() )
084            {
085                LOG.debug( I18n.msg( I18n.MSG_16012_URL_SCHEMA_ALL_LDIF, resource ) );
086            }
087
088            for ( String s : schemaObjectTypeRdns )
089            {
090                scObjEntryMap.put( s, new HashMap<String, List<Entry>>() );
091            }
092
093            InputStream in = resource.openStream();
094
095            initializeSchemas( in );
096        }
097        catch ( LdapException | IOException e )
098        {
099            throw new RuntimeException( e );
100        }
101    }
102
103    
104    /**
105     * Instantiates a new single LDIF schema loader.
106     * 
107     * @param schemaFile The Schema to load
108     */
109    public SingleLdifSchemaLoader( String schemaFile )
110    {
111        try
112        {
113            for ( String s : schemaObjectTypeRdns )
114            {
115                scObjEntryMap.put( s, new HashMap<String, List<Entry>>() );
116            }
117
118            InputStream in = Files.newInputStream( Paths.get( schemaFile ) );
119
120            initializeSchemas( in );
121        }
122        catch ( LdapException | IOException e )
123        {
124            throw new RuntimeException( e );
125        }
126    }
127
128    
129    /**
130     * Instantiates a new single LDIF schema loader.
131     * 
132     * @param schemaUrl The URL of the schema to load
133     */
134    public SingleLdifSchemaLoader( URL schemaUrl )
135    {
136        try
137        {
138            for ( String s : schemaObjectTypeRdns )
139            {
140                scObjEntryMap.put( s, new HashMap<String, List<Entry>>() );
141            }
142
143            InputStream in = schemaUrl.openStream();
144
145            initializeSchemas( in );
146        }
147        catch ( LdapException | IOException e )
148        {
149            throw new RuntimeException( e );
150        }
151    }
152
153
154    /**
155     * Initialize the Schema object from a Single LDIF file
156     * 
157     * @param in The input stream to process
158     * @throws LdapException If the schemas can't be initialized
159     * @throws IOException If we had an issue processing the InputStream
160     */
161    private void initializeSchemas( InputStream in ) throws LdapException, IOException
162    {
163        try ( LdifReader ldifReader = new LdifReader( in ) )
164        {
165            Schema currentSchema = null;
166    
167            while ( ldifReader.hasNext() )
168            {
169                LdifEntry ldifEntry = ldifReader.next();
170                String dn = ldifEntry.getDn().getName();
171                
172                if ( SCHEMA_START_PATTERN.matcher( dn ).matches() )
173                {
174                    Schema schema = getSchema( ldifEntry.getEntry() );
175                    schemaMap.put( schema.getSchemaName(), schema );
176                    currentSchema = schema;
177                }
178                else
179                {
180                    if ( currentSchema == null )
181                    {
182                        throw new LdapException( I18n.err( I18n.ERR_16076_NOT_A_SCHEMA_DEFINITION ) );
183                    }
184                    
185                    loadSchemaObject( currentSchema.getSchemaName(), ldifEntry );
186                }
187            }
188        }
189    }
190
191
192    /**
193     * Load all the schemaObjects
194     * 
195     * @param schemaName The schema name
196     * @param ldifEntry The entry to load
197     */
198    private void loadSchemaObject( String schemaName, LdifEntry ldifEntry )
199    {
200        for ( String scObjTypeRdn : schemaObjectTypeRdns )
201        {
202            Pattern regex = Pattern.compile( "m-oid\\s*=\\s*[0-9\\.]*\\s*" + ",\\s*ou\\s*=\\s*" + scObjTypeRdn
203                + "\\s*,\\s*cn\\s*=\\s*" + schemaName
204                + "\\s*,\\s*ou=schema\\s*", Pattern.CASE_INSENSITIVE );
205
206            String dn = ldifEntry.getDn().getName();
207
208            if ( regex.matcher( dn ).matches() )
209            {
210                Map<String, List<Entry>> m = scObjEntryMap.get( scObjTypeRdn );
211                List<Entry> entryList = m.get( schemaName );
212                
213                if ( entryList == null )
214                {
215                    entryList = new ArrayList<>();
216                    entryList.add( ldifEntry.getEntry() );
217                    m.put( schemaName, entryList );
218                }
219                else
220                {
221                    entryList.add( ldifEntry.getEntry() );
222                }
223
224                break;
225            }
226        }
227    }
228
229
230    private List<Entry> loadSchemaObjects( String schemaObjectType, Schema... schemas )
231    {
232        Map<String, List<Entry>> m = scObjEntryMap.get( schemaObjectType );
233        List<Entry> atList = new ArrayList<>();
234
235        for ( Schema s : schemas )
236        {
237            List<Entry> preLoaded = m.get( s.getSchemaName() );
238            
239            if ( preLoaded != null )
240            {
241                atList.addAll( preLoaded );
242            }
243        }
244
245        return atList;
246    }
247
248
249    /**
250     * {@inheritDoc}
251     */
252    @Override
253    public List<Entry> loadAttributeTypes( Schema... schemas ) throws LdapException, IOException
254    {
255        return loadSchemaObjects( "attributetypes", schemas );
256    }
257
258
259    /**
260     * {@inheritDoc}
261     */
262    @Override
263    public List<Entry> loadComparators( Schema... schemas ) throws LdapException, IOException
264    {
265        return loadSchemaObjects( "comparators", schemas );
266    }
267
268
269    /**
270     * {@inheritDoc}
271     */
272    @Override
273    public List<Entry> loadDitContentRules( Schema... schemas ) throws LdapException, IOException
274    {
275        return loadSchemaObjects( "ditContentRules", schemas );
276    }
277
278
279    /**
280     * {@inheritDoc}
281     */
282    @Override
283    public List<Entry> loadDitStructureRules( Schema... schemas ) throws LdapException, IOException
284    {
285        return loadSchemaObjects( "ditStructureRules", schemas );
286    }
287
288
289    /**
290     * {@inheritDoc}
291     */
292    @Override
293    public List<Entry> loadMatchingRules( Schema... schemas ) throws LdapException, IOException
294    {
295        return loadSchemaObjects( "matchingRules", schemas );
296    }
297
298
299    /**
300     * {@inheritDoc}
301     */
302    @Override
303    public List<Entry> loadMatchingRuleUses( Schema... schemas ) throws LdapException, IOException
304    {
305        return loadSchemaObjects( "matchingRuleUse", schemas );
306    }
307
308
309    /**
310     * {@inheritDoc}
311     */
312    @Override
313    public List<Entry> loadNameForms( Schema... schemas ) throws LdapException, IOException
314    {
315        return loadSchemaObjects( "nameForms", schemas );
316    }
317
318
319    /**
320     * {@inheritDoc}
321     */
322    @Override
323    public List<Entry> loadNormalizers( Schema... schemas ) throws LdapException, IOException
324    {
325        return loadSchemaObjects( "normalizers", schemas );
326    }
327
328
329    /**
330     * {@inheritDoc}
331     */
332    @Override
333    public List<Entry> loadObjectClasses( Schema... schemas ) throws LdapException, IOException
334    {
335        return loadSchemaObjects( "objectClasses", schemas );
336    }
337
338
339    /**
340     * {@inheritDoc}
341     */
342    @Override
343    public List<Entry> loadSyntaxes( Schema... schemas ) throws LdapException, IOException
344    {
345        return loadSchemaObjects( "syntaxes", schemas );
346    }
347
348
349    /**
350     * {@inheritDoc}
351     */
352    @Override
353    public List<Entry> loadSyntaxCheckers( Schema... schemas ) throws LdapException, IOException
354    {
355        return loadSchemaObjects( "syntaxCheckers", schemas );
356    }
357
358}
359
360class SchemaMarker
361{
362    /** The start marker. */
363    private int start;
364
365    /** The end marker. */
366    private int end;
367
368
369    SchemaMarker( int start )
370    {
371        this.start = start;
372    }
373
374
375    public void setEnd( int end )
376    {
377        this.end = end;
378    }
379
380
381    public int getStart()
382    {
383        return start;
384    }
385
386
387    public int getEnd()
388    {
389        return end;
390    }
391}