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 legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2006-2008 Sun Microsystems, Inc.
025 *      Portions Copyright 2014-2015 ForgeRock AS
026 */
027package org.opends.server.plugins;
028
029import java.util.List;
030import java.util.Set;
031
032import org.forgerock.i18n.LocalizableMessage;
033import org.forgerock.i18n.slf4j.LocalizedLogger;
034import org.forgerock.opendj.config.server.ConfigChangeResult;
035import org.forgerock.opendj.config.server.ConfigException;
036import org.forgerock.opendj.ldap.ByteString;
037import org.forgerock.opendj.ldap.ModificationType;
038import org.opends.server.admin.server.ConfigurationChangeListener;
039import org.opends.server.admin.std.meta.PluginCfgDefn;
040import org.opends.server.admin.std.server.LastModPluginCfg;
041import org.opends.server.admin.std.server.PluginCfg;
042import org.opends.server.api.plugin.DirectoryServerPlugin;
043import org.opends.server.api.plugin.PluginResult;
044import org.opends.server.api.plugin.PluginType;
045import org.opends.server.core.DirectoryServer;
046import org.opends.server.types.*;
047import org.opends.server.types.operation.PreOperationAddOperation;
048import org.opends.server.types.operation.PreOperationModifyDNOperation;
049import org.opends.server.types.operation.PreOperationModifyOperation;
050
051import static org.opends.messages.PluginMessages.*;
052import static org.opends.server.config.ConfigConstants.*;
053import static org.opends.server.util.TimeThread.*;
054
055/**
056 * This class implements a Directory Server plugin that will add the
057 * creatorsName and createTimestamp attributes to an entry whenever it is added
058 * to the server, and will add the modifiersName and modifyTimestamp attributes
059 * whenever the entry is modified or renamed.
060 */
061public final class LastModPlugin
062       extends DirectoryServerPlugin<LastModPluginCfg>
063       implements ConfigurationChangeListener<LastModPluginCfg>
064{
065  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
066
067  /** The attribute type for the "createTimestamp" attribute. */
068  private final AttributeType createTimestampType;
069  /** The attribute type for the "creatorsName" attribute. */
070  private final AttributeType creatorsNameType;
071  /** The attribute type for the "modifiersName" attribute. */
072  private final AttributeType modifiersNameType;
073  /** The attribute type for the "modifyTimestamp" attribute. */
074  private final AttributeType modifyTimestampType;
075  /** The current configuration for this plugin. */
076  private LastModPluginCfg currentConfig;
077
078
079  /**
080   * Creates a new instance of this Directory Server plugin.  Every plugin must
081   * implement a default constructor (it is the only one that will be used to
082   * create plugins defined in the configuration), and every plugin constructor
083   * must call <CODE>super()</CODE> as its first element.
084   */
085  public LastModPlugin()
086  {
087    super();
088
089
090    // Get the attribute types for the attributes that we will use.  This needs
091    // to be done in the constructor in order to make the associated variables "final".
092    createTimestampType = DirectoryServer.getAttributeTypeOrDefault(OP_ATTR_CREATE_TIMESTAMP_LC);
093    creatorsNameType = DirectoryServer.getAttributeTypeOrDefault(OP_ATTR_CREATORS_NAME_LC);
094    modifiersNameType = DirectoryServer.getAttributeTypeOrDefault(OP_ATTR_MODIFIERS_NAME_LC);
095    modifyTimestampType = DirectoryServer.getAttributeTypeOrDefault(OP_ATTR_MODIFY_TIMESTAMP_LC);
096  }
097
098
099
100  /** {@inheritDoc} */
101  @Override
102  public final void initializePlugin(Set<PluginType> pluginTypes,
103                                     LastModPluginCfg configuration)
104         throws ConfigException
105  {
106    currentConfig = configuration;
107    configuration.addLastModChangeListener(this);
108
109    // Make sure that the plugin has been enabled for the appropriate types.
110    for (PluginType t : pluginTypes)
111    {
112      switch (t)
113      {
114        case PRE_OPERATION_ADD:
115        case PRE_OPERATION_MODIFY:
116        case PRE_OPERATION_MODIFY_DN:
117          // These are acceptable.
118          break;
119
120        default:
121          throw new ConfigException(ERR_PLUGIN_LASTMOD_INVALID_PLUGIN_TYPE.get(t));
122      }
123    }
124  }
125
126
127
128  /** {@inheritDoc} */
129  @Override
130  public final void finalizePlugin()
131  {
132    currentConfig.removeLastModChangeListener(this);
133  }
134
135
136
137  /** {@inheritDoc} */
138  @Override
139  public final PluginResult.PreOperation
140               doPreOperation(PreOperationAddOperation addOperation)
141  {
142    // Create the attribute list for the creatorsName attribute, if appropriate.
143    AttributeBuilder builder = new AttributeBuilder(creatorsNameType,
144        OP_ATTR_CREATORS_NAME);
145    DN creatorDN = addOperation.getAuthorizationDN();
146    if (creatorDN == null)
147    {
148      // This must mean that the operation was performed anonymously.
149      // Even so, we still need to update the creatorsName attribute.
150      builder.add(ByteString.empty());
151    }
152    else
153    {
154      builder.add(creatorDN.toString());
155    }
156    addOperation.setAttribute(creatorsNameType, builder.toAttributeList());
157
158
159    //  Create the attribute list for the createTimestamp attribute.
160    List<Attribute> timeList = Attributes.createAsList(
161        createTimestampType, OP_ATTR_CREATE_TIMESTAMP, getGMTTime());
162    addOperation.setAttribute(createTimestampType, timeList);
163
164    // We shouldn't ever need to return a non-success result.
165    return PluginResult.PreOperation.continueOperationProcessing();
166  }
167
168
169
170  /** {@inheritDoc} */
171  @Override
172  public final PluginResult.PreOperation
173       doPreOperation(PreOperationModifyOperation modifyOperation)
174  {
175    // Create the modifiersName attribute.
176    AttributeBuilder builder = new AttributeBuilder(modifiersNameType,
177        OP_ATTR_MODIFIERS_NAME);
178    DN modifierDN = modifyOperation.getAuthorizationDN();
179    if (modifierDN == null)
180    {
181      // This must mean that the operation was performed anonymously.
182      // Even so, we still need to update the modifiersName attribute.
183      builder.add(ByteString.empty());
184    }
185    else
186    {
187      builder.add(modifierDN.toString());
188    }
189    Attribute nameAttr = builder.toAttribute();
190    try
191    {
192      modifyOperation.addModification(new Modification(ModificationType.REPLACE,
193                                                       nameAttr, true));
194    }
195    catch (DirectoryException de)
196    {
197      logger.traceException(de);
198
199      // This should never happen.
200      return PluginResult.PreOperation.stopProcessing(
201          DirectoryConfig.getServerErrorResultCode(), de.getMessageObject());
202    }
203
204
205    //  Create the modifyTimestamp attribute.
206    Attribute timeAttr = Attributes.create(modifyTimestampType,
207        OP_ATTR_MODIFY_TIMESTAMP, getGMTTime());
208    try
209    {
210      modifyOperation.addModification(new Modification(ModificationType.REPLACE,
211                                                       timeAttr, true));
212    }
213    catch (DirectoryException de)
214    {
215      logger.traceException(de);
216
217      // This should never happen.
218      return PluginResult.PreOperation.stopProcessing(
219          DirectoryConfig.getServerErrorResultCode(), de.getMessageObject());
220    }
221
222
223    // We shouldn't ever need to return a non-success result.
224    return PluginResult.PreOperation.continueOperationProcessing();
225  }
226
227
228
229  /** {@inheritDoc} */
230  @Override
231  public final PluginResult.PreOperation
232       doPreOperation(PreOperationModifyDNOperation modifyDNOperation)
233  {
234    // Create the modifiersName attribute.
235    AttributeBuilder builder = new AttributeBuilder(modifiersNameType,
236        OP_ATTR_MODIFIERS_NAME);
237    DN modifierDN = modifyDNOperation.getAuthorizationDN();
238    if (modifierDN == null)
239    {
240      // This must mean that the operation was performed anonymously.
241      // Even so, we still need to update the modifiersName attribute.
242      builder.add(ByteString.empty());
243    }
244    else
245    {
246      builder.add(modifierDN.toString());
247    }
248    Attribute nameAttr = builder.toAttribute();
249    modifyDNOperation.addModification(new Modification(
250        ModificationType.REPLACE, nameAttr, true));
251
252
253    // Create the modifyTimestamp attribute.
254    Attribute timeAttr = Attributes.create(modifyTimestampType,
255        OP_ATTR_MODIFY_TIMESTAMP, getGMTTime());
256    modifyDNOperation.addModification(new Modification(
257        ModificationType.REPLACE, timeAttr, true));
258
259
260    // We shouldn't ever need to return a non-success result.
261    return PluginResult.PreOperation.continueOperationProcessing();
262  }
263
264
265
266  /** {@inheritDoc} */
267  @Override
268  public boolean isConfigurationAcceptable(PluginCfg configuration,
269                                           List<LocalizableMessage> unacceptableReasons)
270  {
271    LastModPluginCfg cfg = (LastModPluginCfg) configuration;
272    return isConfigurationChangeAcceptable(cfg, unacceptableReasons);
273  }
274
275
276
277  /** {@inheritDoc} */
278  @Override
279  public boolean isConfigurationChangeAcceptable(LastModPluginCfg configuration,
280                      List<LocalizableMessage> unacceptableReasons)
281  {
282    boolean configAcceptable = true;
283
284    // Ensure that the set of plugin types contains only pre-operation add,
285    // pre-operation modify, and pre-operation modify DN.
286    for (PluginCfgDefn.PluginType pluginType : configuration.getPluginType())
287    {
288      switch (pluginType)
289      {
290        case PREOPERATIONADD:
291        case PREOPERATIONMODIFY:
292        case PREOPERATIONMODIFYDN:
293          // These are acceptable.
294          break;
295
296
297        default:
298          unacceptableReasons.add(ERR_PLUGIN_LASTMOD_INVALID_PLUGIN_TYPE.get(pluginType));
299          configAcceptable = false;
300      }
301    }
302
303    return configAcceptable;
304  }
305
306
307
308  /** {@inheritDoc} */
309  @Override
310  public ConfigChangeResult applyConfigurationChange(
311                                 LastModPluginCfg configuration)
312  {
313    currentConfig = configuration;
314    return new ConfigChangeResult();
315  }
316}
317