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 2006-2010 Sun Microsystems, Inc.
015 * Portions Copyright 2013-2016 ForgeRock AS.
016 */
017package org.opends.quicksetup;
018
019import java.io.BufferedReader;
020import java.io.File;
021import java.io.FileReader;
022import java.io.IOException;
023import java.util.HashSet;
024import java.util.Set;
025
026import org.opends.quicksetup.util.Utils;
027
028/**
029 * Represents the contents of an OpenDS configuration file.
030 */
031public class Configuration {
032
033  private String contents;
034  private String lowerCaseContents;
035  private final Installation install;
036  private final File file;
037
038  /**
039   * Create a Configuration from a file.
040   * @param install of which this configuration is part
041   * @param file config.ldif file
042   */
043  public Configuration(Installation install, File file) {
044    if (install == null) {
045      throw new NullPointerException("config file cannot be null");
046    }
047    if (file == null) {
048      throw new NullPointerException("config file cannot be null");
049    } else if (
050            // Leave open the possibility that the file might be
051            // config.ldif.<svn rev>
052            !file.getName().startsWith("config.ldif")) {
053      throw new IllegalArgumentException("file must be a config.ldif file");
054    }
055    this.install = install;
056    this.file = file;
057  }
058
059  /**
060   * Returns the list of directory manager dns as they appear in the
061   * configuration file.
062   *
063   * @return the list of directory manager dns as they appear in the
064   *         configuration file.
065   * @throws IOException if there were problems reading the information from
066   * the configuration file.
067   */
068  public Set<String> getDirectoryManagerDns() throws IOException {
069    return getConfigurationValues("ds-cfg-alternate-bind-dn");
070  }
071
072  /**
073   * Provides the LDAP port as is specified in the config.ldif file.
074   *
075   * @return the LDAP port specified in the config.ldif file.
076   * @throws IOException if there were problems reading the information from
077   * the configuration file.
078   */
079  public int getPort() throws IOException {
080    return getLDAPPort("ds-cfg-listen-port");
081  }
082
083  /**
084   * Provides the administration port as is specified in the config.ldif file.
085   *
086   * @return the administration port specified in the config.ldif file.
087   * @throws IOException if there were problems reading the information from
088   * the configuration file.
089   */
090  public int getAdminConnectorPort() throws IOException
091  {
092    return getAdminConnectorPort("ds-cfg-listen-port");
093  }
094
095  /**
096   * Tells whether this server is configured as a replication server or not.
097   * @return <CODE>true</CODE> if the server is configured as a Replication
098   * Server and <CODE>false</CODE> otherwise.
099   * @throws IOException if there were problems reading the information from
100   * the configuration file.
101   */
102  public boolean isReplicationServer() throws IOException
103  {
104    return getReplicationPort() != -1;
105  }
106
107  /**
108   * Provides the Replication port as is specified in the config.ldif file.
109   * Returns -1 if this server is not a Replication Server.
110   *
111   * @return the Replication port specified in the config.ldif file.
112   * @throws IOException if there were problems reading the information from
113   * the configuration file.
114   */
115  public int getReplicationPort() throws IOException {
116    int port = -1;
117    String contents = getLowerCaseContents();
118    int index = contents.indexOf("cn=replication server");
119
120    if (index != -1) {
121      String attrWithPoints = "ds-cfg-replication-port:";
122      int index1 = contents.indexOf(attrWithPoints, index);
123      if (index1 != -1) {
124        int index2 =
125                contents.indexOf(Constants.LINE_SEPARATOR, index1);
126        if (index2 != -1) {
127          String sPort =
128                  contents.substring(attrWithPoints.length() +
129                          index1,
130                          index2).trim();
131          try {
132            port = Integer.parseInt(sPort);
133          } catch (NumberFormatException nfe) {
134            // do nothing;
135          }
136        }
137      }
138    }
139    return port;
140  }
141
142  /**
143   * Returns the list of paths where the logs files are located as they appear
144   * in the configuration file.
145   *
146   * @return the list of paths where the logs files are located as they appear
147   *         in the configuration file.
148   * @throws IOException if there were problems reading the information from
149   * the configuration file.
150   */
151  public Set<String> getLogPaths() throws IOException {
152    return getConfigurationValues("ds-cfg-log-file");
153  }
154
155  private int extractPort(String portAttr, int index)
156  {
157    int port = -1;
158    String attrWithPoints = portAttr + ":";
159    int index1 = contents.indexOf(attrWithPoints, index);
160    if (index1 != -1) {
161      int index2 =
162        contents.indexOf(Constants.LINE_SEPARATOR, index1);
163      if (index2 != -1) {
164        String sPort =
165          contents.substring(attrWithPoints.length() +
166              index1, index2).trim();
167        try {
168          port = Integer.parseInt(sPort);
169        } catch (NumberFormatException nfe) {
170          // do nothing;
171        }
172      }
173    }
174    return port;
175  }
176
177
178  private int getLDAPPort(String portAttr) throws IOException {
179    String contents = getLowerCaseContents();
180    int index = contents.indexOf("cn=ldap connection handler");
181    if (index != -1) {
182      return extractPort (portAttr, index);
183    }
184    return -1;
185  }
186
187  private int getAdminConnectorPort(String portAttr) throws IOException {
188    String contents = getLowerCaseContents();
189    int index = contents.indexOf("cn=administration connector");
190    if (index != -1) {
191      return extractPort(portAttr, index);
192    }
193    return -1;
194  }
195
196  /**
197   * Indicates whether the config.ldif file has been modified (compared to what
198   * we had in the zip file). This is used to know if we have configured the
199   * current binaries or not.
200   *
201   * @return <CODE>true</CODE> if the config.ldif file has been modified, or
202   *         <CODE>false</CODE> if not.
203   * @throws IOException if there were problems reading the information from
204   * the configuration file.
205   */
206  public boolean hasBeenModified() throws IOException {
207    boolean isConfigFileModified = getPort() != 389;
208
209    if (!isConfigFileModified) {
210      // TODO: this is not really stable
211      // Note: a better way might be to diff this file with
212      // /config/ldif/upgrade/config.ldif.<svn rev>
213      isConfigFileModified = !getLowerCaseContents().contains(
214                  "# The contents of this file are subject to the terms of the Common Development".toLowerCase());
215    }
216
217    return isConfigFileModified;
218  }
219
220  /**
221   * Returns a Set of relative paths containing the log paths outside the
222   * installation.
223   * @return a Set of relative paths containing the log paths outside the
224   * installation.
225   * @throws IOException if there is trouble reading the config file
226   */
227  public Set<String> getOutsideLogs()
228          throws IOException
229  {
230    return getOutsidePaths(getLogPaths());
231  }
232
233  /**
234   * Returns a Set of relative paths containing the db paths outside the
235   * installation.
236   * @return a Set of relative paths containing the db paths outside the
237   * installation.
238   * @throws IOException if there is trouble reading the config file
239   */
240  public Set<String> getOutsideDbs()
241          throws IOException
242  {
243    return getOutsidePaths(getDatabasePaths());
244  }
245
246  private Set<String> getOutsidePaths(Set<String> paths) {
247    Set<String> outsidePaths = new HashSet<>();
248    for (String path : paths) {
249      File fullDbPath;
250      File pathFile = new File(path);
251      if (pathFile.isAbsolute()) {
252        fullDbPath = pathFile;
253      } else {
254        fullDbPath = new File(install.getInstanceDirectory(), path);
255      }
256
257      if (!Utils.isDescendant(fullDbPath, install.getInstanceDirectory())) {
258        outsidePaths.add(Utils.getPath(fullDbPath));
259      }
260    }
261    return outsidePaths;
262  }
263
264  /**
265   * Provides the contents of the config.ldif file in a String.
266   *
267   * @return a String representing the contents of the config.ldif file.
268   * @throws IOException if there was a problem reading the file
269   */
270  public String getContents() throws IOException {
271    if (contents == null) {
272      load();
273    }
274    return contents;
275  }
276
277  /**
278   * Provides the contents of the config.ldif file in a lower case String.
279   *
280   * @return a lower case String representing the contents of the config.ldif
281   * file.
282   * @throws IOException if there was a problem reading the file
283   */
284  public String getLowerCaseContents() throws IOException {
285    if (lowerCaseContents == null) {
286      load();
287    }
288    return lowerCaseContents;
289  }
290
291  /**
292   * Returns the list of paths where the databases are installed as they appear
293   * in the configuration file.
294   *
295   * @return the list of paths where the databases are installed as they appear
296   * in the configuration file.
297   * @throws IOException if there is a problem reading the config file.
298   */
299  public Set<String> getDatabasePaths() throws IOException {
300    return getConfigurationValues("ds-cfg-db-directory");
301  }
302
303  /**
304   * Returns the list of base dns as they appear in the configuration file.
305   *
306   * @return the list of base dns as they appear in the configuration file.
307   * @throws IOException if there is a problem reading the config file.
308   */
309  public Set<String> getBaseDNs() throws IOException {
310    return getConfigurationValues("ds-cfg-base-dn");
311  }
312
313  /**
314   * Loads the contents of the configuration file into memory.
315   * @throws IOException if there were problems loading the file
316   */
317  private void load() throws IOException
318  {
319    StringBuilder buf = new StringBuilder();
320    FileReader reader = new FileReader(file);
321    BufferedReader in = new BufferedReader(reader);
322    String line;
323    // We do not care about encoding: we are just interested in the ports
324    while ((line = in.readLine()) != null) {
325      buf.append(line).append(Constants.LINE_SEPARATOR);
326    }
327    reader.close();
328    contents = buf.toString();
329    lowerCaseContents = contents.toLowerCase();
330  }
331
332  private Set<String> getConfigurationValues(String attrName)
333          throws IOException
334  {
335    Set<String> set = new HashSet<>();
336    attrName += ":";
337    String lowerCaseContents = getLowerCaseContents();
338    String contents = getContents();
339    int index1 = lowerCaseContents.indexOf(attrName);
340    while (index1 != -1) {
341      int index2 = lowerCaseContents.indexOf(Constants.LINE_SEPARATOR, index1);
342      String value;
343      if (index2 > index1 + attrName.length()) {
344        value = contents.substring(attrName.length() + index1, index2).trim();
345      } else if (lowerCaseContents.length() > index1 + attrName.length()) {
346        // Assume end of file
347        value = contents.substring(attrName.length() + index1).trim();
348      } else {
349        value = null;
350      }
351
352      if (value != null && value.length() > 0) {
353        set.add(value);
354      }
355
356      index1 = lowerCaseContents.indexOf(attrName,
357              index1 + attrName.length());
358    }
359    return set;
360  }
361}