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 package org.apache.directory.server.core.changelog;
020
021
022 import java.util.ArrayList;
023 import java.util.List;
024 import java.util.Set;
025
026 import org.apache.directory.server.constants.ApacheSchemaConstants;
027 import org.apache.directory.server.core.DirectoryService;
028 import org.apache.directory.server.core.entry.ClonedServerEntry;
029 import org.apache.directory.server.core.entry.ServerEntryUtils;
030 import org.apache.directory.server.core.interceptor.BaseInterceptor;
031 import org.apache.directory.server.core.interceptor.NextInterceptor;
032 import org.apache.directory.server.core.interceptor.context.AddOperationContext;
033 import org.apache.directory.server.core.interceptor.context.DeleteOperationContext;
034 import org.apache.directory.server.core.interceptor.context.ModifyOperationContext;
035 import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext;
036 import org.apache.directory.server.core.interceptor.context.MoveOperationContext;
037 import org.apache.directory.server.core.interceptor.context.OperationContext;
038 import org.apache.directory.server.core.interceptor.context.RenameOperationContext;
039 import org.apache.directory.server.core.partition.ByPassConstants;
040 import org.apache.directory.server.core.schema.SchemaService;
041 import org.apache.directory.shared.ldap.entry.Entry;
042 import org.apache.directory.shared.ldap.entry.EntryAttribute;
043 import org.apache.directory.shared.ldap.entry.Modification;
044 import org.apache.directory.shared.ldap.entry.ServerEntry;
045 import org.apache.directory.shared.ldap.entry.ServerModification;
046 import org.apache.directory.shared.ldap.entry.client.DefaultClientEntry;
047 import org.apache.directory.shared.ldap.ldif.ChangeType;
048 import org.apache.directory.shared.ldap.ldif.LdifEntry;
049 import org.apache.directory.shared.ldap.ldif.LdifRevertor;
050 import org.apache.directory.shared.ldap.name.DN;
051 import org.apache.directory.shared.ldap.name.RDN;
052 import org.apache.directory.shared.ldap.schema.AttributeType;
053 import org.slf4j.Logger;
054 import org.slf4j.LoggerFactory;
055
056
057 /**
058 * An interceptor which intercepts write operations to the directory and
059 * logs them with the server's ChangeLog service.
060 * Note: Adding/deleting a tag is not recorded as a change
061 *
062 * @org.apache.xbean.XBean
063 */
064 public class ChangeLogInterceptor extends BaseInterceptor
065 {
066 /** for debugging */
067 private static final Logger LOG = LoggerFactory.getLogger( ChangeLogInterceptor.class );
068
069 /** used to ignore modify operations to tombstone entries */
070 private AttributeType entryDeleted;
071
072 /** the changelog service to log changes to */
073 private ChangeLog changeLog;
074
075 /** we need the schema service to deal with special conditions */
076 private SchemaService schemaService;
077
078 /** OID of the 'rev' attribute used in changeLogEvent and tag objectclasses */
079 private static final String REV_AT_OID = "1.3.6.1.4.1.18060.0.4.1.2.47";
080
081 // -----------------------------------------------------------------------
082 // Overridden init() and destroy() methods
083 // -----------------------------------------------------------------------
084
085
086 /**
087 * The init method will initialize the local variables and load the
088 * entryDeleted AttributeType.
089 */
090 public void init( DirectoryService directoryService ) throws Exception
091 {
092 super.init( directoryService );
093
094 changeLog = directoryService.getChangeLog();
095 schemaService = directoryService.getSchemaService();
096 entryDeleted = directoryService.getSchemaManager()
097 .lookupAttributeTypeRegistry( ApacheSchemaConstants.ENTRY_DELETED_AT_OID );
098 }
099
100
101 // -----------------------------------------------------------------------
102 // Overridden (only change inducing) intercepted methods
103 // -----------------------------------------------------------------------
104
105
106 public void add( NextInterceptor next, AddOperationContext opContext ) throws Exception
107 {
108 next.add( opContext );
109
110 if ( ! changeLog.isEnabled() || ! opContext.isFirstOperation() )
111 {
112 return;
113 }
114
115 ServerEntry addEntry = opContext.getEntry();
116
117 // we don't want to record addition of a tag as a change
118 if( addEntry.get( REV_AT_OID ) != null )
119 {
120 return;
121 }
122
123 LdifEntry forward = new LdifEntry();
124 forward.setChangeType( ChangeType.Add );
125 forward.setDn( opContext.getDn() );
126
127 Set<AttributeType> list = addEntry.getAttributeTypes();
128
129 for ( AttributeType attributeType:list )
130 {
131 forward.addAttribute( addEntry.get( attributeType).toClientAttribute() );
132 }
133
134 LdifEntry reverse = LdifRevertor.reverseAdd( opContext.getDn() );
135 opContext.setChangeLogEvent( changeLog.log( getPrincipal(), forward, reverse ) );
136 }
137
138
139 /**
140 * The delete operation has to be stored with a way to restore the deleted element.
141 * There is no way to do that but reading the entry and dump it into the LOG.
142 */
143 public void delete( NextInterceptor next, DeleteOperationContext opContext ) throws Exception
144 {
145 // @todo make sure we're not putting in operational attributes that cannot be user modified
146 // must save the entry if change log is enabled
147 ServerEntry serverEntry = null;
148
149 if ( changeLog.isEnabled() && opContext.isFirstOperation() )
150 {
151 serverEntry = getAttributes( opContext );
152 }
153
154 next.delete( opContext );
155
156 if ( ! changeLog.isEnabled() || ! opContext.isFirstOperation() )
157 {
158 return;
159 }
160
161 // we don't want to record deleting a tag as a change
162 if( serverEntry.get( REV_AT_OID ) != null )
163 {
164 return;
165 }
166
167 LdifEntry forward = new LdifEntry();
168 forward.setChangeType( ChangeType.Delete );
169 forward.setDn( opContext.getDn() );
170
171 Entry reverseEntry = new DefaultClientEntry( serverEntry.getDn() );
172
173 for ( EntryAttribute attribute : serverEntry )
174 {
175 // filter collective attributes, they can't be added by the revert operation
176 AttributeType at = schemaService.getSchemaManager().getAttributeTypeRegistry().lookup( attribute.getId() );
177 if ( !at.isCollective() )
178 {
179 reverseEntry.add( attribute.toClientAttribute() );
180 }
181 }
182
183 LdifEntry reverse = LdifRevertor.reverseDel( opContext.getDn(), reverseEntry );
184 opContext.setChangeLogEvent( changeLog.log( getPrincipal(), forward, reverse ) );
185 }
186
187
188 /**
189 * Gets attributes required for modifications.
190 *
191 * @param dn the dn of the entry to get
192 * @return the entry's attributes (may be immutable if the schema subentry)
193 * @throws Exception on error accessing the entry's attributes
194 */
195 private ServerEntry getAttributes( OperationContext opContext ) throws Exception
196 {
197 DN dn = opContext.getDn();
198 ClonedServerEntry serverEntry;
199
200 // @todo make sure we're not putting in operational attributes that cannot be user modified
201 if ( schemaService.isSchemaSubentry( dn.getNormName() ) )
202 {
203 return schemaService.getSubschemaEntryCloned();
204 }
205 else
206 {
207 serverEntry = opContext.lookup( dn, ByPassConstants.LOOKUP_BYPASS );
208 }
209
210 return serverEntry;
211 }
212
213
214 /**
215 *
216 */
217 public void modify( NextInterceptor next, ModifyOperationContext opContext ) throws Exception
218 {
219 ServerEntry serverEntry = null;
220 Modification modification = ServerEntryUtils.getModificationItem( opContext.getModItems(), entryDeleted );
221 boolean isDelete = ( modification != null );
222
223 if ( ! isDelete && ( changeLog.isEnabled() && opContext.isFirstOperation() ) )
224 {
225 // @todo make sure we're not putting in operational attributes that cannot be user modified
226 serverEntry = getAttributes( opContext );
227 }
228
229 // Duplicate modifications so that the reverse does not contain the operational attributes
230 List<Modification> clonedMods = new ArrayList<Modification>();
231
232 for ( Modification mod : opContext.getModItems() )
233 {
234 clonedMods.add( mod.clone() );
235 }
236
237 // Call the next interceptor
238 next.modify( opContext );
239
240 // @TODO: needs big consideration!!!
241 // NOTE: perhaps we need to log this as a system operation that cannot and should not be reapplied?
242 if (
243 isDelete ||
244 ! changeLog.isEnabled() ||
245 ! opContext.isFirstOperation() ||
246
247 // if there are no modifications due to stripping out bogus non-
248 // existing attributes then we will have no modification items and
249 // should ignore not this without registering it with the changelog
250
251 opContext.getModItems().size() == 0 )
252 {
253 if ( isDelete )
254 {
255 LOG.debug( "Bypassing changelog on modify of entryDeleted attribute." );
256 }
257
258 return;
259 }
260
261 LdifEntry forward = new LdifEntry();
262 forward.setChangeType( ChangeType.Modify );
263 forward.setDn( opContext.getDn() );
264
265 List<Modification> mods = new ArrayList<Modification>( clonedMods.size() );
266
267 for ( Modification modItem : clonedMods )
268 {
269 Modification mod = ((ServerModification)modItem).toClientModification();
270
271 // TODO: handle correctly http://issues.apache.org/jira/browse/DIRSERVER-1198
272 mod.getAttribute().setId( modItem.getAttribute().getId() );
273 mods.add( mod );
274
275 forward.addModificationItem( mod );
276 }
277
278 Entry clientEntry = new DefaultClientEntry( serverEntry.getDn() );
279
280 for ( EntryAttribute attribute:serverEntry )
281 {
282 clientEntry.add( attribute.toClientAttribute() );
283 }
284
285 LdifEntry reverse = LdifRevertor.reverseModify(
286 opContext.getDn(),
287 mods,
288 clientEntry );
289
290 opContext.setChangeLogEvent( changeLog.log( getPrincipal(), forward, reverse ) );
291 }
292
293
294 // -----------------------------------------------------------------------
295 // Though part left as an exercise (Not Any More!)
296 // -----------------------------------------------------------------------
297
298
299 public void rename ( NextInterceptor next, RenameOperationContext renameContext ) throws Exception
300 {
301 ServerEntry serverEntry = null;
302
303 if ( renameContext.getEntry() != null )
304 {
305 serverEntry = renameContext.getEntry().getOriginalEntry();
306 }
307
308 next.rename( renameContext );
309
310 // After this point, the entry has been modified. The cloned entry contains
311 // the modified entry, the originalEntry has changed
312
313 if ( ! changeLog.isEnabled() || ! renameContext.isFirstOperation() )
314 {
315 return;
316 }
317
318 LdifEntry forward = new LdifEntry();
319 forward.setChangeType( ChangeType.ModRdn );
320 forward.setDn( renameContext.getDn() );
321 forward.setNewRdn( renameContext.getNewRdn().getName() );
322 forward.setDeleteOldRdn( renameContext.getDelOldDn() );
323
324 List<LdifEntry> reverses = LdifRevertor.reverseRename(
325 serverEntry, renameContext.getNewRdn(), renameContext.getDelOldDn() );
326
327 renameContext.setChangeLogEvent( changeLog.log( getPrincipal(), forward, reverses ) );
328 }
329
330
331 public void moveAndRename( NextInterceptor next, MoveAndRenameOperationContext opCtx )
332 throws Exception
333 {
334 ClonedServerEntry serverEntry = null;
335
336 if ( changeLog.isEnabled() && opCtx.isFirstOperation() )
337 {
338 // @todo make sure we're not putting in operational attributes that cannot be user modified
339 serverEntry = opCtx.lookup( opCtx.getDn(), ByPassConstants.LOOKUP_BYPASS );
340 }
341
342 next.moveAndRename( opCtx );
343
344 if ( ! changeLog.isEnabled() || ! opCtx.isFirstOperation() )
345 {
346 return;
347 }
348
349 LdifEntry forward = new LdifEntry();
350 forward.setChangeType( ChangeType.ModDn );
351 forward.setDn( opCtx.getDn() );
352 forward.setDeleteOldRdn( opCtx.getDelOldDn() );
353 forward.setNewRdn( opCtx.getNewRdn().getName() );
354 forward.setNewSuperior( opCtx.getParent().getName() );
355
356 List<LdifEntry> reverses = LdifRevertor.reverseMoveAndRename(
357 serverEntry, opCtx.getParent(), new RDN( opCtx.getNewRdn() ), false );
358 opCtx.setChangeLogEvent( changeLog.log( getPrincipal(), forward, reverses ) );
359 }
360
361
362 public void move ( NextInterceptor next, MoveOperationContext opCtx ) throws Exception
363 {
364 next.move( opCtx );
365
366 if ( ! changeLog.isEnabled() || ! opCtx.isFirstOperation() )
367 {
368 return;
369 }
370
371 LdifEntry forward = new LdifEntry();
372 forward.setChangeType( ChangeType.ModDn );
373 forward.setDn( opCtx.getDn() );
374 forward.setNewSuperior( opCtx.getParent().getName() );
375
376 LdifEntry reverse = LdifRevertor.reverseMove( opCtx.getParent(), opCtx.getDn() );
377 opCtx.setChangeLogEvent( changeLog.log( getPrincipal(), forward, reverse ) );
378 }
379 }