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 *     https://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            try ( InputStream in = resource.openStream() )
094            {
095                initializeSchemas( in );
096            }
097        }
098        catch ( LdapException | IOException e )
099        {
100            throw new RuntimeException( e );
101        }
102    }
103
104    
105    /**
106     * Instantiates a new single LDIF schema loader.
107     * 
108     * @param schemaFile The Schema to load
109     */
110    public SingleLdifSchemaLoader( String schemaFile )
111    {
112        try
113        {
114            for ( String s : schemaObjectTypeRdns )
115            {
116                scObjEntryMap.put( s, new HashMap<String, List<Entry>>() );
117            }
118
119            try ( InputStream in = Files.newInputStream( Paths.get( schemaFile ) ) )
120            {
121                initializeSchemas( in );
122            }
123        }
124        catch ( LdapException | IOException e )
125        {
126            throw new RuntimeException( e );
127        }
128    }
129
130    
131    /**
132     * Instantiates a new single LDIF schema loader.
133     * 
134     * @param schemaUrl The URL of the schema to load
135     */
136    public SingleLdifSchemaLoader( URL schemaUrl )
137    {
138        try
139        {
140            for ( String s : schemaObjectTypeRdns )
141            {
142                scObjEntryMap.put( s, new HashMap<String, List<Entry>>() );
143            }
144
145            try ( InputStream in = schemaUrl.openStream() )
146            {
147                initializeSchemas( in );
148            }
149        }
150        catch ( LdapException | IOException e )
151        {
152            throw new RuntimeException( e );
153        }
154    }
155
156
157    /**
158     * Initialize the Schema object from a Single LDIF file
159     * 
160     * @param in The input stream to process
161     * @throws LdapException If the schemas can't be initialized
162     * @throws IOException If we had an issue processing the InputStream
163     */
164    private void initializeSchemas( InputStream in ) throws LdapException, IOException
165    {
166        try ( LdifReader ldifReader = new LdifReader( in ) )
167        {
168            Schema currentSchema = null;
169    
170            while ( ldifReader.hasNext() )
171            {
172                LdifEntry ldifEntry = ldifReader.next();
173                String dn = ldifEntry.getDn().getName();
174                
175                if ( SCHEMA_START_PATTERN.matcher( dn ).matches() )
176                {
177                    Schema schema = getSchema( ldifEntry.getEntry() );
178                    schemaMap.put( schema.getSchemaName(), schema );
179                    currentSchema = schema;
180                }
181                else
182                {
183                    if ( currentSchema == null )
184                    {
185                        throw new LdapException( I18n.err( I18n.ERR_16076_NOT_A_SCHEMA_DEFINITION ) );
186                    }
187                    
188                    loadSchemaObject( currentSchema.getSchemaName(), ldifEntry );
189                }
190            }
191        }
192    }
193
194
195    /**
196     * Load all the schemaObjects
197     * 
198     * @param schemaName The schema name
199     * @param ldifEntry The entry to load
200     */
201    private void loadSchemaObject( String schemaName, LdifEntry ldifEntry )
202    {
203        for ( String scObjTypeRdn : schemaObjectTypeRdns )
204        {
205            Pattern regex = Pattern.compile( "m-oid\\s*=\\s*[0-9\\.]*\\s*" + ",\\s*ou\\s*=\\s*" + scObjTypeRdn
206                + "\\s*,\\s*cn\\s*=\\s*" + schemaName
207                + "\\s*,\\s*ou=schema\\s*", Pattern.CASE_INSENSITIVE );
208
209            String dn = ldifEntry.getDn().getName();
210
211            if ( regex.matcher( dn ).matches() )
212            {
213                Map<String, List<Entry>> m = scObjEntryMap.get( scObjTypeRdn );
214                List<Entry> entryList = m.get( schemaName );
215                
216                if ( entryList == null )
217                {
218                    entryList = new ArrayList<>();
219                    entryList.add( ldifEntry.getEntry() );
220                    m.put( schemaName, entryList );
221                }
222                else
223                {
224                    entryList.add( ldifEntry.getEntry() );
225                }
226
227                break;
228            }
229        }
230    }
231
232
233    private List<Entry> loadSchemaObjects( String schemaObjectType, Schema... schemas )
234    {
235        Map<String, List<Entry>> m = scObjEntryMap.get( schemaObjectType );
236        List<Entry> atList = new ArrayList<>();
237
238        for ( Schema s : schemas )
239        {
240            List<Entry> preLoaded = m.get( s.getSchemaName() );
241            
242            if ( preLoaded != null )
243            {
244                atList.addAll( preLoaded );
245            }
246        }
247
248        return atList;
249    }
250
251
252    /**
253     * {@inheritDoc}
254     */
255    @Override
256    public List<Entry> loadAttributeTypes( Schema... schemas ) throws LdapException, IOException
257    {
258        return loadSchemaObjects( "attributetypes", schemas );
259    }
260
261
262    /**
263     * {@inheritDoc}
264     */
265    @Override
266    public List<Entry> loadComparators( Schema... schemas ) throws LdapException, IOException
267    {
268        return loadSchemaObjects( "comparators", schemas );
269    }
270
271
272    /**
273     * {@inheritDoc}
274     */
275    @Override
276    public List<Entry> loadDitContentRules( Schema... schemas ) throws LdapException, IOException
277    {
278        return loadSchemaObjects( "ditContentRules", schemas );
279    }
280
281
282    /**
283     * {@inheritDoc}
284     */
285    @Override
286    public List<Entry> loadDitStructureRules( Schema... schemas ) throws LdapException, IOException
287    {
288        return loadSchemaObjects( "ditStructureRules", schemas );
289    }
290
291
292    /**
293     * {@inheritDoc}
294     */
295    @Override
296    public List<Entry> loadMatchingRules( Schema... schemas ) throws LdapException, IOException
297    {
298        return loadSchemaObjects( "matchingRules", schemas );
299    }
300
301
302    /**
303     * {@inheritDoc}
304     */
305    @Override
306    public List<Entry> loadMatchingRuleUses( Schema... schemas ) throws LdapException, IOException
307    {
308        return loadSchemaObjects( "matchingRuleUse", schemas );
309    }
310
311
312    /**
313     * {@inheritDoc}
314     */
315    @Override
316    public List<Entry> loadNameForms( Schema... schemas ) throws LdapException, IOException
317    {
318        return loadSchemaObjects( "nameForms", schemas );
319    }
320
321
322    /**
323     * {@inheritDoc}
324     */
325    @Override
326    public List<Entry> loadNormalizers( Schema... schemas ) throws LdapException, IOException
327    {
328        return loadSchemaObjects( "normalizers", schemas );
329    }
330
331
332    /**
333     * {@inheritDoc}
334     */
335    @Override
336    public List<Entry> loadObjectClasses( Schema... schemas ) throws LdapException, IOException
337    {
338        return loadSchemaObjects( "objectClasses", schemas );
339    }
340
341
342    /**
343     * {@inheritDoc}
344     */
345    @Override
346    public List<Entry> loadSyntaxes( Schema... schemas ) throws LdapException, IOException
347    {
348        return loadSchemaObjects( "syntaxes", schemas );
349    }
350
351
352    /**
353     * {@inheritDoc}
354     */
355    @Override
356    public List<Entry> loadSyntaxCheckers( Schema... schemas ) throws LdapException, IOException
357    {
358        return loadSchemaObjects( "syntaxCheckers", schemas );
359    }
360
361}
362
363class SchemaMarker
364{
365    /** The start marker. */
366    private int start;
367
368    /** The end marker. */
369    private int end;
370
371
372    SchemaMarker( int start )
373    {
374        this.start = start;
375    }
376
377
378    public void setEnd( int end )
379    {
380        this.end = end;
381    }
382
383
384    public int getStart()
385    {
386        return start;
387    }
388
389
390    public int getEnd()
391    {
392        return end;
393    }
394}