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