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