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