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 2011-2015 ForgeRock AS
026 *      Portions Copyright 2013 ForgeRock AS
027 */
028package org.opends.server.plugins;
029
030import java.util.List;
031import java.util.Set;
032
033import java.util.TreeSet;
034import java.io.IOException;
035
036import org.forgerock.i18n.LocalizableMessage;
037import org.opends.server.admin.server.ConfigurationChangeListener;
038import org.opends.server.admin.std.meta.PluginCfgDefn;
039import org.opends.server.admin.std.server.ChangeNumberControlPluginCfg;
040import org.opends.server.admin.std.server.PluginCfg;
041import org.opends.server.api.plugin.DirectoryServerPlugin;
042import org.opends.server.api.plugin.PluginType;
043import org.opends.server.api.plugin.PluginResult;
044import org.forgerock.opendj.config.server.ConfigException;
045import org.forgerock.opendj.io.ASN1Writer;
046import org.opends.server.replication.common.CSN;
047import org.opends.server.replication.protocol.OperationContext;
048import org.forgerock.opendj.config.server.ConfigChangeResult;
049import org.opends.server.types.Control;
050import org.opends.server.types.operation.PostOperationAddOperation;
051import org.opends.server.types.operation.PostOperationDeleteOperation;
052import org.opends.server.types.operation.PostOperationModifyDNOperation;
053import org.opends.server.types.operation.PostOperationModifyOperation;
054import org.opends.server.types.operation.PostOperationOperation;
055
056import static org.opends.messages.PluginMessages.*;
057import static org.opends.server.util.ServerConstants.*;
058
059/**
060 * This class implements a Directory Server plugin that will add the
061 * replication CSN to a response whenever the CSN control is received.
062 */
063public final class ChangeNumberControlPlugin
064       extends DirectoryServerPlugin<ChangeNumberControlPluginCfg>
065       implements ConfigurationChangeListener<ChangeNumberControlPluginCfg>
066{
067
068  /** The current configuration for this plugin. */
069  private ChangeNumberControlPluginCfg currentConfig;
070
071  /** The control used by this plugin. */
072  public static class ChangeNumberControl extends Control
073  {
074    private CSN csn;
075
076    /**
077     * Constructs a new change number control.
078     *
079     * @param isCritical Indicates whether support for this control should be
080     *                   considered a critical part of the server processing.
081     * @param csn        The CSN.
082     */
083    public ChangeNumberControl(boolean isCritical, CSN csn)
084    {
085      super(OID_CSN_CONTROL, isCritical);
086      this.csn = csn;
087    }
088
089    /**
090     * Writes this control's value to an ASN.1 writer. The value (if any) must
091     * be written as an ASN1OctetString.
092     *
093     * @param writer The ASN.1 writer to use.
094     * @throws IOException If a problem occurs while writing to the stream.
095     */
096    protected void writeValue(ASN1Writer writer) throws IOException {
097      writer.writeOctetString(csn.toString());
098    }
099
100    /**
101     * Retrieves the CSN.
102     *
103     * @return The CSN.
104     */
105    public CSN getCSN()
106    {
107      return csn;
108    }
109  }
110
111  /**
112   * Creates a new instance of this Directory Server plugin. Every plugin must
113   * implement a default constructor (it is the only one that will be used to
114   * create plugins defined in the configuration), and every plugin constructor
115   * must call <CODE>super()</CODE> as its first element.
116   */
117  public ChangeNumberControlPlugin()
118  {
119    super();
120  }
121
122  /** {@inheritDoc} */
123  @Override
124  public final void initializePlugin(Set<PluginType> pluginTypes,
125                                     ChangeNumberControlPluginCfg configuration)
126         throws ConfigException
127  {
128    currentConfig = configuration;
129    configuration.addChangeNumberControlChangeListener(this);
130    Set<PluginType> types = new TreeSet<>();
131
132    // Make sure that the plugin has been enabled for the appropriate types.
133    for (PluginType t : pluginTypes)
134    {
135      switch (t)
136      {
137        case POST_OPERATION_ADD:
138        case POST_OPERATION_DELETE:
139        case POST_OPERATION_MODIFY:
140        case POST_OPERATION_MODIFY_DN:
141          // These are acceptable.
142          types.add(t);
143          break;
144
145        default:
146          throw new ConfigException(ERR_PLUGIN_CHANGE_NUMBER_INVALID_PLUGIN_TYPE.get(t));
147      }
148    }
149    if (types.size() != 4) {
150      StringBuilder expected = new StringBuilder();
151      expected.append(PluginType.POST_OPERATION_ADD);
152      expected.append(", ");
153      expected.append(PluginType.POST_OPERATION_DELETE);
154      expected.append(", ");
155      expected.append(PluginType.POST_OPERATION_MODIFY);
156      expected.append(", ");
157      expected.append(PluginType.POST_OPERATION_MODIFY_DN);
158
159      StringBuilder found = new StringBuilder();
160      boolean first = true;
161      for (PluginType t : types) {
162        if (first) {
163          first = false;
164        } else {
165          found.append(", ");
166        }
167        found.append(t);
168      }
169
170      throw new ConfigException(ERR_PLUGIN_CHANGE_NUMBER_INVALID_PLUGIN_TYPE_LIST.get(
171          found, expected));
172    }
173  }
174
175
176
177  /** {@inheritDoc} */
178  @Override
179  public final void finalizePlugin()
180  {
181    currentConfig.removeChangeNumberControlChangeListener(this);
182  }
183
184
185  /** {@inheritDoc} */
186  @Override
187  public final PluginResult.PostOperation
188       doPostOperation(PostOperationAddOperation addOperation)
189  {
190    processCsnControl(addOperation);
191
192    // We shouldn't ever need to return a non-success result.
193    return PluginResult.PostOperation.continueOperationProcessing();
194  }
195
196  /** {@inheritDoc} */
197  @Override
198  public final PluginResult.PostOperation
199       doPostOperation(PostOperationDeleteOperation deleteOperation)
200  {
201    processCsnControl(deleteOperation);
202
203    // We shouldn't ever need to return a non-success result.
204    return PluginResult.PostOperation.continueOperationProcessing();
205  }
206
207  /** {@inheritDoc} */
208  @Override
209  public final PluginResult.PostOperation
210       doPostOperation(PostOperationModifyOperation modifyOperation)
211  {
212    processCsnControl(modifyOperation);
213
214    // We shouldn't ever need to return a non-success result.
215    return PluginResult.PostOperation.continueOperationProcessing();
216  }
217
218  /** {@inheritDoc} */
219  @Override
220  public final PluginResult.PostOperation
221       doPostOperation(PostOperationModifyDNOperation modifyDNOperation)
222  {
223    processCsnControl(modifyDNOperation);
224
225    // We shouldn't ever need to return a non-success result.
226    return PluginResult.PostOperation.continueOperationProcessing();
227  }
228
229
230
231  /** {@inheritDoc} */
232  @Override
233  public boolean isConfigurationAcceptable(PluginCfg configuration,
234                                           List<LocalizableMessage> unacceptableReasons)
235  {
236    ChangeNumberControlPluginCfg cfg =
237        (ChangeNumberControlPluginCfg) configuration;
238    return isConfigurationChangeAcceptable(cfg, unacceptableReasons);
239  }
240
241
242
243  /** {@inheritDoc} */
244  public boolean isConfigurationChangeAcceptable(
245      ChangeNumberControlPluginCfg configuration,
246      List<LocalizableMessage> unacceptableReasons)
247  {
248    boolean configAcceptable = true;
249
250    // Ensure that the set of plugin types contains only pre-operation add,
251    // pre-operation modify, and pre-operation modify DN.
252    for (PluginCfgDefn.PluginType pluginType : configuration.getPluginType())
253    {
254      switch (pluginType)
255      {
256        case POSTOPERATIONADD:
257        case POSTOPERATIONDELETE:
258        case POSTOPERATIONMODIFY:
259        case POSTOPERATIONMODIFYDN:
260          // These are acceptable.
261          break;
262
263
264        default:
265          unacceptableReasons.add(ERR_PLUGIN_CHANGE_NUMBER_INVALID_PLUGIN_TYPE.get(pluginType));
266          configAcceptable = false;
267      }
268    }
269
270    return configAcceptable;
271  }
272
273  /** {@inheritDoc} */
274  public ConfigChangeResult applyConfigurationChange(
275                                 ChangeNumberControlPluginCfg configuration)
276  {
277    currentConfig = configuration;
278    return new ConfigChangeResult();
279  }
280
281  /**
282   * Retrieves the CSN from the synchronization context and sets the control
283   * response in the operation.
284   *
285   * @param operation the operation
286   */
287  private void processCsnControl(PostOperationOperation operation) {
288    List<Control> requestControls = operation.getRequestControls();
289    if (requestControls != null && ! requestControls.isEmpty()) {
290      for (Control c : requestControls) {
291        if (c.getOID().equals(OID_CSN_CONTROL)) {
292          OperationContext ctx = (OperationContext)
293            operation.getAttachment(OperationContext.SYNCHROCONTEXT);
294          if (ctx != null) {
295            CSN cn = ctx.getCSN();
296            if (cn != null) {
297              Control responseControl =
298                  new ChangeNumberControl(c.isCritical(), cn);
299              operation.getResponseControls().add(responseControl);
300            }
301          }
302          break;
303        }
304      }
305    }
306  }
307}
308