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.forgerock.opendj.config.server.ConfigurationChangeListener;
029import org.forgerock.opendj.server.config.meta.PluginCfgDefn;
030import org.forgerock.opendj.server.config.server.ChangeNumberControlPluginCfg;
031import org.forgerock.opendj.server.config.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    @Override
078    protected void writeValue(ASN1Writer writer) throws IOException {
079      writer.writeOctetString(csn.toString());
080    }
081
082    /**
083     * Retrieves the CSN.
084     *
085     * @return The CSN.
086     */
087    public CSN getCSN()
088    {
089      return csn;
090    }
091  }
092
093  /**
094   * Creates a new instance of this Directory Server plugin. Every plugin must
095   * implement a default constructor (it is the only one that will be used to
096   * create plugins defined in the configuration), and every plugin constructor
097   * must call <CODE>super()</CODE> as its first element.
098   */
099  public ChangeNumberControlPlugin()
100  {
101    super();
102  }
103
104  @Override
105  public final void initializePlugin(Set<PluginType> pluginTypes,
106                                     ChangeNumberControlPluginCfg configuration)
107         throws ConfigException
108  {
109    currentConfig = configuration;
110    configuration.addChangeNumberControlChangeListener(this);
111    Set<PluginType> types = new TreeSet<>();
112
113    // Make sure that the plugin has been enabled for the appropriate types.
114    for (PluginType t : pluginTypes)
115    {
116      switch (t)
117      {
118        case POST_OPERATION_ADD:
119        case POST_OPERATION_DELETE:
120        case POST_OPERATION_MODIFY:
121        case POST_OPERATION_MODIFY_DN:
122          // These are acceptable.
123          types.add(t);
124          break;
125
126        default:
127          throw new ConfigException(ERR_PLUGIN_CHANGE_NUMBER_INVALID_PLUGIN_TYPE.get(t));
128      }
129    }
130    if (types.size() != 4) {
131      StringBuilder expected = new StringBuilder();
132      expected.append(PluginType.POST_OPERATION_ADD);
133      expected.append(", ");
134      expected.append(PluginType.POST_OPERATION_DELETE);
135      expected.append(", ");
136      expected.append(PluginType.POST_OPERATION_MODIFY);
137      expected.append(", ");
138      expected.append(PluginType.POST_OPERATION_MODIFY_DN);
139
140      StringBuilder found = new StringBuilder();
141      boolean first = true;
142      for (PluginType t : types) {
143        if (first) {
144          first = false;
145        } else {
146          found.append(", ");
147        }
148        found.append(t);
149      }
150
151      throw new ConfigException(ERR_PLUGIN_CHANGE_NUMBER_INVALID_PLUGIN_TYPE_LIST.get(
152          found, expected));
153    }
154  }
155
156  @Override
157  public final void finalizePlugin()
158  {
159    currentConfig.removeChangeNumberControlChangeListener(this);
160  }
161
162  @Override
163  public final PluginResult.PostOperation
164       doPostOperation(PostOperationAddOperation addOperation)
165  {
166    processCsnControl(addOperation);
167
168    // We shouldn't ever need to return a non-success result.
169    return PluginResult.PostOperation.continueOperationProcessing();
170  }
171
172  @Override
173  public final PluginResult.PostOperation
174       doPostOperation(PostOperationDeleteOperation deleteOperation)
175  {
176    processCsnControl(deleteOperation);
177
178    // We shouldn't ever need to return a non-success result.
179    return PluginResult.PostOperation.continueOperationProcessing();
180  }
181
182  @Override
183  public final PluginResult.PostOperation
184       doPostOperation(PostOperationModifyOperation modifyOperation)
185  {
186    processCsnControl(modifyOperation);
187
188    // We shouldn't ever need to return a non-success result.
189    return PluginResult.PostOperation.continueOperationProcessing();
190  }
191
192  @Override
193  public final PluginResult.PostOperation
194       doPostOperation(PostOperationModifyDNOperation modifyDNOperation)
195  {
196    processCsnControl(modifyDNOperation);
197
198    // We shouldn't ever need to return a non-success result.
199    return PluginResult.PostOperation.continueOperationProcessing();
200  }
201
202  @Override
203  public boolean isConfigurationAcceptable(PluginCfg configuration,
204                                           List<LocalizableMessage> unacceptableReasons)
205  {
206    ChangeNumberControlPluginCfg cfg =
207        (ChangeNumberControlPluginCfg) configuration;
208    return isConfigurationChangeAcceptable(cfg, unacceptableReasons);
209  }
210
211  @Override
212  public boolean isConfigurationChangeAcceptable(
213      ChangeNumberControlPluginCfg configuration,
214      List<LocalizableMessage> unacceptableReasons)
215  {
216    boolean configAcceptable = true;
217
218    // Ensure that the set of plugin types contains only pre-operation add,
219    // pre-operation modify, and pre-operation modify DN.
220    for (PluginCfgDefn.PluginType pluginType : configuration.getPluginType())
221    {
222      switch (pluginType)
223      {
224        case POSTOPERATIONADD:
225        case POSTOPERATIONDELETE:
226        case POSTOPERATIONMODIFY:
227        case POSTOPERATIONMODIFYDN:
228          // These are acceptable.
229          break;
230
231
232        default:
233          unacceptableReasons.add(ERR_PLUGIN_CHANGE_NUMBER_INVALID_PLUGIN_TYPE.get(pluginType));
234          configAcceptable = false;
235      }
236    }
237
238    return configAcceptable;
239  }
240
241  @Override
242  public ConfigChangeResult applyConfigurationChange(
243                                 ChangeNumberControlPluginCfg configuration)
244  {
245    currentConfig = configuration;
246    return new ConfigChangeResult();
247  }
248
249  /**
250   * Retrieves the CSN from the synchronization context and sets the control
251   * response in the operation.
252   *
253   * @param operation the operation
254   */
255  private void processCsnControl(PostOperationOperation operation) {
256    List<Control> requestControls = operation.getRequestControls();
257    if (requestControls != null && ! requestControls.isEmpty()) {
258      for (Control c : requestControls) {
259        if (c.getOID().equals(OID_CSN_CONTROL)) {
260          OperationContext ctx = (OperationContext)
261            operation.getAttachment(OperationContext.SYNCHROCONTEXT);
262          if (ctx != null) {
263            CSN cn = ctx.getCSN();
264            if (cn != null) {
265              Control responseControl =
266                  new ChangeNumberControl(c.isCritical(), cn);
267              operation.getResponseControls().add(responseControl);
268            }
269          }
270          break;
271        }
272      }
273    }
274  }
275}
276