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