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