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 2008 Sun Microsystems, Inc.
026 */
027 package org.opends.server.protocols;
028
029
030
031 import java.io.File;
032 import java.io.IOException;
033 import java.util.Collection;
034 import java.util.Collections;
035 import java.util.LinkedHashMap;
036 import java.util.List;
037
038 import org.opends.messages.Message;
039 import org.opends.messages.MessageBuilder;
040 import org.opends.server.admin.server.ConfigurationChangeListener;
041 import org.opends.server.admin.std.server.ConnectionHandlerCfg;
042 import org.opends.server.admin.std.server.LDIFConnectionHandlerCfg;
043 import org.opends.server.api.AlertGenerator;
044 import org.opends.server.api.ClientConnection;
045 import org.opends.server.api.ConnectionHandler;
046 import org.opends.server.core.DirectoryServer;
047 import org.opends.server.loggers.debug.DebugTracer;
048 import org.opends.server.protocols.internal.InternalClientConnection;
049 import org.opends.server.types.ConfigChangeResult;
050 import org.opends.server.types.DebugLogLevel;
051 import org.opends.server.types.DirectoryConfig;
052 import org.opends.server.types.DN;
053 import org.opends.server.types.ExistingFileBehavior;
054 import org.opends.server.types.HostPort;
055 import org.opends.server.types.LDIFExportConfig;
056 import org.opends.server.types.LDIFImportConfig;
057 import org.opends.server.types.Operation;
058 import org.opends.server.types.ResultCode;
059 import org.opends.server.util.AddChangeRecordEntry;
060 import org.opends.server.util.ChangeRecordEntry;
061 import org.opends.server.util.DeleteChangeRecordEntry;
062 import org.opends.server.util.LDIFException;
063 import org.opends.server.util.LDIFReader;
064 import org.opends.server.util.LDIFWriter;
065 import org.opends.server.util.ModifyChangeRecordEntry;
066 import org.opends.server.util.ModifyDNChangeRecordEntry;
067 import org.opends.server.util.TimeThread;
068
069 import static org.opends.messages.ProtocolMessages.*;
070 import static org.opends.server.loggers.ErrorLogger.*;
071 import static org.opends.server.loggers.debug.DebugLogger.*;
072 import static org.opends.server.util.ServerConstants.*;
073 import static org.opends.server.util.StaticUtils.*;
074
075
076
077 /**
078 * This class defines an LDIF connection handler, which can be used to watch for
079 * new LDIF files to be placed in a specified directory. If a new LDIF file is
080 * detected, the connection handler will process any changes contained in that
081 * file as internal operations.
082 */
083 public final class LDIFConnectionHandler
084 extends ConnectionHandler<LDIFConnectionHandlerCfg>
085 implements ConfigurationChangeListener<LDIFConnectionHandlerCfg>,
086 AlertGenerator
087 {
088 /**
089 * The debug log tracer for this class.
090 */
091 private static final DebugTracer TRACER = getTracer();
092
093
094
095 // Indicates whether this connection handler is currently stopped.
096 private volatile boolean isStopped;
097
098 // Indicates whether we should stop this connection handler.
099 private volatile boolean stopRequested;
100
101 // The path to the directory to watch for new LDIF files.
102 private File ldifDirectory;
103
104 // The internal client connection that will be used for all processing.
105 private InternalClientConnection conn;
106
107 // The current configuration for this LDIF connection handler.
108 private LDIFConnectionHandlerCfg currentConfig;
109
110 // The thread used to run the connection handler.
111 private Thread connectionHandlerThread;
112
113 // Help to not warn permanently and fullfill the log file
114 // in debug mode.
115 private boolean alreadyWarn = false;
116
117
118 /**
119 * Creates a new instance of this connection handler. All initialization
120 * should be performed in the {@code initializeConnectionHandler} method.
121 */
122 public LDIFConnectionHandler()
123 {
124 super("LDIFConnectionHandler");
125
126 isStopped = true;
127 stopRequested = false;
128 connectionHandlerThread = null;
129 alreadyWarn = false;
130 }
131
132
133
134 /**
135 * {@inheritDoc}
136 */
137 @Override()
138 public void initializeConnectionHandler(LDIFConnectionHandlerCfg
139 configuration)
140 {
141 String ldifDirectoryPath = configuration.getLDIFDirectory();
142 ldifDirectory = new File(ldifDirectoryPath);
143
144 // If we have a relative path to the instance, get the absolute one.
145 if ( ! ldifDirectory.isAbsolute() ) {
146 ldifDirectory = new File(DirectoryServer.getServerRoot() + File.separator
147 + ldifDirectoryPath);
148 }
149
150 if (ldifDirectory.exists())
151 {
152 if (! ldifDirectory.isDirectory())
153 {
154 // The path specified as the LDIF directory exists, but isn't a
155 // directory. This is probably a mistake, and we should at least log
156 // a warning message.
157 logError(WARN_LDIF_CONNHANDLER_LDIF_DIRECTORY_NOT_DIRECTORY.get(
158 ldifDirectory.getAbsolutePath(),
159 configuration.dn().toString()));
160 }
161 }
162 else
163 {
164 // The path specified as the LDIF directory doesn't exist. We should log
165 // a warning message saying that we won't do anything until it's created.
166 logError(WARN_LDIF_CONNHANDLER_LDIF_DIRECTORY_MISSING.get(
167 ldifDirectory.getAbsolutePath(),
168 configuration.dn().toString()));
169 }
170
171 this.currentConfig = configuration;
172 currentConfig.addLDIFChangeListener(this);
173 DirectoryConfig.registerAlertGenerator(this);
174 conn = InternalClientConnection.getRootConnection();
175 }
176
177
178
179 /**
180 * {@inheritDoc}
181 */
182 @Override()
183 public void finalizeConnectionHandler(Message finalizeReason,
184 boolean closeConnections)
185 {
186 stopRequested = true;
187
188 for (int i=0; i < 5; i++)
189 {
190 if (isStopped)
191 {
192 return;
193 }
194 else
195 {
196 try
197 {
198 if ((connectionHandlerThread != null) &&
199 (connectionHandlerThread.isAlive()))
200 {
201 connectionHandlerThread.join(100);
202 connectionHandlerThread.interrupt();
203 }
204 else
205 {
206 return;
207 }
208 } catch (Exception e) {}
209 }
210 }
211 }
212
213
214
215 /**
216 * {@inheritDoc}
217 */
218 @Override()
219 public String getConnectionHandlerName()
220 {
221 return "LDIF Connection Handler";
222 }
223
224
225
226 /**
227 * {@inheritDoc}
228 */
229 @Override()
230 public String getProtocol()
231 {
232 return "LDIF";
233 }
234
235
236
237 /**
238 * {@inheritDoc}
239 */
240 @Override()
241 public Collection<HostPort> getListeners()
242 {
243 // There are no listeners for this connection handler.
244 return Collections.<HostPort>emptySet();
245 }
246
247
248
249 /**
250 * {@inheritDoc}
251 */
252 @Override()
253 public Collection<ClientConnection> getClientConnections()
254 {
255 // There are no client connections for this connection handler.
256 return Collections.<ClientConnection>emptySet();
257 }
258
259
260
261 /**
262 * {@inheritDoc}
263 */
264 @Override()
265 public void run()
266 {
267 isStopped = false;
268 connectionHandlerThread = Thread.currentThread();
269
270 try
271 {
272 while (! stopRequested)
273 {
274 try
275 {
276 long startTime = System.currentTimeMillis();
277
278 File dir = ldifDirectory;
279 if (dir.exists() && dir.isDirectory())
280 {
281 File[] ldifFiles = dir.listFiles();
282 if (ldifFiles != null)
283 {
284 for (File f : ldifFiles)
285 {
286 if (f.getName().endsWith(".ldif"))
287 {
288 processLDIFFile(f);
289 }
290 }
291 }
292 }
293 else
294 {
295 if (!alreadyWarn && debugEnabled())
296 {
297 TRACER.debugInfo("LDIF connection handler directory " +
298 dir.getAbsolutePath() +
299 "doesn't exist or isn't a file");
300 alreadyWarn = true;
301 }
302 }
303
304 if (! stopRequested)
305 {
306 long currentTime = System.currentTimeMillis();
307 long sleepTime = startTime + currentConfig.getPollInterval() -
308 currentTime;
309 if (sleepTime > 0)
310 {
311 try
312 {
313 Thread.sleep(sleepTime);
314 }
315 catch (InterruptedException ie)
316 {
317 if (debugEnabled())
318 {
319 TRACER.debugCaught(DebugLogLevel.ERROR, ie);
320 }
321 }
322 }
323 }
324 }
325 catch (Exception e)
326 {
327 if (debugEnabled())
328 {
329 TRACER.debugCaught(DebugLogLevel.ERROR, e);
330 }
331 }
332 }
333 }
334 finally
335 {
336 connectionHandlerThread = null;
337 isStopped = true;
338 }
339 }
340
341
342
343 /**
344 * Processes the contents of the provided LDIF file.
345 *
346 * @param ldifFile The LDIF file to be processed.
347 */
348 private void processLDIFFile(File ldifFile)
349 {
350 if (debugEnabled())
351 {
352 TRACER.debugInfo("Beginning processing on LDIF file " +
353 ldifFile.getAbsolutePath());
354 }
355
356 boolean fullyProcessed = false;
357 boolean errorEncountered = false;
358 String inputPath = ldifFile.getAbsolutePath();
359
360 LDIFImportConfig importConfig =
361 new LDIFImportConfig(inputPath);
362 importConfig.setInvokeImportPlugins(false);
363 importConfig.setValidateSchema(true);
364
365 String outputPath = inputPath + ".applied." + TimeThread.getGMTTime();
366 if (new File(outputPath).exists())
367 {
368 int i=2;
369 while (true)
370 {
371 if (! new File(outputPath + "." + i).exists())
372 {
373 outputPath = outputPath + "." + i;
374 break;
375 }
376
377 i++;
378 }
379 }
380
381 LDIFExportConfig exportConfig =
382 new LDIFExportConfig(outputPath, ExistingFileBehavior.APPEND);
383 if (debugEnabled())
384 {
385 TRACER.debugInfo("Creating applied file " + outputPath);
386 }
387
388
389 LDIFReader reader = null;
390 LDIFWriter writer = null;
391
392 try
393 {
394 reader = new LDIFReader(importConfig);
395 writer = new LDIFWriter(exportConfig);
396
397 while (true)
398 {
399 ChangeRecordEntry changeRecord;
400 try
401 {
402 changeRecord = reader.readChangeRecord(false);
403 if (debugEnabled())
404 {
405 TRACER.debugInfo("Read change record entry " +
406 String.valueOf(changeRecord));
407 }
408 }
409 catch (LDIFException le)
410 {
411 if (debugEnabled())
412 {
413 TRACER.debugCaught(DebugLogLevel.ERROR, le);
414 }
415
416 errorEncountered = true;
417 if (le.canContinueReading())
418 {
419 Message m =
420 ERR_LDIF_CONNHANDLER_CANNOT_READ_CHANGE_RECORD_NONFATAL.get(
421 le.getMessageObject());
422 writer.writeComment(m, 78);
423 continue;
424 }
425 else
426 {
427 Message m =
428 ERR_LDIF_CONNHANDLER_CANNOT_READ_CHANGE_RECORD_FATAL.get(
429 le.getMessageObject());
430 writer.writeComment(m, 78);
431 DirectoryConfig.sendAlertNotification(this,
432 ALERT_TYPE_LDIF_CONNHANDLER_PARSE_ERROR, m);
433 break;
434 }
435 }
436
437 Operation operation = null;
438 if (changeRecord == null)
439 {
440 fullyProcessed = true;
441 break;
442 }
443
444 if (changeRecord instanceof AddChangeRecordEntry)
445 {
446 operation = conn.processAdd((AddChangeRecordEntry) changeRecord);
447 }
448 else if (changeRecord instanceof DeleteChangeRecordEntry)
449 {
450 operation = conn.processDelete(
451 (DeleteChangeRecordEntry) changeRecord);
452 }
453 else if (changeRecord instanceof ModifyChangeRecordEntry)
454 {
455 operation = conn.processModify(
456 (ModifyChangeRecordEntry) changeRecord);
457 }
458 else if (changeRecord instanceof ModifyDNChangeRecordEntry)
459 {
460 operation = conn.processModifyDN(
461 (ModifyDNChangeRecordEntry) changeRecord);
462 }
463
464 if (operation == null)
465 {
466 Message m = INFO_LDIF_CONNHANDLER_UNKNOWN_CHANGETYPE.get(
467 changeRecord.getChangeOperationType().getLDIFChangeType());
468 writer.writeComment(m, 78);
469 }
470 else
471 {
472 if (debugEnabled())
473 {
474 TRACER.debugInfo("Result Code: " +
475 operation.getResultCode().toString());
476 }
477
478 Message m = INFO_LDIF_CONNHANDLER_RESULT_CODE.get(
479 operation.getResultCode().getIntValue(),
480 operation.getResultCode().toString());
481 writer.writeComment(m, 78);
482
483 MessageBuilder errorMessage = operation.getErrorMessage();
484 if ((errorMessage != null) && (errorMessage.length() > 0))
485 {
486 m = INFO_LDIF_CONNHANDLER_ERROR_MESSAGE.get(errorMessage);
487 writer.writeComment(m, 78);
488 }
489
490 DN matchedDN = operation.getMatchedDN();
491 if (matchedDN != null)
492 {
493 m = INFO_LDIF_CONNHANDLER_MATCHED_DN.get(matchedDN.toString());
494 writer.writeComment(m, 78);
495 }
496
497 List<String> referralURLs = operation.getReferralURLs();
498 if ((referralURLs != null) && (! referralURLs.isEmpty()))
499 {
500 for (String url : referralURLs)
501 {
502 m = INFO_LDIF_CONNHANDLER_REFERRAL_URL.get(url);
503 writer.writeComment(m, 78);
504 }
505 }
506 }
507
508 writer.writeChangeRecord(changeRecord);
509 }
510 }
511 catch (IOException ioe)
512 {
513 if (debugEnabled())
514 {
515 TRACER.debugCaught(DebugLogLevel.ERROR, ioe);
516 }
517
518 fullyProcessed = false;
519 Message m = ERR_LDIF_CONNHANDLER_IO_ERROR.get(inputPath,
520 getExceptionMessage(ioe));
521 logError(m);
522 DirectoryConfig.sendAlertNotification(this,
523 ALERT_TYPE_LDIF_CONNHANDLER_PARSE_ERROR, m);
524 }
525 finally
526 {
527 if (reader != null)
528 {
529 try
530 {
531 reader.close();
532 } catch (Exception e) {}
533 }
534
535 if (writer != null)
536 {
537 try
538 {
539 writer.close();
540 } catch (Exception e) {}
541 }
542 }
543
544 if (errorEncountered || (! fullyProcessed))
545 {
546 String renamedPath = inputPath + ".errors-encountered." +
547 TimeThread.getGMTTime();
548 if (new File(renamedPath).exists())
549 {
550 int i=2;
551 while (true)
552 {
553 if (! new File(renamedPath + "." + i).exists())
554 {
555 renamedPath = renamedPath + "." + i;
556 }
557
558 i++;
559 }
560 }
561
562 try
563 {
564 if (debugEnabled())
565 {
566 TRACER.debugInfo("Renaming source file to " + renamedPath);
567 }
568
569 ldifFile.renameTo(new File(renamedPath));
570 }
571 catch (Exception e)
572 {
573 if (debugEnabled())
574 {
575 TRACER.debugCaught(DebugLogLevel.ERROR, e);
576 }
577
578 Message m = ERR_LDIF_CONNHANDLER_CANNOT_RENAME.get(inputPath,
579 renamedPath, getExceptionMessage(e));
580 logError(m);
581 DirectoryConfig.sendAlertNotification(this,
582 ALERT_TYPE_LDIF_CONNHANDLER_IO_ERROR, m);
583 }
584 }
585 else
586 {
587 try
588 {
589 if (debugEnabled())
590 {
591 TRACER.debugInfo("Deleting source file");
592 }
593
594 ldifFile.delete();
595 }
596 catch (Exception e)
597 {
598 if (debugEnabled())
599 {
600 TRACER.debugCaught(DebugLogLevel.ERROR, e);
601 }
602
603 Message m = ERR_LDIF_CONNHANDLER_CANNOT_DELETE.get(inputPath,
604 getExceptionMessage(e));
605 logError(m);
606 DirectoryConfig.sendAlertNotification(this,
607 ALERT_TYPE_LDIF_CONNHANDLER_IO_ERROR, m);
608 }
609 }
610 }
611
612
613
614 /**
615 * {@inheritDoc}
616 */
617 @Override()
618 public void toString(StringBuilder buffer)
619 {
620 buffer.append("LDIFConnectionHandler(ldifDirectory=\"");
621 buffer.append(ldifDirectory.getAbsolutePath());
622 buffer.append("\", pollInterval=");
623 buffer.append(currentConfig.getPollInterval());
624 buffer.append("ms)");
625 }
626
627
628
629 /**
630 * {@inheritDoc}
631 */
632 @Override()
633 public boolean isConfigurationAcceptable(ConnectionHandlerCfg configuration,
634 List<Message> unacceptableReasons)
635 {
636 LDIFConnectionHandlerCfg cfg = (LDIFConnectionHandlerCfg) configuration;
637 return isConfigurationChangeAcceptable(cfg, unacceptableReasons);
638 }
639
640
641
642 /**
643 * {@inheritDoc}
644 */
645 public boolean isConfigurationChangeAcceptable(
646 LDIFConnectionHandlerCfg configuration,
647 List<Message> unacceptableReasons)
648 {
649 // The configuration should always be acceptable.
650 return true;
651 }
652
653
654
655 /**
656 * {@inheritDoc}
657 */
658 public ConfigChangeResult applyConfigurationChange(
659 LDIFConnectionHandlerCfg configuration)
660 {
661 // The only processing we need to do here is to get the LDIF directory and
662 // create a File object from it.
663 File newLDIFDirectory = new File(configuration.getLDIFDirectory());
664 this.ldifDirectory = newLDIFDirectory;
665 currentConfig = configuration;
666 return new ConfigChangeResult(ResultCode.SUCCESS, false);
667 }
668
669
670
671 /**
672 * {@inheritDoc}
673 */
674 public DN getComponentEntryDN()
675 {
676 return currentConfig.dn();
677 }
678
679
680
681 /**
682 * {@inheritDoc}
683 */
684 public String getClassName()
685 {
686 return LDIFConnectionHandler.class.getName();
687 }
688
689
690
691 /**
692 * {@inheritDoc}
693 */
694 public LinkedHashMap<String,String> getAlerts()
695 {
696 LinkedHashMap<String,String> alerts = new LinkedHashMap<String,String>();
697
698 alerts.put(ALERT_TYPE_LDIF_CONNHANDLER_PARSE_ERROR,
699 ALERT_DESCRIPTION_LDIF_CONNHANDLER_PARSE_ERROR);
700 alerts.put(ALERT_TYPE_LDIF_CONNHANDLER_IO_ERROR,
701 ALERT_DESCRIPTION_LDIF_CONNHANDLER_IO_ERROR);
702
703 return alerts;
704 }
705 }
706