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
010 * trunk/opends/resource/legal-notices/OpenDS.LICENSE
011 * or https://OpenDS.dev.java.net/OpenDS.LICENSE.
012 * See the License for the specific language governing permissions
013 * and limitations under the License.
014 *
015 * When distributing Covered Code, include this CDDL HEADER in each
016 * file and include the License file at
017 * trunk/opends/resource/legal-notices/OpenDS.LICENSE. If applicable,
018 * add the following below this CDDL HEADER, with the fields enclosed
019 * by brackets "[]" replaced with your own identifying information:
020 * Portions Copyright [yyyy] [name of copyright owner]
021 *
022 * CDDL HEADER END
023 *
024 *
025 * Copyright 2006-2008 Sun Microsystems, Inc.
026 */
027 package org.opends.server.core;
028 import org.opends.messages.MessageBuilder;
029
030
031 import java.util.ArrayList;
032 import java.util.Iterator;
033 import java.util.List;
034
035 import org.opends.server.api.ClientConnection;
036 import org.opends.server.api.ExtendedOperationHandler;
037 import org.opends.server.api.plugin.PluginResult;
038 import org.opends.server.protocols.asn1.ASN1OctetString;
039 import org.opends.server.types.operation.PostOperationExtendedOperation;
040 import org.opends.server.types.operation.PostResponseExtendedOperation;
041 import org.opends.server.types.operation.PreOperationExtendedOperation;
042 import org.opends.server.types.operation.PreParseExtendedOperation;
043
044 import static org.opends.server.core.CoreConstants.*;
045 import static org.opends.server.loggers.AccessLogger.*;
046 import static org.opends.server.loggers.debug.DebugLogger.*;
047
048 import org.opends.server.loggers.debug.DebugLogger;
049 import org.opends.server.loggers.debug.DebugTracer;
050 import org.opends.server.types.*;
051 import static org.opends.messages.CoreMessages.*;
052 import static org.opends.server.util.ServerConstants.*;
053
054
055
056 /**
057 * This class defines an extended operation, which can perform virtually any
058 * kind of task.
059 */
060 public class ExtendedOperationBasis
061 extends AbstractOperation
062 implements ExtendedOperation,
063 PreParseExtendedOperation,
064 PreOperationExtendedOperation,
065 PostOperationExtendedOperation,
066 PostResponseExtendedOperation
067 {
068 /**
069 * The tracer object for the debug logger.
070 */
071 private static final DebugTracer TRACER = DebugLogger.getTracer();
072
073 // The value for the request associated with this extended operation.
074 private ASN1OctetString requestValue;
075
076 // The value for the response associated with this extended operation.
077 private ASN1OctetString responseValue;
078
079 // Indicates whether a response has yet been sent for this operation.
080 private boolean responseSent;
081
082 // The set of response controls for this extended operation.
083 private List<Control> responseControls;
084
085 // The OID for the request associated with this extended operation.
086 private String requestOID;
087
088 // The OID for the response associated with this extended operation.
089 private String responseOID;
090
091
092
093 /**
094 * Creates a new extended operation with the provided information.
095 *
096 * @param clientConnection The client connection with which this operation
097 * is associated.
098 * @param operationID The operation ID for this operation.
099 * @param messageID The message ID of the request with which this
100 * operation is associated.
101 * @param requestControls The set of controls included in the request.
102 * @param requestOID The OID for the request associated with this
103 * extended operation.
104 * @param requestValue The value for the request associated with this
105 * extended operation.
106 */
107 public ExtendedOperationBasis(ClientConnection clientConnection,
108 long operationID,
109 int messageID, List<Control> requestControls,
110 String requestOID, ASN1OctetString requestValue)
111 {
112 super(clientConnection, operationID, messageID, requestControls);
113
114
115 this.requestOID = requestOID;
116 this.requestValue = requestValue;
117
118 responseOID = null;
119 responseValue = null;
120 responseControls = new ArrayList<Control>();
121 cancelRequest = null;
122 responseSent = false;
123
124 if (requestOID.equals(OID_CANCEL_REQUEST))
125 {
126 cancelResult = new CancelResult(ResultCode.CANNOT_CANCEL,
127 ERR_CANNOT_CANCEL_CANCEL.get());
128 }
129 if(requestOID.equals(OID_START_TLS_REQUEST))
130 {
131 cancelResult = new CancelResult(ResultCode.CANNOT_CANCEL,
132 ERR_CANNOT_CANCEL_START_TLS.get());
133 }
134 }
135
136
137
138 /**
139 * {@inheritDoc}
140 */
141 public final String getRequestOID()
142 {
143 return requestOID;
144 }
145
146
147
148 /**
149 * Specifies the OID for the request associated with this extended operation.
150 * This should only be called by pre-parse plugins.
151 *
152 * @param requestOID The OID for the request associated with this extended
153 * operation.
154 */
155 public final void setRequestOID(String requestOID)
156 {
157 this.requestOID = requestOID;
158 }
159
160
161
162 /**
163 * {@inheritDoc}
164 */
165 public final ASN1OctetString getRequestValue()
166 {
167 return requestValue;
168 }
169
170
171
172 /**
173 * Specifies the value for the request associated with this extended
174 * operation. This should only be called by pre-parse plugins.
175 *
176 * @param requestValue The value for the request associated with this
177 * extended operation.
178 */
179 public final void setRequestValue(ASN1OctetString requestValue)
180 {
181 this.requestValue = requestValue;
182 }
183
184
185
186 /**
187 * {@inheritDoc}
188 */
189 public final String getResponseOID()
190 {
191 return responseOID;
192 }
193
194
195
196 /**
197 * {@inheritDoc}
198 */
199 public final void setResponseOID(String responseOID)
200 {
201 this.responseOID = responseOID;
202 }
203
204
205
206 /**
207 * {@inheritDoc}
208 */
209 public final ASN1OctetString getResponseValue()
210 {
211 return responseValue;
212 }
213
214
215
216 /**
217 * {@inheritDoc}
218 */
219 public final void setResponseValue(ASN1OctetString responseValue)
220 {
221 this.responseValue = responseValue;
222 }
223
224
225 /**
226 * {@inheritDoc}
227 */
228 @Override()
229 public final OperationType getOperationType()
230 {
231 // Note that no debugging will be done in this method because it is a likely
232 // candidate for being called by the logging subsystem.
233
234 return OperationType.EXTENDED;
235 }
236
237
238
239 /**
240 * {@inheritDoc}
241 */
242 @Override()
243 public final String[][] getRequestLogElements()
244 {
245 // Note that no debugging will be done in this method because it is a likely
246 // candidate for being called by the logging subsystem.
247
248 return new String[][]
249 {
250 new String[] { LOG_ELEMENT_EXTENDED_REQUEST_OID, requestOID }
251 };
252 }
253
254
255
256 /**
257 * {@inheritDoc}
258 */
259 @Override()
260 public final String[][] getResponseLogElements()
261 {
262 // Note that no debugging will be done in this method because it is a likely
263 // candidate for being called by the logging subsystem.
264
265 String resultCode = String.valueOf(getResultCode().getIntValue());
266
267 String errorMessage;
268 MessageBuilder errorMessageBuffer = getErrorMessage();
269 if (errorMessageBuffer == null)
270 {
271 errorMessage = null;
272 }
273 else
274 {
275 errorMessage = errorMessageBuffer.toString();
276 }
277
278 String matchedDNStr;
279 DN matchedDN = getMatchedDN();
280 if (matchedDN == null)
281 {
282 matchedDNStr = null;
283 }
284 else
285 {
286 matchedDNStr = matchedDN.toString();
287 }
288
289 String referrals;
290 List<String> referralURLs = getReferralURLs();
291 if ((referralURLs == null) || referralURLs.isEmpty())
292 {
293 referrals = null;
294 }
295 else
296 {
297 StringBuilder buffer = new StringBuilder();
298 Iterator<String> iterator = referralURLs.iterator();
299 buffer.append(iterator.next());
300
301 while (iterator.hasNext())
302 {
303 buffer.append(", ");
304 buffer.append(iterator.next());
305 }
306
307 referrals = buffer.toString();
308 }
309
310 String processingTime =
311 String.valueOf(getProcessingTime());
312
313 return new String[][]
314 {
315 new String[] { LOG_ELEMENT_RESULT_CODE, resultCode },
316 new String[] { LOG_ELEMENT_ERROR_MESSAGE, errorMessage },
317 new String[] { LOG_ELEMENT_MATCHED_DN, matchedDNStr },
318 new String[] { LOG_ELEMENT_REFERRAL_URLS, referrals },
319 new String[] { LOG_ELEMENT_EXTENDED_RESPONSE_OID, responseOID },
320 new String[] { LOG_ELEMENT_PROCESSING_TIME, processingTime }
321 };
322 }
323
324
325
326 /**
327 * {@inheritDoc}
328 */
329 @Override()
330 public final List<Control> getResponseControls()
331 {
332 return responseControls;
333 }
334
335
336
337 /**
338 * {@inheritDoc}
339 */
340 @Override()
341 public final void addResponseControl(Control control)
342 {
343 responseControls.add(control);
344 }
345
346
347
348 /**
349 * {@inheritDoc}
350 */
351 @Override()
352 public final void removeResponseControl(Control control)
353 {
354 responseControls.remove(control);
355 }
356
357
358
359 /**
360 * Performs the work of actually processing this operation. This
361 * should include all processing for the operation, including
362 * invoking plugins, logging messages, performing access control,
363 * managing synchronization, and any other work that might need to
364 * be done in the course of processing.
365 */
366 public final void run()
367 {
368 setResultCode(ResultCode.UNDEFINED);
369
370 // Start the processing timer.
371 setProcessingStartTime();
372
373 // Log the extended request message.
374 logExtendedRequest(this);
375
376 // Get the plugin config manager that will be used for invoking plugins.
377 PluginConfigManager pluginConfigManager =
378 DirectoryServer.getPluginConfigManager();
379
380 try
381 {
382 // Check for and handle a request to cancel this operation.
383 checkIfCanceled(false);
384
385 // Invoke the pre-parse extended plugins.
386 PluginResult.PreParse preParseResult =
387 pluginConfigManager.invokePreParseExtendedPlugins(this);
388
389 if(!preParseResult.continueProcessing())
390 {
391 setResultCode(preParseResult.getResultCode());
392 appendErrorMessage(preParseResult.getErrorMessage());
393 setMatchedDN(preParseResult.getMatchedDN());
394 setReferralURLs(preParseResult.getReferralURLs());
395 return;
396 }
397
398 checkIfCanceled(false);
399
400
401 // Get the extended operation handler for the request OID. If there is
402 // none, then fail.
403 ExtendedOperationHandler handler =
404 DirectoryServer.getExtendedOperationHandler(requestOID);
405 if (handler == null)
406 {
407 setResultCode(ResultCode.UNWILLING_TO_PERFORM);
408 appendErrorMessage(ERR_EXTENDED_NO_HANDLER.get(
409 String.valueOf(requestOID)));
410 return;
411 }
412
413
414 // Look at the controls included in the request and ensure that all
415 // critical controls are supported by the handler.
416 List<Control> requestControls = getRequestControls();
417 if ((requestControls != null) && (! requestControls.isEmpty()))
418 {
419 for (Control c : requestControls)
420 {
421 if (!AccessControlConfigManager.getInstance().
422 getAccessControlHandler().
423 isAllowed(this.getAuthorizationDN(), this, c)) {
424 setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
425
426 appendErrorMessage(ERR_CONTROL_INSUFFICIENT_ACCESS_RIGHTS.get(
427 c.getOID()));
428 return;
429 }
430 if (! c.isCritical())
431 {
432 // The control isn't critical, so we don't care if it's supported
433 // or not.
434 }
435 else if (! handler.supportsControl(c.getOID()))
436 {
437 setResultCode(ResultCode.UNAVAILABLE_CRITICAL_EXTENSION);
438
439 appendErrorMessage(ERR_EXTENDED_UNSUPPORTED_CRITICAL_CONTROL.get(
440 String.valueOf(requestOID),
441 c.getOID()));
442
443 return;
444 }
445 }
446 }
447
448
449 // Check to see if the client has permission to perform the
450 // extended operation.
451
452 // FIXME: for now assume that this will check all permission
453 // pertinent to the operation. This includes proxy authorization
454 // and any other controls specified.
455 if (AccessControlConfigManager.getInstance()
456 .getAccessControlHandler().isAllowed(this) == false) {
457 setResultCode(ResultCode.INSUFFICIENT_ACCESS_RIGHTS);
458
459 appendErrorMessage(ERR_EXTENDED_AUTHZ_INSUFFICIENT_ACCESS_RIGHTS.get(
460 String.valueOf(requestOID)));
461
462 return;
463 }
464
465 try
466 {
467 // Invoke the pre-operation extended plugins.
468 PluginResult.PreOperation preOpResult =
469 pluginConfigManager.invokePreOperationExtendedPlugins(this);
470 if(!preOpResult.continueProcessing())
471 {
472 setResultCode(preParseResult.getResultCode());
473 appendErrorMessage(preParseResult.getErrorMessage());
474 setMatchedDN(preParseResult.getMatchedDN());
475 setReferralURLs(preParseResult.getReferralURLs());
476 return;
477 }
478
479 checkIfCanceled(false);
480
481 // Actually perform the processing for this operation.
482 handler.processExtendedOperation(this);
483
484 }
485 finally
486 {
487 pluginConfigManager.invokePostOperationExtendedPlugins(this);
488 }
489
490 }
491 catch(CanceledOperationException coe)
492 {
493 if (debugEnabled())
494 {
495 TRACER.debugCaught(DebugLogLevel.ERROR, coe);
496 }
497
498 setResultCode(ResultCode.CANCELED);
499 cancelResult = new CancelResult(ResultCode.CANCELED, null);
500
501 appendErrorMessage(coe.getCancelRequest().getCancelReason());
502 }
503 finally
504 {
505 // Stop the processing timer.
506 setProcessingStopTime();
507
508 // Send the response to the client, if it has not already been sent.
509 if (! responseSent)
510 {
511 responseSent = true;
512 if(cancelRequest == null || cancelResult == null ||
513 cancelResult.getResultCode() != ResultCode.CANCELED ||
514 cancelRequest.notifyOriginalRequestor() ||
515 DirectoryServer.notifyAbandonedOperations())
516 {
517 clientConnection.sendResponse(this);
518 }
519 }
520
521 // Log the extended response.
522 logExtendedResponse(this);
523
524 // Invoke the post-response extended plugins.
525 pluginConfigManager.invokePostResponseExtendedPlugins(this);
526
527 // If no cancel result, set it
528 if(cancelResult == null)
529 {
530 cancelResult = new CancelResult(ResultCode.TOO_LATE, null);
531 }
532 }
533 }
534
535
536
537 /**
538 * Sends an extended response to the client if none has already been sent.
539 * Note that extended operation handlers are strongly discouraged from using
540 * this method when it is not necessary because its use will prevent the
541 * response from being sent after post-operation plugin processing, which may
542 * impact the result that should be included. Nevertheless, it may be needed
543 * in some special cases in which the response must be sent before the
544 * extended operation handler completes its processing (e.g., the StartTLS
545 * operation in which the response must be sent in the clear before actually
546 * enabling TLS protection).
547 */
548 public final void sendExtendedResponse()
549 {
550 if (! responseSent)
551 {
552 responseSent = true;
553 clientConnection.sendResponse(this);
554 }
555 }
556
557
558 /**
559 * {@inheritDoc}
560 */
561 public final void setResponseSent()
562 {
563 this.responseSent = true;
564 }
565
566 /**
567 * {@inheritDoc}
568 */
569 @Override()
570 public final void toString(StringBuilder buffer)
571 {
572 buffer.append("ExtendedOperation(connID=");
573 buffer.append(clientConnection.getConnectionID());
574 buffer.append(", opID=");
575 buffer.append(operationID);
576 buffer.append(", oid=");
577 buffer.append(requestOID);
578 buffer.append(")");
579 }
580
581 }
582