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.server.core.schema;
021
022
023import java.io.File;
024import java.io.FileNotFoundException;
025import java.io.IOException;
026import java.io.InputStream;
027import java.io.InvalidObjectException;
028import java.nio.file.Files;
029import java.util.Map;
030import java.util.TreeMap;
031import java.util.UUID;
032import java.util.regex.Pattern;
033
034import org.apache.directory.api.i18n.I18n;
035import org.apache.directory.api.ldap.model.constants.SchemaConstants;
036import org.apache.directory.api.ldap.model.csn.Csn;
037import org.apache.directory.api.ldap.model.csn.CsnFactory;
038import org.apache.directory.api.ldap.model.entry.DefaultEntry;
039import org.apache.directory.api.ldap.model.entry.Entry;
040import org.apache.directory.api.ldap.model.exception.LdapException;
041import org.apache.directory.api.ldap.model.exception.LdapOtherException;
042import org.apache.directory.api.ldap.model.ldif.LdifEntry;
043import org.apache.directory.api.ldap.model.ldif.LdifReader;
044import org.apache.directory.api.ldap.model.name.Dn;
045import org.apache.directory.api.ldap.model.schema.SchemaManager;
046import org.apache.directory.api.ldap.schema.extractor.SchemaLdifExtractor;
047import org.apache.directory.api.ldap.schema.extractor.impl.DefaultSchemaLdifExtractor;
048import org.apache.directory.api.ldap.schema.extractor.impl.ResourceMap;
049import org.apache.directory.server.core.api.interceptor.context.AddOperationContext;
050import org.apache.directory.server.core.api.interceptor.context.HasEntryOperationContext;
051import org.apache.directory.server.core.api.partition.Partition;
052import org.apache.directory.server.core.api.partition.PartitionTxn;
053import org.slf4j.Logger;
054import org.slf4j.LoggerFactory;
055
056
057/**
058 * An schema extractor that adds schema LDIF entries directly to the schema partition.
059 *
060 * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
061 */
062public class SchemaLdifToPartitionExtractor implements SchemaLdifExtractor
063{
064    /** The logger. */
065    private static final Logger LOG = LoggerFactory.getLogger( SchemaLdifToPartitionExtractor.class );
066
067    /**
068     * The pattern to extract the schema from LDIF files.
069     * java.util.regex.Pattern is immutable so only one instance is needed for all uses.
070     */
071    private static final Pattern EXTRACT_PATTERN = Pattern.compile( ".*schema" + "[/\\Q\\\\E]" + "ou=schema.*\\.ldif" );
072
073    private final CsnFactory csnFactory = new CsnFactory( 0 );
074
075    /** The extracted flag. */
076    private boolean extracted;
077
078    private final SchemaManager schemaManager;
079    private final Partition partition;
080
081
082    /**
083     * Creates an extractor which adds schema LDIF entries directly to the schema partition.
084     * The bootstrap schema manager must at least know the 'apachemeta' schema.
085     *
086     * @param schemaManager the bootstrap schema manager
087     * @param partition the destination partition
088     * @throws LdapException If the instance can't be created
089     */
090    public SchemaLdifToPartitionExtractor( SchemaManager schemaManager, Partition partition ) throws LdapException
091    {
092        this.schemaManager = schemaManager;
093        this.partition = partition;
094
095        Dn dn = new Dn( schemaManager, SchemaConstants.OU_SCHEMA );
096        HasEntryOperationContext hasEntryContext = new HasEntryOperationContext( null, dn );
097        hasEntryContext.setPartition( partition );
098        
099        try ( PartitionTxn partitionTxn = partition.beginReadTransaction() )
100        {
101            hasEntryContext.setTransaction( partitionTxn );
102            
103            if ( partition.hasEntry( hasEntryContext ) )
104            {
105                LOG.info( "Schema entry 'ou=schema' exists: extracted state set to true." );
106                extracted = true;
107            }
108            else
109            {
110                LOG.info( "Schema entry 'ou=schema' does NOT exist: extracted state set to false." );
111                extracted = false;
112            }
113        }
114        catch ( IOException ioe )
115        {
116            throw new LdapOtherException( ioe.getMessage(), ioe );
117        }
118    }
119
120
121    /**
122     * Gets whether or not the schema has already been added to the schema partition.
123     *
124     * @return true if schema has already been added to the schema partition
125     */
126    @Override
127    public boolean isExtracted()
128    {
129        return extracted;
130    }
131
132
133    /**
134     * {@inheritDoc}
135     */
136    @Override
137    public void extractOrCopy( boolean overwrite ) throws IOException
138    {
139        Map<String, Boolean> resources = ResourceMap.getResources( EXTRACT_PATTERN );
140
141        // must sort the map to ensure parent entries are added before children
142        resources = new TreeMap<>( resources );
143
144        if ( !extracted || overwrite )
145        {
146            for ( Map.Entry<String, Boolean> entry : resources.entrySet() )
147            {
148                if ( entry.getValue() )
149                {
150                    addFromClassLoader( entry.getKey() );
151                }
152                else
153                {
154                    File resource = new File( entry.getKey() );
155                    addLdifFile( resource );
156                }
157            }
158
159            extracted = true;
160        }
161    }
162
163
164    /**
165     * {@inheritDoc}
166     */
167    @Override
168    public void extractOrCopy() throws IOException
169    {
170        extractOrCopy( false );
171    }
172
173
174    /**
175     * Adds an schema entry from an LDIF file.
176     *
177     * @param source the source file to copy
178     * @throws IOException if there are IO errors or the source does not exist
179     */
180    private void addLdifFile( File source ) throws IOException
181    {
182        LOG.debug( "copyFile(): source = {}", source );
183
184        if ( !source.getParentFile().exists() )
185        {
186            throw new FileNotFoundException( I18n.err( I18n.ERR_16002_MORE_THAN_ONE_ENTRY, source.getAbsolutePath() ) );
187        }
188
189        InputStream in = Files.newInputStream( source.toPath() );
190        addFromStream( in, source.getAbsolutePath() );
191    }
192
193
194    /**
195     * Adds an schema entry from a class loader resource.
196     *
197     * @param resource the LDIF schema resource
198     * @throws IOException if there are IO errors
199     */
200    private void addFromClassLoader( String resource ) throws IOException
201    {
202        InputStream in = DefaultSchemaLdifExtractor.getUniqueResourceAsStream( resource,
203            "LDIF file in schema repository" );
204        addFromStream( in, resource );
205    }
206
207
208    /**
209     * Adds an schema entry from the given stream to the schema partition
210     *
211     * @param in the input stream
212     * @param source the source
213     * @throws IOException signals that an I/O exception has occurred.
214     */
215    private void addFromStream( InputStream in, String source ) throws IOException
216    {
217        try
218        {
219            LdifReader ldifReader = new LdifReader( in );
220            boolean first = true;
221            LdifEntry ldifEntry = null;
222
223            try
224            {
225                while ( ldifReader.hasNext() )
226                {
227                    if ( first )
228                    {
229                        ldifEntry = ldifReader.next();
230
231                        if ( ldifEntry.get( SchemaConstants.ENTRY_UUID_AT ) == null )
232                        {
233                            // No UUID, let's create one
234                            UUID entryUuid = UUID.randomUUID();
235                            ldifEntry.addAttribute( SchemaConstants.ENTRY_UUID_AT, entryUuid.toString() );
236                        }
237                        if ( ldifEntry.get( SchemaConstants.ENTRY_CSN_AT ) == null )
238                        {
239                            // No CSN, let's create one
240                            Csn csn = csnFactory.newInstance();
241                            ldifEntry.addAttribute( SchemaConstants.ENTRY_CSN_AT, csn.toString() );
242                        }
243
244                        first = false;
245                    }
246                    else
247                    {
248                        // throw an exception : we should not have more than one entry per schema ldif file
249                        String msg = I18n.err( I18n.ERR_16002_MORE_THAN_ONE_ENTRY, source );
250                        LOG.error( msg );
251                        throw new InvalidObjectException( msg );
252                    }
253                }
254            }
255            finally
256            {
257                ldifReader.close();
258            }
259
260            // inject the entry if any
261            if ( ldifEntry != null )
262            {
263                Entry entry = new DefaultEntry( schemaManager, ldifEntry.getEntry() );
264                AddOperationContext addContext = new AddOperationContext( null, entry );
265                addContext.setPartition( partition );
266                
267                PartitionTxn partitionTxn = null;
268
269                try
270                { 
271                    partitionTxn = partition.beginWriteTransaction();
272                    addContext.setTransaction( partitionTxn );
273                    
274                    partition.add( addContext );
275                    partitionTxn.commit();
276                }
277                catch ( LdapException le )
278                {
279                    if ( partitionTxn != null )
280                    {
281                        try
282                        { 
283                            partitionTxn.abort();
284                        }
285                        catch ( IOException ioe )
286                        {
287                            throw new LdapOtherException( ioe.getMessage(), ioe );
288                        }
289                    }
290                    
291                    throw le;
292                }
293                catch ( IOException ioe )
294                {
295                    try
296                    { 
297                        partitionTxn.abort();
298                    }
299                    catch ( IOException ioe2 )
300                    {
301                        throw new LdapOtherException( ioe2.getMessage(), ioe2 );
302                    }
303
304                    throw new LdapOtherException( ioe.getMessage(), ioe );
305                }
306            }
307        }
308        catch ( LdapException ne )
309        {
310            String msg = I18n.err( I18n.ERR_16003_ERROR_PARSING_LDIF, source, ne.getLocalizedMessage() );
311            LOG.error( msg );
312            throw new InvalidObjectException( msg );
313        }
314    }
315
316}