001    /*
002     * CDDL HEADER START
003     *
004     * The contents of this file are subject to the terms of the
005     * Common Development and Distribution License, Version 1.0 only
006     * (the "License").  You may not use this file except in compliance
007     * with the License.
008     *
009     * You can obtain a copy of the license at
010     * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011     * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012     * See the License for the specific language governing permissions
013     * and limitations under the License.
014     *
015     * When distributing Covered Code, include this CDDL HEADER in each
016     * file and include the License file at
017     * trunk/opends/resource/legal-notices/OpenDS.LICENSE.  If applicable,
018     * add the following below this CDDL HEADER, with the fields enclosed
019     * by brackets "[]" replaced with your own identifying information:
020     *      Portions Copyright [yyyy] [name of copyright owner]
021     *
022     * CDDL HEADER END
023     *
024     *
025     *      Copyright 2006-2008 Sun Microsystems, Inc.
026     */
027    package org.opends.server.plugins;
028    
029    
030    
031    import java.util.ArrayList;
032    import java.util.LinkedHashSet;
033    import java.util.List;
034    import java.util.Set;
035    
036    import org.opends.messages.Message;
037    import org.opends.server.admin.server.ConfigurationChangeListener;
038    import org.opends.server.admin.std.meta.PluginCfgDefn;
039    import org.opends.server.admin.std.server.LastModPluginCfg;
040    import org.opends.server.admin.std.server.PluginCfg;
041    import org.opends.server.api.plugin.DirectoryServerPlugin;
042    import org.opends.server.api.plugin.PluginType;
043    import org.opends.server.api.plugin.PluginResult;
044    import org.opends.server.config.ConfigException;
045    import org.opends.server.loggers.debug.DebugTracer;
046    import org.opends.server.types.Attribute;
047    import org.opends.server.types.AttributeType;
048    import org.opends.server.types.AttributeValue;
049    import org.opends.server.types.ByteStringFactory;
050    import org.opends.server.types.ConfigChangeResult;
051    import org.opends.server.types.DebugLogLevel;
052    import org.opends.server.types.DirectoryConfig;
053    import org.opends.server.types.DirectoryException;
054    import org.opends.server.types.DN;
055    import org.opends.server.types.Modification;
056    import org.opends.server.types.ModificationType;
057    import org.opends.server.types.ResultCode;
058    import org.opends.server.types.operation.PreOperationAddOperation;
059    import org.opends.server.types.operation.PreOperationModifyOperation;
060    import org.opends.server.types.operation.PreOperationModifyDNOperation;
061    
062    import static org.opends.messages.PluginMessages.*;
063    import static org.opends.server.config.ConfigConstants.*;
064    import static org.opends.server.loggers.debug.DebugLogger.*;
065    import static org.opends.server.util.TimeThread.*;
066    
067    
068    
069    /**
070     * This class implements a Directory Server plugin that will add the
071     * creatorsName and createTimestamp attributes to an entry whenever it is added
072     * to the server, and will add the modifiersName and modifyTimestamp attributes
073     * whenever the entry is modified or renamed.
074     */
075    public final class LastModPlugin
076           extends DirectoryServerPlugin<LastModPluginCfg>
077           implements ConfigurationChangeListener<LastModPluginCfg>
078    {
079      /**
080       * The tracer object for the debug logger.
081       */
082      private static final DebugTracer TRACER = getTracer();
083    
084      // The attribute type for the "createTimestamp" attribute.
085      private final AttributeType createTimestampType;
086    
087      // The attribute type for the "creatorsName" attribute.
088      private final AttributeType creatorsNameType;
089    
090      // The attribute type for the "modifiersName" attribute.
091      private final AttributeType modifiersNameType;
092    
093      // The attribute type for the "modifyTimestamp" attribute.
094      private final AttributeType modifyTimestampType;
095    
096      // The current configuration for this plugin.
097      private LastModPluginCfg currentConfig;
098    
099    
100    
101      /**
102       * Creates a new instance of this Directory Server plugin.  Every plugin must
103       * implement a default constructor (it is the only one that will be used to
104       * create plugins defined in the configuration), and every plugin constructor
105       * must call <CODE>super()</CODE> as its first element.
106       */
107      public LastModPlugin()
108      {
109        super();
110    
111    
112        // Get the attribute types for the attributes that we will use.  This needs
113        // to be done in the constructor in order to make the associated variables
114        // "final".
115        createTimestampType =
116             DirectoryConfig.getAttributeType(OP_ATTR_CREATE_TIMESTAMP_LC, true);
117        creatorsNameType =
118             DirectoryConfig.getAttributeType(OP_ATTR_CREATORS_NAME_LC, true);
119        modifiersNameType =
120             DirectoryConfig.getAttributeType(OP_ATTR_MODIFIERS_NAME_LC, true);
121        modifyTimestampType =
122             DirectoryConfig.getAttributeType(OP_ATTR_MODIFY_TIMESTAMP_LC, true);
123      }
124    
125    
126    
127      /**
128       * {@inheritDoc}
129       */
130      @Override()
131      public final void initializePlugin(Set<PluginType> pluginTypes,
132                                         LastModPluginCfg configuration)
133             throws ConfigException
134      {
135        currentConfig = configuration;
136        configuration.addLastModChangeListener(this);
137    
138        // Make sure that the plugin has been enabled for the appropriate types.
139        for (PluginType t : pluginTypes)
140        {
141          switch (t)
142          {
143            case PRE_OPERATION_ADD:
144            case PRE_OPERATION_MODIFY:
145            case PRE_OPERATION_MODIFY_DN:
146              // These are acceptable.
147              break;
148    
149    
150            default:
151              Message message =
152                  ERR_PLUGIN_LASTMOD_INVALID_PLUGIN_TYPE.get(t.toString());
153              throw new ConfigException(message);
154          }
155        }
156      }
157    
158    
159    
160      /**
161       * {@inheritDoc}
162       */
163      @Override()
164      public final void finalizePlugin()
165      {
166        currentConfig.removeLastModChangeListener(this);
167      }
168    
169    
170    
171      /**
172       * {@inheritDoc}
173       */
174      @Override()
175      public final PluginResult.PreOperation
176                   doPreOperation(PreOperationAddOperation addOperation)
177      {
178        // Create the attribute list for the creatorsName attribute, if appropriate.
179        DN creatorDN = addOperation.getAuthorizationDN();
180        LinkedHashSet<AttributeValue> nameValues =
181             new LinkedHashSet<AttributeValue>(1);
182        if (creatorDN == null)
183        {
184          // This must mean that the operation was performed anonymously.  Even so,
185          // we still need to update the creatorsName attribute.
186          nameValues.add(new AttributeValue(creatorsNameType,
187                                            ByteStringFactory.create()));
188        }
189        else
190        {
191          nameValues.add(new AttributeValue(creatorsNameType,
192               ByteStringFactory.create(creatorDN.toString())));
193        }
194        Attribute nameAttr = new Attribute(creatorsNameType, OP_ATTR_CREATORS_NAME,
195                                           nameValues);
196        ArrayList<Attribute> nameList = new ArrayList<Attribute>(1);
197        nameList.add(nameAttr);
198        addOperation.setAttribute(creatorsNameType, nameList);
199    
200    
201        //  Create the attribute list for the createTimestamp attribute.
202        LinkedHashSet<AttributeValue> timeValues =
203             new LinkedHashSet<AttributeValue>(1);
204        timeValues.add(new AttributeValue(createTimestampType,
205                                          ByteStringFactory.create(getGMTTime())));
206    
207        Attribute timeAttr = new Attribute(createTimestampType,
208                                           OP_ATTR_CREATE_TIMESTAMP, timeValues);
209        ArrayList<Attribute> timeList = new ArrayList<Attribute>(1);
210        timeList.add(timeAttr);
211        addOperation.setAttribute(createTimestampType, timeList);
212    
213    
214        // We shouldn't ever need to return a non-success result.
215        return PluginResult.PreOperation.continueOperationProcessing();
216      }
217    
218    
219    
220      /**
221       * {@inheritDoc}
222       */
223      @Override()
224      public final PluginResult.PreOperation
225           doPreOperation(PreOperationModifyOperation modifyOperation)
226      {
227        // Create the modifiersName attribute.
228        DN modifierDN = modifyOperation.getAuthorizationDN();
229        LinkedHashSet<AttributeValue> nameValues =
230             new LinkedHashSet<AttributeValue>(1);
231        if (modifierDN == null)
232        {
233          // This must mean that the operation was performed anonymously.  Even so,
234          // we still need to update the modifiersName attribute.
235          nameValues.add(new AttributeValue(modifiersNameType,
236                                            ByteStringFactory.create()));
237        }
238        else
239        {
240          nameValues.add(new AttributeValue(modifiersNameType,
241               ByteStringFactory.create(modifierDN.toString())));
242        }
243        Attribute nameAttr = new Attribute(modifiersNameType,
244                                           OP_ATTR_MODIFIERS_NAME, nameValues);
245        try
246        {
247          modifyOperation.addModification(new Modification(ModificationType.REPLACE,
248                                                           nameAttr, true));
249        }
250        catch (DirectoryException de)
251        {
252          if (debugEnabled())
253          {
254            TRACER.debugCaught(DebugLogLevel.ERROR, de);
255          }
256    
257          // This should never happen.
258          return PluginResult.PreOperation.stopProcessing(
259              DirectoryConfig.getServerErrorResultCode(), de.getMessageObject());
260        }
261    
262    
263        //  Create the modifyTimestamp attribute.
264        LinkedHashSet<AttributeValue> timeValues =
265             new LinkedHashSet<AttributeValue>(1);
266        timeValues.add(new AttributeValue(modifyTimestampType,
267                                          ByteStringFactory.create(getGMTTime())));
268    
269        Attribute timeAttr = new Attribute(modifyTimestampType,
270                                           OP_ATTR_MODIFY_TIMESTAMP, timeValues);
271        try
272        {
273          modifyOperation.addModification(new Modification(ModificationType.REPLACE,
274                                                           timeAttr, true));
275        }
276        catch (DirectoryException de)
277        {
278          if (debugEnabled())
279          {
280            TRACER.debugCaught(DebugLogLevel.ERROR, de);
281          }
282    
283          // This should never happen.
284          return PluginResult.PreOperation.stopProcessing(
285              DirectoryConfig.getServerErrorResultCode(), de.getMessageObject());
286        }
287    
288    
289        // We shouldn't ever need to return a non-success result.
290        return PluginResult.PreOperation.continueOperationProcessing();
291      }
292    
293    
294    
295      /**
296       * {@inheritDoc}
297       */
298      @Override()
299      public final PluginResult.PreOperation
300           doPreOperation(PreOperationModifyDNOperation modifyDNOperation)
301      {
302        // Create the modifiersName attribute.
303        DN modifierDN = modifyDNOperation.getAuthorizationDN();
304        LinkedHashSet<AttributeValue> nameValues =
305             new LinkedHashSet<AttributeValue>(1);
306        if (modifierDN == null)
307        {
308          // This must mean that the operation was performed anonymously.  Even so,
309          // we still need to update the modifiersName attribute.
310          nameValues.add(new AttributeValue(modifiersNameType,
311                                            ByteStringFactory.create()));
312        }
313        else
314        {
315          nameValues.add(new AttributeValue(modifiersNameType,
316               ByteStringFactory.create(modifierDN.toString())));
317        }
318        Attribute nameAttr = new Attribute(modifiersNameType,
319                                           OP_ATTR_MODIFIERS_NAME, nameValues);
320        modifyDNOperation.addModification(new Modification(ModificationType.REPLACE,
321                                                           nameAttr, true));
322    
323    
324        //  Create the modifyTimestamp attribute.
325        LinkedHashSet<AttributeValue> timeValues =
326             new LinkedHashSet<AttributeValue>(1);
327        timeValues.add(new AttributeValue(modifyTimestampType,
328                                          ByteStringFactory.create(getGMTTime())));
329    
330        Attribute timeAttr = new Attribute(modifyTimestampType,
331                                           OP_ATTR_MODIFY_TIMESTAMP, timeValues);
332        modifyDNOperation.addModification(new Modification(ModificationType.REPLACE,
333                                                           timeAttr, true));
334    
335    
336        // We shouldn't ever need to return a non-success result.
337        return PluginResult.PreOperation.continueOperationProcessing();
338      }
339    
340    
341    
342      /**
343       * {@inheritDoc}
344       */
345      @Override()
346      public boolean isConfigurationAcceptable(PluginCfg configuration,
347                                               List<Message> unacceptableReasons)
348      {
349        LastModPluginCfg cfg = (LastModPluginCfg) configuration;
350        return isConfigurationChangeAcceptable(cfg, unacceptableReasons);
351      }
352    
353    
354    
355      /**
356       * {@inheritDoc}
357       */
358      public boolean isConfigurationChangeAcceptable(LastModPluginCfg configuration,
359                          List<Message> unacceptableReasons)
360      {
361        boolean configAcceptable = true;
362    
363        // Ensure that the set of plugin types contains only pre-operation add,
364        // pre-operation modify, and pre-operation modify DN.
365        for (PluginCfgDefn.PluginType pluginType : configuration.getPluginType())
366        {
367          switch (pluginType)
368          {
369            case PREOPERATIONADD:
370            case PREOPERATIONMODIFY:
371            case PREOPERATIONMODIFYDN:
372              // These are acceptable.
373              break;
374    
375    
376            default:
377              Message message = ERR_PLUGIN_LASTMOD_INVALID_PLUGIN_TYPE.get(
378                      pluginType.toString());
379              unacceptableReasons.add(message);
380              configAcceptable = false;
381          }
382        }
383    
384        return configAcceptable;
385      }
386    
387    
388    
389      /**
390       * {@inheritDoc}
391       */
392      public ConfigChangeResult applyConfigurationChange(
393                                     LastModPluginCfg configuration)
394      {
395        currentConfig = configuration;
396        return new ConfigChangeResult(ResultCode.SUCCESS, false);
397      }
398    }
399