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