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 2008-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2014-2015 ForgeRock AS.
016 */
017package org.opends.guitools.controlpanel.datamodel;
018
019import static org.opends.admin.ads.util.ConnectionUtils.*;
020import static org.opends.guitools.controlpanel.util.Utilities.*;
021import static org.opends.server.tools.ConfigureWindowsService.*;
022
023import static com.forgerock.opendj.cli.Utils.*;
024import static com.forgerock.opendj.util.OperatingSystem.*;
025
026import java.io.File;
027import java.net.InetAddress;
028import java.util.Collection;
029import java.util.Collections;
030import java.util.HashSet;
031import java.util.LinkedHashSet;
032import java.util.Objects;
033import java.util.Set;
034import java.util.SortedSet;
035
036import javax.naming.NamingException;
037import javax.naming.ldap.InitialLdapContext;
038
039import org.forgerock.i18n.LocalizableMessage;
040import org.forgerock.i18n.slf4j.LocalizedLogger;
041import org.forgerock.opendj.config.ConfigurationFramework;
042import org.forgerock.opendj.config.server.ConfigException;
043import org.opends.admin.ads.util.ApplicationTrustManager;
044import org.opends.admin.ads.util.ConnectionUtils;
045import org.opends.guitools.controlpanel.browser.IconPool;
046import org.opends.guitools.controlpanel.browser.LDAPConnectionPool;
047import org.opends.guitools.controlpanel.datamodel.ServerDescriptor.ServerStatus;
048import org.opends.guitools.controlpanel.event.BackendPopulatedEvent;
049import org.opends.guitools.controlpanel.event.BackendPopulatedListener;
050import org.opends.guitools.controlpanel.event.BackupCreatedEvent;
051import org.opends.guitools.controlpanel.event.BackupCreatedListener;
052import org.opends.guitools.controlpanel.event.ConfigChangeListener;
053import org.opends.guitools.controlpanel.event.ConfigurationChangeEvent;
054import org.opends.guitools.controlpanel.event.IndexModifiedEvent;
055import org.opends.guitools.controlpanel.event.IndexModifiedListener;
056import org.opends.guitools.controlpanel.task.Task;
057import org.opends.guitools.controlpanel.task.Task.State;
058import org.opends.guitools.controlpanel.task.Task.Type;
059import org.opends.guitools.controlpanel.util.ConfigFromDirContext;
060import org.opends.guitools.controlpanel.util.ConfigFromFile;
061import org.opends.guitools.controlpanel.util.ConfigReader;
062import org.opends.guitools.controlpanel.util.Utilities;
063import org.opends.quicksetup.util.UIKeyStore;
064import org.opends.quicksetup.util.Utils;
065import org.opends.server.util.StaticUtils;
066
067import com.forgerock.opendj.cli.CliConstants;
068
069/**
070 * This is the classes that is shared among all the different places in the
071 * Control Panel.  It contains information about the server status and
072 * configuration and some objects that are shared everywhere.
073 */
074public class ControlPanelInfo
075{
076  private long poolingPeriod = 20000;
077
078  private ServerDescriptor serverDesc;
079  private Set<Task> tasks = new HashSet<>();
080  private InitialLdapContext ctx;
081  private InitialLdapContext userDataCtx;
082  private final LDAPConnectionPool connectionPool = new LDAPConnectionPool();
083  /** Used by the browsers. */
084  private final IconPool iconPool = new IconPool();
085  private Thread poolingThread;
086  private boolean stopPooling;
087  private boolean pooling;
088  private ApplicationTrustManager trustManager;
089  private int connectTimeout = CliConstants.DEFAULT_LDAP_CONNECT_TIMEOUT;
090  private ConnectionProtocolPolicy connectionPolicy =
091    ConnectionProtocolPolicy.USE_MOST_SECURE_AVAILABLE;
092  private String ldapURL;
093  private String startTLSURL;
094  private String ldapsURL;
095  private String adminConnectorURL;
096  private String localAdminConnectorURL;
097  private String lastWorkingBindDN;
098  private String lastWorkingBindPwd;
099  private String lastRemoteHostName;
100  private String lastRemoteAdministrationURL;
101
102  private static boolean mustDeregisterConfig;
103
104  private boolean isLocal = true;
105
106  private Set<AbstractIndexDescriptor> modifiedIndexes = new HashSet<>();
107  private LinkedHashSet<ConfigChangeListener> configListeners = new LinkedHashSet<>();
108  private LinkedHashSet<BackupCreatedListener> backupListeners = new LinkedHashSet<>();
109  private LinkedHashSet<BackendPopulatedListener> backendPopulatedListeners = new LinkedHashSet<>();
110  private LinkedHashSet<IndexModifiedListener> indexListeners = new LinkedHashSet<>();
111
112  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
113
114  private static ControlPanelInfo instance;
115
116  /** Default constructor. */
117  protected ControlPanelInfo()
118  {
119  }
120
121  /**
122   * Returns a singleton for this instance.
123   * @return the control panel info.
124   */
125  public static ControlPanelInfo getInstance()
126  {
127    if (instance == null)
128    {
129      instance = new ControlPanelInfo();
130      try
131      {
132        instance.setTrustManager(
133            new ApplicationTrustManager(UIKeyStore.getInstance()));
134      }
135      catch (Throwable t)
136      {
137        logger.warn(LocalizableMessage.raw("Error retrieving UI key store: "+t, t));
138        instance.setTrustManager(new ApplicationTrustManager(null));
139      }
140    }
141    return instance;
142  }
143
144  /**
145   * Returns the last ServerDescriptor that has been retrieved.
146   * @return the last ServerDescriptor that has been retrieved.
147   */
148  public ServerDescriptor getServerDescriptor()
149  {
150    return serverDesc;
151  }
152
153  /**
154   * Returns the list of tasks.
155   * @return the list of tasks.
156   */
157  public Set<Task> getTasks()
158  {
159    return Collections.unmodifiableSet(tasks);
160  }
161
162  /**
163   * Registers a task.  The Control Panel creates a task every time an operation
164   * is made and they are stored here.
165   * @param task the task to be registered.
166   */
167  public void registerTask(Task task)
168  {
169    tasks.add(task);
170  }
171
172  /**
173   * Unregisters a task.
174   * @param task the task to be unregistered.
175   */
176  public void unregisterTask(Task task)
177  {
178    tasks.remove(task);
179  }
180
181  /**
182   * Tells whether an index must be reindexed or not.
183   * @param index the index.
184   * @return <CODE>true</CODE> if the index must be reindexed and
185   * <CODE>false</CODE> otherwise.
186   */
187  public boolean mustReindex(AbstractIndexDescriptor index)
188  {
189    boolean mustReindex = false;
190    for (AbstractIndexDescriptor i : modifiedIndexes)
191    {
192      if (i.getName().equals(index.getName()) &&
193          i.getBackend().getBackendID().equals(
194              index.getBackend().getBackendID()))
195      {
196        mustReindex = true;
197        break;
198      }
199    }
200    return mustReindex;
201  }
202
203  /**
204   * Registers an index as modified.  This is used by the panels to be able
205   * to inform the user that a rebuild of the index is required.
206   * @param index the index.
207   */
208  public void registerModifiedIndex(AbstractIndexDescriptor index)
209  {
210    modifiedIndexes.add(index);
211    indexModified(index);
212  }
213
214  /**
215   * Unregisters a modified index.
216   * @param index the index.
217   * @return <CODE>true</CODE> if the index is found in the list of modified
218   * indexes and <CODE>false</CODE> otherwise.
219   */
220  public boolean unregisterModifiedIndex(AbstractIndexDescriptor index)
221  {
222    // We might have stored indexes whose configuration has changed, just remove
223    // them if they have the same name, are of the same type and are defined in
224    // the same backend.
225    Set<AbstractIndexDescriptor> toRemove = new HashSet<>();
226    for (AbstractIndexDescriptor i : modifiedIndexes)
227    {
228      if (i.getName().equalsIgnoreCase(index.getName()) &&
229          i.getBackend().getBackendID().equalsIgnoreCase(
230              index.getBackend().getBackendID()) &&
231          i.getClass().equals(index.getClass()))
232      {
233        toRemove.add(i);
234      }
235    }
236
237    if (!toRemove.isEmpty())
238    {
239      boolean returnValue = modifiedIndexes.removeAll(toRemove);
240      indexModified(toRemove.iterator().next());
241      return returnValue;
242    }
243    return false;
244  }
245
246  /**
247   * Unregisters all the modified indexes on a given backend.
248   * @param backendName the name of the backend.
249   */
250  public void unregisterModifiedIndexesInBackend(String backendName)
251  {
252    HashSet<AbstractIndexDescriptor> toDelete = new HashSet<>();
253    for (AbstractIndexDescriptor index : modifiedIndexes)
254    {
255      // Compare only the Backend ID, since the backend object attached to
256      // the registered index might have changed (for instance the number of
257      // entries).  Relying on the backend ID to identify the backend is
258      // safe.
259      if (index.getBackend().getBackendID().equalsIgnoreCase(backendName))
260      {
261        toDelete.add(index);
262      }
263    }
264    modifiedIndexes.removeAll(toDelete);
265    for (BackendDescriptor backend : getServerDescriptor().getBackends())
266    {
267      if (backend.getBackendID().equals(backendName))
268      {
269        IndexModifiedEvent ev = new IndexModifiedEvent(backend);
270        for (IndexModifiedListener listener : indexListeners)
271        {
272          listener.backendIndexesModified(ev);
273        }
274        break;
275      }
276    }
277  }
278
279  /**
280   * Returns a collection with all the modified indexes.
281   * @return a collection with all the modified indexes.
282   */
283  public Collection<AbstractIndexDescriptor> getModifiedIndexes()
284  {
285    return Collections.unmodifiableCollection(modifiedIndexes);
286  }
287
288  /**
289   * Sets the dir context to be used by the ControlPanelInfo to retrieve
290   * monitoring and configuration information.
291   * @param ctx the connection.
292   */
293  public void setDirContext(InitialLdapContext ctx)
294  {
295    this.ctx = ctx;
296    if (ctx != null)
297    {
298      lastWorkingBindDN = ConnectionUtils.getBindDN(ctx);
299      lastWorkingBindPwd = ConnectionUtils.getBindPassword(ctx);
300      lastRemoteHostName = ConnectionUtils.getHostName(ctx);
301      lastRemoteAdministrationURL = ConnectionUtils.getLdapUrl(ctx);
302    }
303  }
304
305  /**
306   * Returns the dir context to be used by the ControlPanelInfo to retrieve
307   * monitoring and configuration information.
308   * @return the dir context to be used by the ControlPanelInfo to retrieve
309   * monitoring and configuration information.
310   */
311  public InitialLdapContext getDirContext()
312  {
313    return ctx;
314  }
315
316  /**
317   * Sets the dir context to be used by the ControlPanelInfo to retrieve
318   * user data.
319   * @param ctx the connection.
320   * @throws NamingException if there is a problem updating the connection pool.
321   */
322  public void setUserDataDirContext(InitialLdapContext ctx)
323  throws NamingException
324  {
325    if (userDataCtx != null)
326    {
327      unregisterConnection(connectionPool, ctx);
328    }
329    this.userDataCtx = ctx;
330    if (ctx != null)
331    {
332      InitialLdapContext cloneLdc =
333        ConnectionUtils.cloneInitialLdapContext(userDataCtx,
334            getConnectTimeout(),
335            getTrustManager(), null);
336      connectionPool.registerConnection(cloneLdc);
337    }
338  }
339
340  /**
341   * Returns the dir context to be used by the ControlPanelInfo to retrieve
342   * user data.
343   * @return the dir context to be used by the ControlPanelInfo to retrieve
344   * user data.
345   */
346  public InitialLdapContext getUserDataDirContext()
347  {
348    return userDataCtx;
349  }
350
351  /**
352   * Informs that a backup has been created.  The method will notify to all
353   * the backup listeners that a backup has been created.
354   * @param newBackup the new created backup.
355   */
356  public void backupCreated(BackupDescriptor newBackup)
357  {
358    BackupCreatedEvent ev = new BackupCreatedEvent(newBackup);
359    for (BackupCreatedListener listener : backupListeners)
360    {
361      listener.backupCreated(ev);
362    }
363  }
364
365  /**
366   * Informs that a set of backends have been populated.  The method will notify
367   * to all the backend populated listeners.
368   * @param backends the populated backends.
369   */
370  public void backendPopulated(Set<BackendDescriptor> backends)
371  {
372    BackendPopulatedEvent ev = new BackendPopulatedEvent(backends);
373    for (BackendPopulatedListener listener : backendPopulatedListeners)
374    {
375      listener.backendPopulated(ev);
376    }
377  }
378
379  /**
380   * Informs that an index has been modified.  The method will notify to all
381   * the index listeners that an index has been modified.
382   * @param modifiedIndex the modified index.
383   */
384  public void indexModified(AbstractIndexDescriptor modifiedIndex)
385  {
386    IndexModifiedEvent ev = new IndexModifiedEvent(modifiedIndex);
387    for (IndexModifiedListener listener : indexListeners)
388    {
389      listener.indexModified(ev);
390    }
391  }
392
393  /**
394   * Returns an empty new server descriptor instance.
395   * @return an empty new server descriptor instance.
396   */
397  protected ServerDescriptor createNewServerDescriptorInstance()
398  {
399    return new ServerDescriptor();
400  }
401
402  /**
403   * Returns a reader that will read the configuration from a file.
404   * @return a reader that will read the configuration from a file.
405   */
406  protected ConfigFromFile createNewConfigFromFileReader()
407  {
408    return new ConfigFromFile();
409  }
410
411  /**
412   * Returns a reader that will read the configuration from a dir context.
413   * @return a reader that will read the configuration from a dir context.
414   */
415  protected ConfigFromDirContext createNewConfigFromDirContextReader()
416  {
417    ConfigFromDirContext configFromDirContext = new ConfigFromDirContext();
418    configFromDirContext.setIsLocal(isLocal());
419    return configFromDirContext;
420  }
421
422  /**
423   * Updates the contents of the server descriptor with the provider reader.
424   * @param reader the configuration reader.
425   * @param desc the server descriptor.
426   */
427  protected void updateServerDescriptor(ConfigReader reader,
428      ServerDescriptor desc)
429  {
430    desc.setExceptions(reader.getExceptions());
431    desc.setAdministrativeUsers(reader.getAdministrativeUsers());
432    desc.setBackends(reader.getBackends());
433    desc.setConnectionHandlers(reader.getConnectionHandlers());
434    desc.setAdminConnector(reader.getAdminConnector());
435    desc.setSchema(reader.getSchema());
436    desc.setSchemaEnabled(reader.isSchemaEnabled());
437  }
438
439  /** Regenerates the last found ServerDescriptor object. */
440  public synchronized void regenerateDescriptor()
441  {
442    boolean isLocal = isLocal();
443
444    ServerDescriptor desc = createNewServerDescriptorInstance();
445    desc.setIsLocal(isLocal);
446    InitialLdapContext ctx = getDirContext();
447    if (isLocal)
448    {
449      desc.setOpenDSVersion(
450        org.opends.server.util.DynamicConstants.FULL_VERSION_STRING);
451      String installPath = Utilities.getInstallPathFromClasspath();
452      desc.setInstallPath(installPath);
453      desc.setInstancePath(Utils.getInstancePathFromInstallPath(installPath));
454      desc.setWindowsServiceEnabled(isWindows() && serviceState() == SERVICE_STATE_ENABLED);
455    }
456    else if (lastRemoteHostName != null)
457    {
458      desc.setHostname(lastRemoteHostName);
459    }
460    ConfigReader reader;
461
462    ServerStatus status = getStatus(desc);
463    if (status != null)
464    {
465      desc.setStatus(status);
466      if (status == ServerStatus.STOPPING)
467      {
468        StaticUtils.close(ctx);
469        this.ctx = null;
470        if (userDataCtx != null)
471        {
472          unregisterConnection(connectionPool, ctx);
473          StaticUtils.close(userDataCtx);
474          userDataCtx = null;
475        }
476      }
477      if (isLocal)
478      {
479        reader = createNewConfigFromFileReader();
480        ((ConfigFromFile)reader).readConfiguration();
481      }
482      else
483      {
484        reader = null;
485      }
486      desc.setAuthenticated(false);
487    }
488    else if (!isLocal ||
489        Utilities.isServerRunning(new File(desc.getInstancePath())))
490    {
491      desc.setStatus(ServerStatus.STARTED);
492
493      if (ctx == null && lastWorkingBindDN != null)
494      {
495        // Try with previous credentials.
496        try
497        {
498          if (isLocal)
499          {
500            ctx = Utilities.getAdminDirContext(this, lastWorkingBindDN,
501              lastWorkingBindPwd);
502          }
503          else if (lastRemoteAdministrationURL != null)
504          {
505            ctx = createLdapsContext(lastRemoteAdministrationURL,
506                lastWorkingBindDN,
507                lastWorkingBindPwd,
508                getConnectTimeout(), null,
509                getTrustManager(), null);
510          }
511        }
512        catch (ConfigReadException | NamingException cre)
513        {
514          // Ignore: we will ask the user for credentials.
515        }
516        if (ctx != null)
517        {
518          this.ctx = ctx;
519        }
520      }
521
522      if (isLocal && ctx == null)
523      {
524        reader = createNewConfigFromFileReader();
525        ((ConfigFromFile)reader).readConfiguration();
526      }
527      else if (!isLocal && ctx == null)
528      {
529        desc.setStatus(ServerStatus.NOT_CONNECTED_TO_REMOTE);
530        reader = null;
531      }
532      else
533      {
534        Utilities.initializeLegacyConfigurationFramework();
535        reader = createNewConfigFromDirContextReader();
536        ((ConfigFromDirContext) reader).readConfiguration(ctx);
537
538        boolean connectionWorks = checkConnections(ctx, userDataCtx);
539        if (!connectionWorks)
540        {
541          if (isLocal)
542          {
543            // Try with off-line info
544            reader = createNewConfigFromFileReader();
545            ((ConfigFromFile) reader).readConfiguration();
546          }
547          else
548          {
549            desc.setStatus(ServerStatus.NOT_CONNECTED_TO_REMOTE);
550            reader = null;
551          }
552          StaticUtils.close(ctx);
553          this.ctx = null;
554          unregisterConnection(connectionPool, ctx);
555          StaticUtils.close(userDataCtx);
556          userDataCtx = null;
557        }
558      }
559
560      if (reader != null)
561      {
562        desc.setAuthenticated(reader instanceof ConfigFromDirContext);
563        desc.setJavaVersion(reader.getJavaVersion());
564        desc.setOpenConnections(reader.getOpenConnections());
565        desc.setTaskEntries(reader.getTaskEntries());
566        if (reader instanceof ConfigFromDirContext)
567        {
568          ConfigFromDirContext rCtx = (ConfigFromDirContext)reader;
569          desc.setRootMonitor(rCtx.getRootMonitor());
570          desc.setEntryCachesMonitor(rCtx.getEntryCaches());
571          desc.setJvmMemoryUsageMonitor(rCtx.getJvmMemoryUsage());
572          desc.setSystemInformationMonitor(rCtx.getSystemInformation());
573          desc.setWorkQueueMonitor(rCtx.getWorkQueue());
574          desc.setOpenDSVersion(getFirstValueAsString(rCtx.getVersionMonitor(), "fullVersion"));
575          String installPath = getFirstValueAsString(rCtx.getSystemInformation(), "installPath");
576          if (installPath != null)
577          {
578            desc.setInstallPath(installPath);
579          }
580          String instancePath = getFirstValueAsString(rCtx.getSystemInformation(), "instancePath");
581          if (instancePath != null)
582          {
583            desc.setInstancePath(instancePath);
584          }
585        }
586      }
587    }
588    else
589    {
590      desc.setStatus(ServerStatus.STOPPED);
591      desc.setAuthenticated(false);
592      reader = createNewConfigFromFileReader();
593      ((ConfigFromFile)reader).readConfiguration();
594    }
595    if (reader != null)
596    {
597      updateServerDescriptor(reader, desc);
598    }
599
600    if (serverDesc == null || !serverDesc.equals(desc))
601    {
602      serverDesc = desc;
603      ldapURL = getURL(serverDesc, ConnectionHandlerDescriptor.Protocol.LDAP);
604      ldapsURL = getURL(serverDesc, ConnectionHandlerDescriptor.Protocol.LDAPS);
605      adminConnectorURL = getAdminConnectorURL(serverDesc);
606      if (serverDesc.isLocal())
607      {
608        localAdminConnectorURL = adminConnectorURL;
609      }
610      startTLSURL = getURL(serverDesc,
611          ConnectionHandlerDescriptor.Protocol.LDAP_STARTTLS);
612      ConfigurationChangeEvent ev = new ConfigurationChangeEvent(this, desc);
613      for (ConfigChangeListener listener : configListeners)
614      {
615        listener.configurationChanged(ev);
616      }
617    }
618  }
619
620  private ServerStatus getStatus(ServerDescriptor desc)
621  {
622    ServerStatus status = null;
623    for (Task task : getTasks())
624    {
625      if (task.getType() == Type.START_SERVER
626          && task.getState() == State.RUNNING
627          && isRunningOnServer(desc, task))
628      {
629        status = ServerStatus.STARTING;
630      }
631      else if (task.getType() == Type.STOP_SERVER && task.getState() == State.RUNNING
632          && isRunningOnServer(desc, task))
633      {
634        status = ServerStatus.STOPPING;
635      }
636    }
637    return status;
638  }
639
640  private void unregisterConnection(LDAPConnectionPool connectionPool, InitialLdapContext userDataCtx)
641  {
642    if (connectionPool.isConnectionRegistered(userDataCtx))
643    {
644      try
645      {
646        connectionPool.unregisterConnection(userDataCtx);
647      }
648      catch (Throwable t)
649      {
650      }
651    }
652  }
653
654  /**
655   * Adds a configuration change listener.
656   * @param listener the listener.
657   */
658  public void addConfigChangeListener(ConfigChangeListener listener)
659  {
660    configListeners.add(listener);
661  }
662
663  /**
664   * Removes a configuration change listener.
665   * @param listener the listener.
666   * @return <CODE>true</CODE> if the listener is found and <CODE>false</CODE>
667   * otherwise.
668   */
669  public boolean removeConfigChangeListener(ConfigChangeListener listener)
670  {
671    return configListeners.remove(listener);
672  }
673
674  /**
675   * Adds a backup creation listener.
676   * @param listener the listener.
677   */
678  public void addBackupCreatedListener(BackupCreatedListener listener)
679  {
680    backupListeners.add(listener);
681  }
682
683  /**
684   * Removes a backup creation listener.
685   * @param listener the listener.
686   * @return <CODE>true</CODE> if the listener is found and <CODE>false</CODE>
687   * otherwise.
688   */
689  public boolean removeBackupCreatedListener(BackupCreatedListener listener)
690  {
691    return backupListeners.remove(listener);
692  }
693
694  /**
695   * Adds a backend populated listener.
696   * @param listener the listener.
697   */
698  public void addBackendPopulatedListener(BackendPopulatedListener listener)
699  {
700    backendPopulatedListeners.add(listener);
701  }
702
703  /**
704   * Removes a backend populated listener.
705   * @param listener the listener.
706   * @return <CODE>true</CODE> if the listener is found and <CODE>false</CODE>
707   * otherwise.
708   */
709  public boolean removeBackendPopulatedListener(
710      BackendPopulatedListener listener)
711  {
712    return backendPopulatedListeners.remove(listener);
713  }
714
715  /**
716   * Adds an index modification listener.
717   * @param listener the listener.
718   */
719  public void addIndexModifiedListener(IndexModifiedListener listener)
720  {
721    indexListeners.add(listener);
722  }
723
724  /**
725   * Removes an index modification listener.
726   * @param listener the listener.
727   * @return <CODE>true</CODE> if the listener is found and <CODE>false</CODE>
728   * otherwise.
729   */
730  public boolean removeIndexModifiedListener(IndexModifiedListener listener)
731  {
732    return indexListeners.remove(listener);
733  }
734
735  /**
736   * Starts pooling the server configuration.  The period of the pooling is
737   * specified as a parameter.  This method is asynchronous and it will start
738   * the pooling in another thread.
739   */
740  public synchronized void startPooling()
741  {
742    if (poolingThread != null)
743    {
744      return;
745    }
746    pooling = true;
747    stopPooling = false;
748
749    poolingThread = new Thread(new Runnable()
750    {
751      @Override
752      public void run()
753      {
754        try
755        {
756          while (!stopPooling)
757          {
758            cleanupTasks();
759            regenerateDescriptor();
760            Thread.sleep(poolingPeriod);
761          }
762        }
763        catch (Throwable t)
764        {
765        }
766        pooling = false;
767      }
768    });
769    poolingThread.start();
770  }
771
772  /**
773   * Stops pooling the server.  This method is synchronous, it does not return
774   * until the pooling is actually stopped.
775   */
776  public synchronized void stopPooling()
777  {
778    stopPooling = true;
779    while (poolingThread != null && pooling)
780    {
781      try
782      {
783        poolingThread.interrupt();
784        Thread.sleep(100);
785      }
786      catch (Throwable t)
787      {
788        // do nothing;
789      }
790    }
791    poolingThread = null;
792    pooling = false;
793  }
794
795  /**
796   * Returns the trust manager to be used by this ControlPanelInfo (and in
797   * general by the control panel).
798   * @return the trust manager to be used by this ControlPanelInfo.
799   */
800  public ApplicationTrustManager getTrustManager()
801  {
802    return trustManager;
803  }
804
805  /**
806   * Sets the trust manager to be used by this ControlPanelInfo (and in
807   * general by the control panel).
808   * @param trustManager the trust manager to be used by this ControlPanelInfo.
809   */
810  public void setTrustManager(ApplicationTrustManager trustManager)
811  {
812    this.trustManager = trustManager;
813    connectionPool.setTrustManager(trustManager);
814  }
815
816  /**
817   * Returns the timeout to establish the connection in milliseconds.
818   * @return the timeout to establish the connection in milliseconds.
819   */
820  public int getConnectTimeout()
821  {
822    return connectTimeout;
823  }
824
825  /**
826   * Sets the timeout to establish the connection in milliseconds.
827   * Use {@code 0} to express no timeout.
828   * @param connectTimeout the timeout to establish the connection in
829   * milliseconds.
830   * Use {@code 0} to express no timeout.
831   */
832  public void setConnectTimeout(int connectTimeout)
833  {
834    this.connectTimeout = connectTimeout;
835    connectionPool.setConnectTimeout(connectTimeout);
836  }
837
838  /**
839   * Returns the connection policy to be used by this ControlPanelInfo (and in
840   * general by the control panel).
841   * @return the connection policy to be used by this ControlPanelInfo.
842   */
843  public ConnectionProtocolPolicy getConnectionPolicy()
844  {
845    return connectionPolicy;
846  }
847
848  /**
849   * Sets the connection policy to be used by this ControlPanelInfo (and in
850   * general by the control panel).
851   * @param connectionPolicy the connection policy to be used by this
852   * ControlPanelInfo.
853   */
854  public void setConnectionPolicy(ConnectionProtocolPolicy connectionPolicy)
855  {
856    this.connectionPolicy = connectionPolicy;
857  }
858
859  /**
860   * Gets the LDAPS URL based in what is read in the configuration. It
861   * returns <CODE>null</CODE> if no LDAPS URL was found.
862   * @return the LDAPS URL to be used to connect to the server.
863   */
864  public String getLDAPSURL()
865  {
866    return ldapsURL;
867  }
868
869  /**
870   * Gets the Administration Connector URL based in what is read in the
871   * configuration. It returns <CODE>null</CODE> if no Administration
872   * Connector URL was found.
873   * @return the Administration Connector URL to be used to connect
874   * to the server.
875   */
876  public String getAdminConnectorURL()
877  {
878    if (isLocal)
879    {
880      // If the user set isLocal to true, we want to return the
881      // localAdminConnectorURL (in particular if regenerateDescriptor has not
882      // been called).
883      return localAdminConnectorURL;
884    }
885    return adminConnectorURL;
886  }
887
888  /**
889   * Gets the Administration Connector URL based in what is read in the local
890   * configuration. It returns <CODE>null</CODE> if no Administration
891   * Connector URL was found.
892   * @return the Administration Connector URL to be used to connect
893   * to the local server.
894   */
895  public String getLocalAdminConnectorURL()
896  {
897    return localAdminConnectorURL;
898  }
899
900  /**
901   * Gets the LDAP URL based in what is read in the configuration. It
902   * returns <CODE>null</CODE> if no LDAP URL was found.
903   * @return the LDAP URL to be used to connect to the server.
904   */
905  public String getLDAPURL()
906  {
907    return ldapURL;
908  }
909
910  /**
911   * Gets the Start TLS URL based in what is read in the configuration. It
912   * returns <CODE>null</CODE> if no Start TLS URL is found.
913   * @return the Start TLS URL to be used to connect to the server.
914   */
915  public String getStartTLSURL()
916  {
917    return startTLSURL;
918  }
919
920  /**
921   * Returns the LDAP URL to be used to connect to a given ServerDescriptor
922   * using a certain protocol. It returns <CODE>null</CODE> if URL for the
923   * protocol is not found.
924   * @param server the server descriptor.
925   * @param protocol the protocol to be used.
926   * @return the LDAP URL to be used to connect to a given ServerDescriptor
927   * using a certain protocol.
928   */
929  private static String getURL(ServerDescriptor server,
930      ConnectionHandlerDescriptor.Protocol protocol)
931  {
932    String sProtocol = toString(protocol);
933
934    String url = null;
935    for (ConnectionHandlerDescriptor desc : server.getConnectionHandlers())
936    {
937      if (desc.getState() == ConnectionHandlerDescriptor.State.ENABLED
938          && desc.getProtocol() == protocol)
939      {
940        int port = desc.getPort();
941        if (port > 0)
942        {
943          if (server.isLocal())
944          {
945            SortedSet<InetAddress> addresses = desc.getAddresses();
946            if (addresses.isEmpty())
947            {
948              url = sProtocol +"://localhost:"+port;
949            }
950            else
951            {
952              InetAddress address = addresses.first();
953              url = sProtocol + "://" + getHostNameForLdapUrl(address.getHostAddress()) + ":" + port;
954            }
955          }
956          else
957          {
958            url = sProtocol + "://" + getHostNameForLdapUrl(server.getHostname()) + ":" + port;
959          }
960        }
961      }
962    }
963    return url;
964  }
965
966  private static String toString(ConnectionHandlerDescriptor.Protocol protocol)
967  {
968    switch (protocol)
969    {
970    case LDAP:
971      return "ldap";
972    case LDAPS:
973      return "ldaps";
974    case LDAP_STARTTLS:
975      return "ldap";
976    case JMX:
977      return "jmx";
978    case JMXS:
979      return "jmxs";
980    default:
981      return null;
982    }
983  }
984
985  /**
986   * Returns the Administration Connector URL.
987   * It returns <CODE>null</CODE> if URL for the
988   * protocol is not found.
989   * @param server the server descriptor.
990   * @return the Administration Connector URL.
991   */
992  private static String getAdminConnectorURL(ServerDescriptor server) {
993    ConnectionHandlerDescriptor desc = server.getAdminConnector();
994    if (desc != null)
995    {
996      int port = desc.getPort();
997      if (port > 0) {
998        SortedSet<InetAddress> addresses = desc.getAddresses();
999        if (!addresses.isEmpty())
1000        {
1001          String hostAddr = addresses.first().getHostAddress();
1002          return getLDAPUrl(hostAddr, port, true);
1003        }
1004        else
1005        {
1006          return getLDAPUrl("localhost", port, true);
1007        }
1008      }
1009    }
1010    return null;
1011  }
1012
1013  /**
1014   * Tells whether we must connect to the server using Start TLS.
1015   * @return <CODE>true</CODE> if we must connect to the server using Start TLS
1016   * and <CODE>false</CODE> otherwise.
1017   */
1018  public boolean connectUsingStartTLS()
1019  {
1020    if (getStartTLSURL() != null)
1021    {
1022      return getStartTLSURL().equals(getURLToConnect());
1023    }
1024    return false;
1025  }
1026
1027  /**
1028   * Tells whether we must connect to the server using LDAPS.
1029   * @return <CODE>true</CODE> if we must connect to the server using LDAPS
1030   * and <CODE>false</CODE> otherwise.
1031   */
1032  public boolean connectUsingLDAPS()
1033  {
1034    if (getLDAPSURL() != null)
1035    {
1036      return getLDAPSURL().equals(getURLToConnect());
1037    }
1038    return false;
1039  }
1040
1041  /**
1042   * Returns the URL that must be used to connect to the server based on the
1043   * available enabled connection handlers in the server and the connection
1044   * policy.
1045   * @return the URL that must be used to connect to the server.
1046   */
1047  public String getURLToConnect()
1048  {
1049    String url;
1050    switch (getConnectionPolicy())
1051    {
1052    case USE_STARTTLS:
1053      return getStartTLSURL();
1054    case USE_LDAP:
1055      return getLDAPURL();
1056    case USE_LDAPS:
1057      return getLDAPSURL();
1058    case USE_ADMIN:
1059      return getAdminConnectorURL();
1060    case USE_MOST_SECURE_AVAILABLE:
1061      url = getLDAPSURL();
1062      if (url == null)
1063      {
1064        url = getStartTLSURL();
1065      }
1066      if (url == null)
1067      {
1068        url = getLDAPURL();
1069      }
1070      return url;
1071    case USE_LESS_SECURE_AVAILABLE:
1072      url = getLDAPURL();
1073      if (url == null)
1074      {
1075        url = getStartTLSURL();
1076      }
1077      if (url == null)
1078      {
1079        url = getLDAPSURL();
1080      }
1081      return url;
1082    default:
1083      throw new RuntimeException("Unknown policy: "+getConnectionPolicy());
1084    }
1085  }
1086
1087  /**
1088   * Returns <CODE>true</CODE> if the configuration must be deregistered and
1089   * <CODE>false</CODE> otherwise.
1090   * This is required when we use the ConfigFileHandler to update the
1091   * configuration, in these cases cn=config must the deregistered from the
1092   * ConfigFileHandler and after that register again.
1093   * @return <CODE>true</CODE> if the configuration must be deregistered and
1094   * <CODE>false</CODE> otherwise.
1095   */
1096  public boolean mustDeregisterConfig()
1097  {
1098    return mustDeregisterConfig;
1099  }
1100
1101  /**
1102   * Sets whether the configuration must be deregistered or not.
1103   * @param mustDeregisterConfig whether the configuration must be deregistered
1104   * or not.
1105   */
1106  public void setMustDeregisterConfig(boolean mustDeregisterConfig)
1107  {
1108    ControlPanelInfo.mustDeregisterConfig = mustDeregisterConfig;
1109  }
1110
1111  /**
1112   * Sets whether the server is local or not.
1113   * @param isLocal whether the server is local or not.
1114   */
1115  public void setIsLocal(boolean isLocal)
1116  {
1117    this.isLocal = isLocal;
1118  }
1119
1120  /**
1121   * Returns <CODE>true</CODE> if we are trying to manage the local host and
1122   * <CODE>false</CODE> otherwise.
1123   * @return <CODE>true</CODE> if we are trying to manage the local host and
1124   * <CODE>false</CODE> otherwise.
1125   */
1126  public boolean isLocal()
1127  {
1128    return isLocal;
1129  }
1130
1131  /**
1132   * Returns the connection pool to be used by the LDAP entry browsers.
1133   * @return the connection pool to be used by the LDAP entry browsers.
1134   */
1135  public LDAPConnectionPool getConnectionPool()
1136  {
1137    return connectionPool;
1138  }
1139
1140  /**
1141   * Returns the icon pool to be used by the LDAP entry browsers.
1142   * @return the icon pool to be used by the LDAP entry browsers.
1143   */
1144  public IconPool getIconPool()
1145  {
1146    return iconPool;
1147  }
1148
1149  /**
1150   * Returns the pooling period in miliseconds.
1151   * @return the pooling period in miliseconds.
1152   */
1153  public long getPoolingPeriod()
1154  {
1155    return poolingPeriod;
1156  }
1157
1158  /**
1159   * Sets the pooling period in miliseconds.
1160   * @param poolingPeriod the pooling time in miliseconds.
1161   */
1162  public void setPoolingPeriod(long poolingPeriod)
1163  {
1164    this.poolingPeriod = poolingPeriod;
1165  }
1166
1167  /** Cleans the tasks that are over. */
1168  private void cleanupTasks()
1169  {
1170    Set<Task> toClean = new HashSet<>();
1171    for (Task task : tasks)
1172    {
1173      if (task.getState() == Task.State.FINISHED_SUCCESSFULLY ||
1174          task.getState() == Task.State.FINISHED_WITH_ERROR)
1175      {
1176        toClean.add(task);
1177      }
1178    }
1179    for (Task task : toClean)
1180    {
1181      unregisterTask(task);
1182    }
1183  }
1184
1185  /**
1186   * Returns whether the provided task is running on the provided server or not.
1187   * The code takes into account that the server object might not be fully
1188   * initialized (but at least it contains the host name and the instance
1189   * path if it is local).
1190   * @param server the server.
1191   * @param task the task to be analyzed.
1192   * @return <CODE>true</CODE> if the provided task is running on the provided
1193   * server and <CODE>false</CODE> otherwise.
1194   */
1195  private boolean isRunningOnServer(ServerDescriptor server, Task task)
1196  {
1197    if (server.isLocal() && task.getServer().isLocal())
1198    {
1199      return true;
1200    }
1201
1202    String host1 = server.getHostname();
1203    String host2 = task.getServer().getHostname();
1204    boolean isRunningOnServer = host1 != null ? host1.equalsIgnoreCase(host2) : host2 == null;
1205    if (!isRunningOnServer)
1206    {
1207      return false;
1208    }
1209
1210    if (server.isLocal())
1211    {
1212      // Compare paths
1213      String path1 = server.getInstancePath();
1214      String path2 = task.getServer().getInstancePath();
1215      return Objects.equals(path1, path2);
1216    }
1217
1218    // At this point we only have connection information about the new server.
1219    // Use the dir context which corresponds to the server to compare things.
1220
1221    // Compare administration port;
1222    int adminPort1 = -1;
1223    int adminPort2 = -1;
1224    if (server.getAdminConnector() != null)
1225    {
1226      adminPort1 = server.getAdminConnector().getPort();
1227    }
1228
1229    if (getDirContext() != null)
1230    {
1231      adminPort2 = ConnectionUtils.getPort(getDirContext());
1232    }
1233    return adminPort1 == adminPort2;
1234  }
1235
1236  private boolean checkConnections(InitialLdapContext ctx, InitialLdapContext userCtx)
1237  {
1238    // Check the connection
1239    int nMaxErrors = 5;
1240    for (int i=0; i< nMaxErrors; i++)
1241    {
1242      try
1243      {
1244        Utilities.pingDirContext(ctx);
1245        if (userCtx != null)
1246        {
1247          Utilities.pingDirContext(userCtx);
1248        }
1249        return true;
1250      }
1251      catch (NamingException ne)
1252      {
1253        try
1254        {
1255          Thread.sleep(400);
1256        }
1257        catch (Throwable t)
1258        {
1259        }
1260      }
1261    }
1262    return false;
1263  }
1264
1265  /**
1266   * Initialize the new configuration framework if needed.
1267   *
1268   * @throws org.opends.server.config.ConfigException
1269   *           If error occurred during the initialization
1270   */
1271  public void initializeConfigurationFramework() throws org.opends.server.config.ConfigException
1272  {
1273    if (!ConfigurationFramework.getInstance().isInitialized())
1274    {
1275      try
1276      {
1277        ConfigurationFramework.getInstance().initialize();
1278      }
1279      catch (ConfigException ce)
1280      {
1281        throw new org.opends.server.config.ConfigException(ce.getMessageObject(), ce);
1282      }
1283    }
1284  }
1285}