/* * The contents of this file are subject to the terms of the Common Development and * Distribution License (the License). You may not use this file except in compliance with the * License. * * You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the * specific language governing permission and limitations under the License. * * When distributing Covered Software, include this CDDL Header Notice in each file and include * the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL * Header, with the fields enclosed by brackets [] replaced by your own identifying * information: "Portions Copyright [year] [name of copyright owner]". * * Copyright 2006-2008 Sun Microsystems, Inc. * Portions Copyright 2014-2016 ForgeRock AS. */ package org.opends.server.plugins; import java.io.IOException; import java.util.List; import java.util.Set; import org.forgerock.i18n.LocalizableMessage; import org.forgerock.opendj.config.server.ConfigException; import org.forgerock.opendj.io.ASN1; import org.forgerock.opendj.io.ASN1Reader; import org.forgerock.opendj.io.ASN1Writer; import org.forgerock.opendj.ldap.ResultCode; import org.forgerock.opendj.ldap.ByteString; import org.forgerock.opendj.server.config.server.PluginCfg; import org.opends.server.api.plugin.DirectoryServerPlugin; import org.opends.server.api.plugin.PluginResult; import org.opends.server.api.plugin.PluginType; import org.opends.server.controls.ControlDecoder; import org.opends.server.types.CanceledOperationException; import org.opends.server.types.Control; import org.opends.server.types.DirectoryException; import org.opends.server.types.operation.*; import org.opends.server.util.CollectionUtils; /** * This class defines a very simple pre-operation plugin that sleeps for up to * five seconds for add, compare, delete, extended, modify, modify DN, and * search operations (and therefore not for abandon, bind, and unbind * operations, since those operations cannot be cancelled). While it is * sleeping, it also checks for a request to cancel the associated operation and * will respond to it accordingly. */ public class DelayPreOpPlugin extends DirectoryServerPlugin { /** * The OID for the delay request control, which is used to flag operations * that should be delayed. */ public static final String OID_DELAY_REQUEST = "1.3.6.1.4.1.26027.1.999.1"; /** * The control used by this plugin. */ public static class DelayRequestControl extends Control { /** * ControlDecoder implementation to decode this control from a ByteString. */ private static final class Decoder implements ControlDecoder { /** {@inheritDoc} */ @Override public DelayRequestControl decode(boolean isCritical, ByteString value) throws DirectoryException { ASN1Reader reader = ASN1.getReader(value); try { long delay = reader.readInteger(); return new DelayRequestControl(isCritical, delay); } catch (Exception e) { // TODO: Need a better message throw new DirectoryException(ResultCode.PROTOCOL_ERROR, null, e); } } @Override public String getOID() { return OID_DELAY_REQUEST; } } /** * The Control Decoder that can be used to decode this control. */ public static final ControlDecoder DECODER = new Decoder(); private long delayDuration; /** * Constructs a new control of this class. * * @param isCritical * Indicates whether support for this control should be considered * a critical part of the server processing. * @param delayDuration * The requested delay duration. */ public DelayRequestControl(boolean isCritical, long delayDuration) { super(OID_DELAY_REQUEST, isCritical); this.delayDuration = delayDuration; } /** * Writes this control's value to an ASN.1 writer. The value (if any) * must be written as an ASN1OctetString. * * @param writer The ASN.1 writer to use. * @throws IOException If a problem occurs while writing to the stream. */ @Override protected void writeValue(ASN1Writer writer) throws IOException { writer.writeStartSequence(ASN1.UNIVERSAL_OCTET_STRING_TYPE); writer.writeInteger(delayDuration); writer.writeEndSequence(); } /** * Retrieves the delay duration. * * @return The delay duration. */ public long getDelayDuration() { return delayDuration; } } /** * Creates a new instance of this Directory Server plugin. Every * plugin must implement a default constructor (it is the only one * that will be used to create plugins defined in the * configuration), and every plugin constructor must call * super() as its first element. */ public DelayPreOpPlugin() { super(); } /** {@inheritDoc} */ @Override public void initializePlugin(Set pluginTypes, PluginCfg configuration) throws ConfigException { // This plugin may only be used as a pre-operation plugin. for (PluginType t : pluginTypes) { switch (t) { case PRE_OPERATION_ADD: case PRE_OPERATION_BIND: case PRE_OPERATION_COMPARE: case PRE_OPERATION_DELETE: case PRE_OPERATION_EXTENDED: case PRE_OPERATION_MODIFY: case PRE_OPERATION_MODIFY_DN: case PRE_OPERATION_SEARCH: // This is fine. break; default: throw new ConfigException(LocalizableMessage.raw("Invalid plugin type " + t + " for delay pre-op plugin.")); } } } /** {@inheritDoc} */ @Override public PluginResult.PreOperation doPreOperation(PreOperationAddOperation addOperation) throws CanceledOperationException { return doPreOperationInternal(addOperation); } /** {@inheritDoc} */ @Override public PluginResult.PreOperation doPreOperation(PreOperationBindOperation bindOperation) { try { return doPreOperationInternal(bindOperation); } catch(CanceledOperationException coe) { // Bind ops can't be canceled. Just ignore. return PluginResult.PreOperation.continueOperationProcessing(); } } /** {@inheritDoc} */ @Override public PluginResult.PreOperation doPreOperation(PreOperationCompareOperation compareOperation) throws CanceledOperationException { return doPreOperationInternal(compareOperation); } /** {@inheritDoc} */ @Override public PluginResult.PreOperation doPreOperation(PreOperationDeleteOperation deleteOperation) throws CanceledOperationException { return doPreOperationInternal(deleteOperation); } /** {@inheritDoc} */ @Override public PluginResult.PreOperation doPreOperation(PreOperationExtendedOperation extendedOperation) throws CanceledOperationException { return doPreOperationInternal(extendedOperation); } /** {@inheritDoc} */ @Override public PluginResult.PreOperation doPreOperation(PreOperationModifyOperation modifyOperation) throws CanceledOperationException { return doPreOperationInternal(modifyOperation); } /** {@inheritDoc} */ @Override public PluginResult.PreOperation doPreOperation(PreOperationModifyDNOperation modifyDNOperation) throws CanceledOperationException { return doPreOperationInternal(modifyDNOperation); } /** {@inheritDoc} */ @Override public PluginResult.PreOperation doPreOperation(PreOperationSearchOperation searchOperation) throws CanceledOperationException { return doPreOperationInternal(searchOperation); } /** * Looks for a delay request control in the operation, and if one is found * then sleep in 10 millisecond increments up to the length of time specified * in the control value. If the operation receives a cancel request during * this time, then the control will stop sleeping immediately. * * @param operation The operation to be processed. * * @return The result of the plugin processing. */ private PluginResult.PreOperation doPreOperationInternal(PreOperationOperation operation) throws CanceledOperationException { DelayRequestControl control; try { control = operation.getRequestControl(DelayRequestControl.DECODER); } catch (Exception e) { return PluginResult.PreOperation.stopProcessing( ResultCode.PROTOCOL_ERROR, LocalizableMessage.raw("Unable to decode the delay request control: " + e)); } if(control != null) { long delayDuration = control.getDelayDuration(); if (delayDuration <= 0) { return PluginResult.PreOperation.continueOperationProcessing(); } long stopSleepTime = System.currentTimeMillis() + delayDuration; while (System.currentTimeMillis() < stopSleepTime) { operation.checkIfCanceled(false); try { Thread.sleep(10); } catch (Exception e) {} } } return PluginResult.PreOperation.continueOperationProcessing(); } /** * Creates a delay request control with the specified delay. * * @param delay The length of time in milliseconds to sleep. * * @return The appropriate delay request control. */ public static Control createDelayControl(long delay) { return new DelayRequestControl(false, delay); } /** * Retrieves a list containing a delay request LDAP control with the specified * delay. * * @param delay The length of time in milliseconds to sleep. * * @return A list containing the appropriate delay request LDAP control. */ public static List createDelayControlList(long delay) { Control c = new DelayRequestControl(false, delay); return CollectionUtils.newArrayList(c); } }