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.operational; |
21 |
|
22 |
import java.util.ArrayList; |
23 |
import java.util.HashSet; |
24 |
import java.util.Iterator; |
25 |
import java.util.List; |
26 |
import java.util.Set; |
27 |
|
28 |
import org.apache.directory.server.constants.ApacheSchemaConstants; |
29 |
import org.apache.directory.server.core.DirectoryService; |
30 |
import org.apache.directory.server.core.entry.DefaultServerAttribute; |
31 |
import org.apache.directory.server.core.entry.DefaultServerEntry; |
32 |
import org.apache.directory.server.core.entry.ServerAttribute; |
33 |
import org.apache.directory.server.core.entry.ServerEntry; |
34 |
import org.apache.directory.server.core.entry.ServerModification; |
35 |
import org.apache.directory.server.core.entry.ServerSearchResult; |
36 |
import org.apache.directory.server.core.enumeration.SearchResultFilter; |
37 |
import org.apache.directory.server.core.enumeration.SearchResultFilteringEnumeration; |
38 |
import org.apache.directory.server.core.interceptor.BaseInterceptor; |
39 |
import org.apache.directory.server.core.interceptor.Interceptor; |
40 |
import org.apache.directory.server.core.interceptor.NextInterceptor; |
41 |
import org.apache.directory.server.core.interceptor.context.AddOperationContext; |
42 |
import org.apache.directory.server.core.interceptor.context.ListOperationContext; |
43 |
import org.apache.directory.server.core.interceptor.context.LookupOperationContext; |
44 |
import org.apache.directory.server.core.interceptor.context.ModifyOperationContext; |
45 |
import org.apache.directory.server.core.interceptor.context.MoveAndRenameOperationContext; |
46 |
import org.apache.directory.server.core.interceptor.context.MoveOperationContext; |
47 |
import org.apache.directory.server.core.interceptor.context.RenameOperationContext; |
48 |
import org.apache.directory.server.core.interceptor.context.SearchOperationContext; |
49 |
import org.apache.directory.server.core.invocation.Invocation; |
50 |
import org.apache.directory.server.core.invocation.InvocationStack; |
51 |
import org.apache.directory.server.schema.registries.AttributeTypeRegistry; |
52 |
import org.apache.directory.server.schema.registries.Registries; |
53 |
import org.apache.directory.shared.ldap.constants.SchemaConstants; |
54 |
import org.apache.directory.shared.ldap.entry.EntryAttribute; |
55 |
import org.apache.directory.shared.ldap.entry.Modification; |
56 |
import org.apache.directory.shared.ldap.entry.ModificationOperation; |
57 |
import org.apache.directory.shared.ldap.entry.Value; |
58 |
import org.apache.directory.shared.ldap.name.AttributeTypeAndValue; |
59 |
import org.apache.directory.shared.ldap.name.LdapDN; |
60 |
import org.apache.directory.shared.ldap.name.Rdn; |
61 |
import org.apache.directory.shared.ldap.schema.AttributeType; |
62 |
import org.apache.directory.shared.ldap.schema.UsageEnum; |
63 |
import org.apache.directory.shared.ldap.util.DateUtils; |
64 |
|
65 |
import javax.naming.NamingEnumeration; |
66 |
import javax.naming.NamingException; |
67 |
import javax.naming.directory.SearchControls; |
68 |
|
69 |
|
70 |
/** |
71 |
* An {@link Interceptor} that adds or modifies the default attributes |
72 |
* of entries. There are four default attributes for now; |
73 |
* <tt>'creatorsName'</tt>, <tt>'createTimestamp'</tt>, <tt>'modifiersName'</tt>, |
74 |
* and <tt>'modifyTimestamp'</tt>. |
75 |
* |
76 |
* @org.apache.xbean.XBean |
77 |
* |
78 |
* @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a> |
79 |
* @version $Rev$, $Date$ |
80 |
*/ |
81 |
public class OperationalAttributeInterceptor extends BaseInterceptor |
82 |
{ |
83 |
private final SearchResultFilter DENORMALIZING_SEARCH_FILTER = new SearchResultFilter() |
84 |
{ |
85 |
public boolean accept( Invocation invocation, ServerSearchResult result, SearchControls controls ) |
86 |
throws NamingException |
87 |
{ |
88 |
ServerEntry serverEntry = result.getServerEntry(); |
89 |
|
90 |
if ( controls.getReturningAttributes() == null ) |
91 |
{ |
92 |
return true; |
93 |
} |
94 |
|
95 |
boolean denormalized = filterDenormalized( serverEntry ); |
96 |
|
97 |
result.setServerEntry( serverEntry ); |
98 |
|
99 |
return denormalized; |
100 |
} |
101 |
}; |
102 |
|
103 |
/** |
104 |
* the database search result filter to register with filter service |
105 |
*/ |
106 |
private final SearchResultFilter SEARCH_FILTER = new SearchResultFilter() |
107 |
{ |
108 |
public boolean accept( Invocation invocation, ServerSearchResult result, SearchControls controls ) |
109 |
throws NamingException |
110 |
{ |
111 |
ServerEntry serverEntry = result.getServerEntry(); |
112 |
|
113 |
return controls.getReturningAttributes() != null || filterOperationalAttributes( serverEntry ); |
114 |
} |
115 |
}; |
116 |
|
117 |
|
118 |
private AttributeTypeRegistry atRegistry; |
119 |
|
120 |
private DirectoryService service; |
121 |
|
122 |
private LdapDN subschemaSubentryDn; |
123 |
|
124 |
private Registries registries; |
125 |
|
126 |
|
127 |
/** |
128 |
* Creates the operational attribute management service interceptor. |
129 |
*/ |
130 |
public OperationalAttributeInterceptor() |
131 |
{ |
132 |
} |
133 |
|
134 |
|
135 |
public void init( DirectoryService directoryService ) throws NamingException |
136 |
{ |
137 |
service = directoryService; |
138 |
registries = directoryService.getRegistries(); |
139 |
atRegistry = registries.getAttributeTypeRegistry(); |
140 |
|
141 |
// stuff for dealing with subentries (garbage for now) |
142 |
Value<?> subschemaSubentry = service.getPartitionNexus() |
143 |
.getRootDSE( null ).get( SchemaConstants.SUBSCHEMA_SUBENTRY_AT ).get(); |
144 |
subschemaSubentryDn = new LdapDN( (String)subschemaSubentry.get() ); |
145 |
subschemaSubentryDn.normalize( directoryService.getRegistries().getAttributeTypeRegistry().getNormalizerMapping() ); |
146 |
} |
147 |
|
148 |
|
149 |
public void destroy() |
150 |
{ |
151 |
} |
152 |
|
153 |
|
154 |
/** |
155 |
* Adds extra operational attributes to the entry before it is added. |
156 |
*/ |
157 |
public void add( NextInterceptor nextInterceptor, AddOperationContext opContext ) |
158 |
throws NamingException |
159 |
{ |
160 |
String principal = getPrincipal().getName(); |
161 |
|
162 |
ServerEntry entry = opContext.getEntry(); |
163 |
|
164 |
entry.put( SchemaConstants.CREATORS_NAME_AT, principal ); |
165 |
entry.put( SchemaConstants.CREATE_TIMESTAMP_AT, DateUtils.getGeneralizedTime() ); |
166 |
|
167 |
nextInterceptor.add( opContext ); |
168 |
} |
169 |
|
170 |
|
171 |
public void modify( NextInterceptor nextInterceptor, ModifyOperationContext opContext ) |
172 |
throws NamingException |
173 |
{ |
174 |
nextInterceptor.modify( opContext ); |
175 |
|
176 |
if ( opContext.getDn().getNormName().equals( subschemaSubentryDn.getNormName() ) ) |
177 |
{ |
178 |
return; |
179 |
} |
180 |
|
181 |
// ------------------------------------------------------------------- |
182 |
// Add the operational attributes for the modifier first |
183 |
// ------------------------------------------------------------------- |
184 |
|
185 |
List<Modification> modItemList = new ArrayList<Modification>(2); |
186 |
|
187 |
AttributeType modifiersNameAt = atRegistry.lookup( SchemaConstants.MODIFIERS_NAME_AT ); |
188 |
ServerAttribute attribute = new DefaultServerAttribute( |
189 |
SchemaConstants.MODIFIERS_NAME_AT, |
190 |
modifiersNameAt, |
191 |
getPrincipal().getName()); |
192 |
|
193 |
Modification modifiers = new ServerModification( ModificationOperation.REPLACE_ATTRIBUTE, attribute ); |
194 |
//modifiers.setServerModified(); |
195 |
modItemList.add( modifiers ); |
196 |
|
197 |
AttributeType modifyTimeStampAt = atRegistry.lookup( SchemaConstants.MODIFY_TIMESTAMP_AT ); |
198 |
attribute = new DefaultServerAttribute( |
199 |
SchemaConstants.MODIFY_TIMESTAMP_AT, |
200 |
modifyTimeStampAt, |
201 |
DateUtils.getGeneralizedTime() ); |
202 |
|
203 |
Modification timestamp = new ServerModification( ModificationOperation.REPLACE_ATTRIBUTE, attribute ); |
204 |
//timestamp.setServerModified(); |
205 |
modItemList.add( timestamp ); |
206 |
|
207 |
// ------------------------------------------------------------------- |
208 |
// Make the modify() call happen |
209 |
// ------------------------------------------------------------------- |
210 |
|
211 |
ModifyOperationContext newModify = new ModifyOperationContext( registries, opContext.getDn(), modItemList ); |
212 |
service.getPartitionNexus().modify( newModify ); |
213 |
} |
214 |
|
215 |
|
216 |
public void rename( NextInterceptor nextInterceptor, RenameOperationContext opContext ) |
217 |
throws NamingException |
218 |
{ |
219 |
nextInterceptor.rename( opContext ); |
220 |
|
221 |
// add operational attributes after call in case the operation fails |
222 |
ServerEntry serverEntry = new DefaultServerEntry( registries, opContext.getDn() ); |
223 |
serverEntry.put( SchemaConstants.MODIFIERS_NAME_AT, getPrincipal().getName() ); |
224 |
serverEntry.put( SchemaConstants.MODIFY_TIMESTAMP_AT, DateUtils.getGeneralizedTime() ); |
225 |
|
226 |
LdapDN newDn = ( LdapDN ) opContext.getDn().clone(); |
227 |
newDn.remove( opContext.getDn().size() - 1 ); |
228 |
newDn.add( opContext.getNewRdn() ); |
229 |
newDn.normalize( atRegistry.getNormalizerMapping() ); |
230 |
|
231 |
List<Modification> items = ModifyOperationContext.createModItems( serverEntry, ModificationOperation.REPLACE_ATTRIBUTE ); |
232 |
|
233 |
ModifyOperationContext newModify = new ModifyOperationContext( registries, newDn, items ); |
234 |
|
235 |
service.getPartitionNexus().modify( newModify ); |
236 |
} |
237 |
|
238 |
|
239 |
public void move( NextInterceptor nextInterceptor, MoveOperationContext opContext ) throws NamingException |
240 |
{ |
241 |
nextInterceptor.move( opContext ); |
242 |
|
243 |
// add operational attributes after call in case the operation fails |
244 |
ServerEntry serverEntry = new DefaultServerEntry( registries, opContext.getDn() ); |
245 |
serverEntry.put( SchemaConstants.MODIFIERS_NAME_AT, getPrincipal().getName() ); |
246 |
serverEntry.put( SchemaConstants.MODIFY_TIMESTAMP_AT, DateUtils.getGeneralizedTime() ); |
247 |
|
248 |
List<Modification> items = ModifyOperationContext.createModItems( serverEntry, ModificationOperation.REPLACE_ATTRIBUTE ); |
249 |
|
250 |
|
251 |
ModifyOperationContext newModify = |
252 |
new ModifyOperationContext( registries, opContext.getParent(), items ); |
253 |
|
254 |
service.getPartitionNexus().modify( newModify ); |
255 |
} |
256 |
|
257 |
|
258 |
public void moveAndRename( NextInterceptor nextInterceptor, MoveAndRenameOperationContext opContext ) |
259 |
throws NamingException |
260 |
{ |
261 |
nextInterceptor.moveAndRename( opContext ); |
262 |
|
263 |
// add operational attributes after call in case the operation fails |
264 |
ServerEntry serverEntry = new DefaultServerEntry( registries, opContext.getDn() ); |
265 |
serverEntry.put( SchemaConstants.MODIFIERS_NAME_AT, getPrincipal().getName() ); |
266 |
serverEntry.put( SchemaConstants.MODIFY_TIMESTAMP_AT, DateUtils.getGeneralizedTime() ); |
267 |
|
268 |
List<Modification> items = ModifyOperationContext.createModItems( serverEntry, ModificationOperation.REPLACE_ATTRIBUTE ); |
269 |
|
270 |
ModifyOperationContext newModify = |
271 |
new ModifyOperationContext( registries, |
272 |
opContext.getParent(), items ); |
273 |
|
274 |
service.getPartitionNexus().modify( newModify ); |
275 |
} |
276 |
|
277 |
|
278 |
public ServerEntry lookup( NextInterceptor nextInterceptor, LookupOperationContext opContext ) throws NamingException |
279 |
{ |
280 |
ServerEntry result = nextInterceptor.lookup( opContext ); |
281 |
|
282 |
if ( result == null ) |
283 |
{ |
284 |
return null; |
285 |
} |
286 |
|
287 |
if ( opContext.getAttrsId() == null ) |
288 |
{ |
289 |
filterOperationalAttributes( result ); |
290 |
} |
291 |
else |
292 |
{ |
293 |
filter( opContext, result ); |
294 |
} |
295 |
|
296 |
return result; |
297 |
} |
298 |
|
299 |
|
300 |
public NamingEnumeration<ServerSearchResult> list( NextInterceptor nextInterceptor, ListOperationContext opContext ) throws NamingException |
301 |
{ |
302 |
NamingEnumeration<ServerSearchResult> result = nextInterceptor.list( opContext ); |
303 |
Invocation invocation = InvocationStack.getInstance().peek(); |
304 |
|
305 |
return new SearchResultFilteringEnumeration( result, new SearchControls(), invocation, SEARCH_FILTER, "List Operational Filter" ); |
306 |
} |
307 |
|
308 |
|
309 |
public NamingEnumeration<ServerSearchResult> search( NextInterceptor nextInterceptor, SearchOperationContext opContext ) throws NamingException |
310 |
{ |
311 |
Invocation invocation = InvocationStack.getInstance().peek(); |
312 |
NamingEnumeration<ServerSearchResult> result = nextInterceptor.search( opContext ); |
313 |
SearchControls searchCtls = opContext.getSearchControls(); |
314 |
|
315 |
if ( searchCtls.getReturningAttributes() != null ) |
316 |
{ |
317 |
if ( service.isDenormalizeOpAttrsEnabled() ) |
318 |
{ |
319 |
return new SearchResultFilteringEnumeration( result, searchCtls, invocation, DENORMALIZING_SEARCH_FILTER, "Search Operational Filter denormalized" ); |
320 |
} |
321 |
|
322 |
return result; |
323 |
} |
324 |
|
325 |
return new SearchResultFilteringEnumeration( result, searchCtls, invocation, SEARCH_FILTER , "Search Operational Filter"); |
326 |
} |
327 |
|
328 |
|
329 |
/** |
330 |
* Filters out the operational attributes within a search results attributes. The attributes are directly |
331 |
* modified. |
332 |
* |
333 |
* @param attributes the resultant attributes to filter |
334 |
* @return true always |
335 |
* @throws NamingException if there are failures in evaluation |
336 |
*/ |
337 |
private boolean filterOperationalAttributes( ServerEntry attributes ) throws NamingException |
338 |
{ |
339 |
Set<AttributeType> removedAttributes = new HashSet<AttributeType>(); |
340 |
|
341 |
// Build a list of attributeType to remove |
342 |
for ( AttributeType attributeType:attributes.getAttributeTypes() ) |
343 |
{ |
344 |
if ( attributeType.getUsage() != UsageEnum.USER_APPLICATIONS ) |
345 |
{ |
346 |
removedAttributes.add( attributeType ); |
347 |
} |
348 |
} |
349 |
|
350 |
// Now remove the attributes which are not USERs |
351 |
for ( AttributeType attributeType:removedAttributes ) |
352 |
{ |
353 |
attributes.removeAttributes( attributeType ); |
354 |
} |
355 |
|
356 |
return true; |
357 |
} |
358 |
|
359 |
|
360 |
private void filter( LookupOperationContext lookupContext, ServerEntry entry ) throws NamingException |
361 |
{ |
362 |
LdapDN dn = lookupContext.getDn(); |
363 |
List<String> ids = lookupContext.getAttrsId(); |
364 |
|
365 |
// still need to protect against returning op attrs when ids is null |
366 |
if ( ids == null ) |
367 |
{ |
368 |
filterOperationalAttributes( entry ); |
369 |
return; |
370 |
} |
371 |
|
372 |
Set<AttributeType> attributeTypes = entry.getAttributeTypes(); |
373 |
|
374 |
if ( dn.size() == 0 ) |
375 |
{ |
376 |
for ( AttributeType attributeType:attributeTypes ) |
377 |
{ |
378 |
if ( !ids.contains( attributeType.getOid() ) ) |
379 |
{ |
380 |
entry.removeAttributes( attributeType ); |
381 |
} |
382 |
} |
383 |
} |
384 |
|
385 |
denormalizeEntryOpAttrs( entry ); |
386 |
|
387 |
// do nothing past here since this explicity specifies which |
388 |
// attributes to include - backends will automatically populate |
389 |
// with right set of attributes using ids array |
390 |
} |
391 |
|
392 |
|
393 |
public void denormalizeEntryOpAttrs( ServerEntry entry ) throws NamingException |
394 |
{ |
395 |
if ( service.isDenormalizeOpAttrsEnabled() ) |
396 |
{ |
397 |
EntryAttribute attr = entry.get( SchemaConstants.CREATORS_NAME_AT ); |
398 |
|
399 |
if ( attr != null ) |
400 |
{ |
401 |
LdapDN creatorsName = new LdapDN( attr.getString() ); |
402 |
|
403 |
attr.clear(); |
404 |
attr.add( denormalizeTypes( creatorsName ).getUpName() ); |
405 |
} |
406 |
|
407 |
attr = entry.get( SchemaConstants.MODIFIERS_NAME_AT ); |
408 |
|
409 |
if ( attr != null ) |
410 |
{ |
411 |
LdapDN modifiersName = new LdapDN( attr.getString() ); |
412 |
|
413 |
attr.clear(); |
414 |
attr.add( denormalizeTypes( modifiersName ).getUpName() ); |
415 |
} |
416 |
|
417 |
attr = entry.get( ApacheSchemaConstants.SCHEMA_MODIFIERS_NAME_AT ); |
418 |
|
419 |
if ( attr != null ) |
420 |
{ |
421 |
LdapDN modifiersName = new LdapDN( attr.getString() ); |
422 |
|
423 |
attr.clear(); |
424 |
attr.add( denormalizeTypes( modifiersName ).getUpName() ); |
425 |
} |
426 |
} |
427 |
} |
428 |
|
429 |
|
430 |
/** |
431 |
* Does not create a new DN but alters existing DN by using the first |
432 |
* short name for an attributeType definition. |
433 |
* |
434 |
* @param dn the normalized distinguished name |
435 |
* @return the distinuished name denormalized |
436 |
* @throws NamingException if there are problems denormalizing |
437 |
*/ |
438 |
public LdapDN denormalizeTypes( LdapDN dn ) throws NamingException |
439 |
{ |
440 |
LdapDN newDn = new LdapDN(); |
441 |
|
442 |
for ( int ii = 0; ii < dn.size(); ii++ ) |
443 |
{ |
444 |
Rdn rdn = dn.getRdn( ii ); |
445 |
if ( rdn.size() == 0 ) |
446 |
{ |
447 |
newDn.add( new Rdn() ); |
448 |
continue; |
449 |
} |
450 |
else if ( rdn.size() == 1 ) |
451 |
{ |
452 |
String name = atRegistry.lookup( rdn.getNormType() ).getName(); |
453 |
String value = (String)rdn.getAtav().getNormValue(); |
454 |
newDn.add( new Rdn( name, name, value, value ) ); |
455 |
continue; |
456 |
} |
457 |
|
458 |
// below we only process multi-valued rdns |
459 |
StringBuffer buf = new StringBuffer(); |
460 |
|
461 |
for ( Iterator<AttributeTypeAndValue> atavs = rdn.iterator(); atavs.hasNext(); /**/ ) |
462 |
{ |
463 |
AttributeTypeAndValue atav = atavs.next(); |
464 |
String type = atRegistry.lookup( rdn.getNormType() ).getName(); |
465 |
buf.append( type ).append( '=' ).append( atav.getNormValue() ); |
466 |
|
467 |
if ( atavs.hasNext() ) |
468 |
{ |
469 |
buf.append( '+' ); |
470 |
} |
471 |
} |
472 |
|
473 |
newDn.add( new Rdn(buf.toString()) ); |
474 |
} |
475 |
|
476 |
return newDn; |
477 |
} |
478 |
|
479 |
|
480 |
private boolean filterDenormalized( ServerEntry entry ) throws NamingException |
481 |
{ |
482 |
denormalizeEntryOpAttrs( entry ); |
483 |
return true; |
484 |
} |
485 |
} |