View Javadoc
1   /*
2    *  Licensed to the Apache Software Foundation (ASF) under one
3    *  or more contributor license agreements.  See the NOTICE file
4    *  distributed with this work for additional information
5    *  regarding copyright ownership.  The ASF licenses this file
6    *  to you under the Apache License, Version 2.0 (the
7    *  "License"); you may not use this file except in compliance
8    *  with the License.  You may obtain a copy of the License at
9    *  
10   *    http://www.apache.org/licenses/LICENSE-2.0
11   *  
12   *  Unless required by applicable law or agreed to in writing,
13   *  software distributed under the License is distributed on an
14   *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   *  KIND, either express or implied.  See the License for the
16   *  specific language governing permissions and limitations
17   *  under the License. 
18   *  
19   */
20  package org.apache.directory.server.core.schema;
21  
22  
23  import java.io.File;
24  import java.io.FileNotFoundException;
25  import java.io.IOException;
26  import java.io.InputStream;
27  import java.io.InvalidObjectException;
28  import java.nio.file.Files;
29  import java.util.Map;
30  import java.util.TreeMap;
31  import java.util.UUID;
32  import java.util.regex.Pattern;
33  
34  import org.apache.directory.api.i18n.I18n;
35  import org.apache.directory.api.ldap.model.constants.SchemaConstants;
36  import org.apache.directory.api.ldap.model.csn.Csn;
37  import org.apache.directory.api.ldap.model.csn.CsnFactory;
38  import org.apache.directory.api.ldap.model.entry.DefaultEntry;
39  import org.apache.directory.api.ldap.model.entry.Entry;
40  import org.apache.directory.api.ldap.model.exception.LdapException;
41  import org.apache.directory.api.ldap.model.exception.LdapOtherException;
42  import org.apache.directory.api.ldap.model.ldif.LdifEntry;
43  import org.apache.directory.api.ldap.model.ldif.LdifReader;
44  import org.apache.directory.api.ldap.model.name.Dn;
45  import org.apache.directory.api.ldap.model.schema.SchemaManager;
46  import org.apache.directory.api.ldap.schema.extractor.SchemaLdifExtractor;
47  import org.apache.directory.api.ldap.schema.extractor.impl.DefaultSchemaLdifExtractor;
48  import org.apache.directory.api.ldap.schema.extractor.impl.ResourceMap;
49  import org.apache.directory.server.core.api.interceptor.context.AddOperationContext;
50  import org.apache.directory.server.core.api.interceptor.context.HasEntryOperationContext;
51  import org.apache.directory.server.core.api.partition.Partition;
52  import org.apache.directory.server.core.api.partition.PartitionTxn;
53  import org.slf4j.Logger;
54  import org.slf4j.LoggerFactory;
55  
56  
57  /**
58   * An schema extractor that adds schema LDIF entries directly to the schema partition.
59   *
60   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
61   */
62  public class SchemaLdifToPartitionExtractor implements SchemaLdifExtractor
63  {
64      /** The logger. */
65      private static final Logger LOG = LoggerFactory.getLogger( SchemaLdifToPartitionExtractor.class );
66  
67      /**
68       * The pattern to extract the schema from LDIF files.
69       * java.util.regex.Pattern is immutable so only one instance is needed for all uses.
70       */
71      private static final Pattern EXTRACT_PATTERN = Pattern.compile( ".*schema" + "[/\\Q\\\\E]" + "ou=schema.*\\.ldif" );
72  
73      private final CsnFactory csnFactory = new CsnFactory( 0 );
74  
75      /** The extracted flag. */
76      private boolean extracted;
77  
78      private final SchemaManager schemaManager;
79      private final Partition partition;
80  
81  
82      /**
83       * Creates an extractor which adds schema LDIF entries directly to the schema partition.
84       * The bootstrap schema manager must at least know the 'apachemeta' schema.
85       *
86       * @param schemaManager the bootstrap schema manager
87       * @param partition the destination partition
88       * @throws LdapException If the instance can't be created
89       */
90      public SchemaLdifToPartitionExtractor( SchemaManager schemaManager, Partition partition ) throws LdapException
91      {
92          this.schemaManager = schemaManager;
93          this.partition = partition;
94  
95          Dn dn = new Dn( schemaManager, SchemaConstants.OU_SCHEMA );
96          HasEntryOperationContextcontext/HasEntryOperationContext.html#HasEntryOperationContext">HasEntryOperationContext hasEntryContext = new HasEntryOperationContext( null, dn );
97          hasEntryContext.setPartition( partition );
98          
99          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                 AddOperationContextterceptor/context/AddOperationContext.html#AddOperationContext">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 }