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}