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 legal-notices/CDDLv1_0.txt
010 * or http://forgerock.org/license/CDDLv1.0.html.
011 * See the License for the specific language governing permissions
012 * and limitations under the License.
013 *
014 * When distributing Covered Code, include this CDDL HEADER in each
015 * file and include the License file at legal-notices/CDDLv1_0.txt.
016 * If applicable, add the following below this CDDL HEADER, with the
017 * fields enclosed by brackets "[]" replaced with your own identifying
018 * information:
019 *      Portions Copyright [yyyy] [name of copyright owner]
020 *
021 * CDDL HEADER END
022 *
023 *
024 *      Copyright 2006-2010 Sun Microsystems, Inc.
025 *      Portions Copyright 2011-2015 ForgeRock AS
026 */
027package org.opends.quicksetup;
028
029import static org.opends.messages.QuickSetupMessages.*;
030import static org.opends.server.util.SetupUtils.*;
031import static com.forgerock.opendj.util.OperatingSystem.isWindows;
032
033import org.forgerock.i18n.LocalizableMessage;
034import org.forgerock.i18n.slf4j.LocalizedLogger;
035import org.opends.quicksetup.util.Utils;
036import org.opends.server.util.DynamicConstants;
037import org.opends.server.util.SetupUtils;
038import org.opends.server.util.StaticUtils;
039
040import java.io.*;
041import java.util.ArrayList;
042import java.util.HashMap;
043import java.util.List;
044import java.util.Map;
045import java.util.regex.Pattern;
046import java.util.regex.Matcher;
047
048/**
049 * Represents information about the current build that is
050 * publicly obtainable by invoking start-ds -F.
051 */
052public class BuildInformation implements Comparable<BuildInformation> {
053
054  private static final LocalizedLogger logger = LocalizedLogger.getLoggerForThisClass();
055
056  /**
057   * Reads build information for a particular installation by reading the
058   * output from invoking the start-ds tool with the full information option.
059   * @param installation from which to gather build information
060   * @return BuildInformation object populated with information
061   * @throws ApplicationException if all or some important information could
062   * not be determined
063   */
064  public static BuildInformation create(Installation installation)
065          throws ApplicationException {
066    BuildInformation bi = new BuildInformation();
067    List<String> args = new ArrayList<>();
068    args.add(Utils.getScriptPath(
069        Utils.getPath(installation.getServerStartCommandFile())));
070    args.add("-F"); // full verbose
071    ProcessBuilder pb = new ProcessBuilder(args);
072    InputStream is = null;
073    OutputStream out = null;
074    final boolean[] done = {false};
075    try {
076      Map<String, String> env = pb.environment();
077      env.put(SetupUtils.OPENDJ_JAVA_HOME, System.getProperty("java.home"));
078      // This is required in order the return code to be valid.
079      env.put("OPENDJ_EXIT_NO_BACKGROUND", "true");
080      final Process process = pb.start();
081      is = process.getInputStream();
082      out = process.getOutputStream();
083      final OutputStream fOut = out;
084      if (isWindows())
085      {
086        // In windows if there is an error we wait the user to click on
087        // return to continue.
088        Thread t = new Thread(new Runnable()
089        {
090          @Override
091          public void run()
092          {
093            while (!done[0])
094            {
095              try
096              {
097                Thread.sleep(15000);
098                if (!done[0])
099                {
100                  fOut.write(Constants.LINE_SEPARATOR.getBytes());
101                  fOut.flush();
102                }
103              }
104              catch (Throwable t)
105              {
106                logger.warn(LocalizableMessage.raw("Error writing to process: "+t, t));
107              }
108            }
109          }
110        });
111        t.start();
112      }
113      BufferedReader reader = new BufferedReader(new InputStreamReader(is));
114      String line = reader.readLine();
115      bi.values.put(NAME, line);
116      StringBuilder sb = new StringBuilder();
117      while (null != (line = reader.readLine())) {
118        if (sb.length() > 0)
119        {
120          sb.append('\n');
121        }
122        sb.append(line);
123        int colonIndex = line.indexOf(':');
124        if (-1 != colonIndex) {
125          String name = line.substring(0, colonIndex).trim();
126          String value = line.substring(colonIndex + 1).trim();
127          bi.values.put(name, value);
128        }
129      }
130      int resultCode = process.waitFor();
131      if (resultCode != 0)
132      {
133        if (sb.length() == 0)
134        {
135          throw new ApplicationException(
136            ReturnCode.START_ERROR,
137            INFO_ERROR_CREATING_BUILD_INFO.get(), null);
138        }
139        else
140        {
141          try
142          {
143            checkNotNull(bi.values,
144                NAME,
145                MAJOR_VERSION,
146                MINOR_VERSION,
147                POINT_VERSION,
148                REVISION_NUMBER);
149          }
150          catch (Throwable t)
151          {
152            // We did not get the required information.
153            throw new ApplicationException(
154                ReturnCode.START_ERROR,
155                INFO_ERROR_CREATING_BUILD_INFO_MSG.get(sb),
156                null);
157          }
158        }
159      }
160    } catch (IOException | InterruptedException e) {
161      throw new ApplicationException(
162          ReturnCode.START_ERROR,
163          INFO_ERROR_CREATING_BUILD_INFO.get(), e);
164
165    } finally {
166      done[0] = true;
167      StaticUtils.close(is, out);
168    }
169
170    // Make sure we got values for important properties that are used
171    // in compareTo, equals, and hashCode
172    checkNotNull(bi.values,
173            NAME,
174            MAJOR_VERSION,
175            MINOR_VERSION,
176            POINT_VERSION,
177            REVISION_NUMBER);
178
179    return bi;
180  }
181
182  /**
183   * Creates an instance from a string representing a build number
184   * of the for MAJOR.MINOR.POINT.REVISION where MAJOR, MINOR, POINT,
185   * and REVISION are integers.
186   * @param bn String representation of a build number
187   * @return a BuildInformation object populated with the information
188   * provided in <code>bn</code>
189   * @throws IllegalArgumentException if <code>bn</code> is not a build
190   * number
191   */
192  public static BuildInformation fromBuildString(String bn) throws
193    IllegalArgumentException
194  {
195    // -------------------------------------------------------
196    // NOTE:  if you change this be sure to change getBuildString()
197    // -------------------------------------------------------
198
199    // Allow negative revision number for cases where there is no
200    // VCS available.
201    Pattern p = Pattern.compile("((\\d+)\\.(\\d+)\\.(\\d+)\\.(-?\\d+))");
202    Matcher m = p.matcher(bn);
203    if (!m.matches()) {
204      throw new IllegalArgumentException("'" + bn + "' is not a build string");
205    }
206    BuildInformation bi = new BuildInformation();
207    try {
208      bi.values.put(MAJOR_VERSION, m.group(2));
209      bi.values.put(MINOR_VERSION, m.group(3));
210      bi.values.put(POINT_VERSION, m.group(4));
211      bi.values.put(REVISION_NUMBER, m.group(5));
212    } catch (Exception e) {
213      throw new IllegalArgumentException("Error parsing build number " + bn);
214    }
215    return bi;
216  }
217
218  /**
219   * Creates an instance from constants present in the current build.
220   * @return BuildInformation created from current constant values
221   * @throws ApplicationException if all or some important information could
222   * not be determined
223   */
224  public static BuildInformation getCurrent() throws ApplicationException {
225    BuildInformation bi = new BuildInformation();
226    bi.values.put(NAME, DynamicConstants.FULL_VERSION_STRING);
227    bi.values.put(BUILD_ID, DynamicConstants.BUILD_ID);
228    bi.values.put(MAJOR_VERSION,
229            String.valueOf(DynamicConstants.MAJOR_VERSION));
230    bi.values.put(MINOR_VERSION,
231            String.valueOf(DynamicConstants.MINOR_VERSION));
232    bi.values.put(POINT_VERSION,
233            String.valueOf(DynamicConstants.POINT_VERSION));
234    bi.values.put(VERSION_QUALIFIER,
235            String.valueOf(DynamicConstants.VERSION_QUALIFIER));
236    bi.values.put(REVISION_NUMBER,
237            String.valueOf(DynamicConstants.REVISION_NUMBER));
238    bi.values.put(URL_REPOSITORY,
239            String.valueOf(DynamicConstants.URL_REPOSITORY));
240    bi.values.put(FIX_IDS, DynamicConstants.FIX_IDS);
241    bi.values.put(DEBUG_BUILD, String.valueOf(DynamicConstants.DEBUG_BUILD));
242    bi.values.put(BUILD_OS, DynamicConstants.BUILD_OS);
243    bi.values.put(BUILD_USER, DynamicConstants.BUILD_USER);
244    bi.values.put(BUILD_JAVA_VERSION, DynamicConstants.BUILD_JAVA_VERSION);
245    bi.values.put(BUILD_JAVA_VENDOR, DynamicConstants.BUILD_JAVA_VENDOR);
246    bi.values.put(BUILD_JVM_VERSION, DynamicConstants.BUILD_JVM_VERSION);
247    bi.values.put(BUILD_JVM_VENDOR, DynamicConstants.BUILD_JVM_VENDOR);
248
249    // Make sure we got values for important properties that are used
250    // in compareTo, equals, and hashCode
251    checkNotNull(bi.values,
252            NAME,
253            MAJOR_VERSION,
254            MINOR_VERSION,
255            POINT_VERSION,
256            REVISION_NUMBER);
257
258    return bi;
259  }
260
261  private Map<String, String> values = new HashMap<>();
262
263  /**
264   * Gets the name of this build.  This is the first line of the output
265   * from invoking start-ds -F.
266   * @return String representing the name of the build
267   */
268  public String getName() {
269    return values.get(NAME);
270  }
271
272  /**
273   * Gets the build ID which is the 14 digit number code like 20070420110336.
274   *
275   * @return String representing the build ID
276   */
277  public String getBuildId() {
278    return values.get(BUILD_ID);
279  }
280
281  /**
282   * Gets the major version.
283   *
284   * @return String representing the major version
285   */
286  public Integer getMajorVersion() {
287    return Integer.valueOf(values.get(MAJOR_VERSION));
288  }
289
290  /**
291   * Gets the minor version.
292   *
293   * @return String representing the minor version
294   */
295  public Integer getMinorVersion() {
296    return Integer.valueOf(values.get(MINOR_VERSION));
297  }
298
299  /**
300   * Gets the point version.
301   *
302   * @return String representing the point version
303   */
304  public Integer getPointVersion() {
305    return Integer.valueOf(values.get(POINT_VERSION));
306  }
307
308  /**
309   * Gets the SVN revision number.
310   *
311   * @return Integer representing the SVN revision number
312   */
313  public Integer getRevisionNumber() {
314    return Integer.valueOf(values.get(REVISION_NUMBER));
315  }
316
317  /** {@inheritDoc} */
318  @Override
319  public String toString() {
320    StringBuilder sb = new StringBuilder();
321    sb.append(getName());
322    String id = getBuildId();
323    if (id != null) {
324      sb.append(" (")
325              .append(INFO_GENERAL_BUILD_ID.get())
326              .append(": ")
327              .append(id)
328              .append(")");
329    }
330    return sb.toString();
331  }
332
333  /** {@inheritDoc} */
334  @Override
335  public int compareTo(BuildInformation bi) {
336    if (getMajorVersion().equals(bi.getMajorVersion())) {
337      if (getMinorVersion().equals(bi.getMinorVersion())) {
338        if (getPointVersion().equals(bi.getPointVersion())) {
339          if (getRevisionNumber().equals(bi.getRevisionNumber())) {
340            return 0;
341          } else if (getRevisionNumber() < bi.getRevisionNumber()) {
342            return -1;
343          }
344        } else if (getPointVersion() < bi.getPointVersion()) {
345          return -1;
346        }
347      } else if (getMinorVersion() < bi.getMinorVersion()) {
348        return -1;
349      }
350    } else if (getMajorVersion() < bi.getMajorVersion()) {
351      return -1;
352    }
353    return 1;
354  }
355
356  /** {@inheritDoc} */
357  @Override
358  public boolean equals(Object o) {
359    if (this == o) {
360      return true;
361    }
362    return o != null
363        && getClass() == o.getClass()
364        && compareTo((BuildInformation)o) == 0;
365  }
366
367  /** {@inheritDoc} */
368  @Override
369  public int hashCode() {
370    int hc = 11;
371    hc = 31 * hc + getMajorVersion().hashCode();
372    hc = 31 * hc + getMinorVersion().hashCode();
373    hc = 31 * hc + getPointVersion().hashCode();
374    hc = 31 * hc + getRevisionNumber().hashCode();
375    return hc;
376  }
377
378  private static void checkNotNull(Map<?, ?> values, String... props)
379          throws ApplicationException {
380    for (String prop : props) {
381      if (null == values.get(prop)) {
382        throw new ApplicationException(
383                ReturnCode.TOOL_ERROR,
384                INFO_ERROR_PROP_VALUE.get(prop), null);
385      }
386    }
387  }
388
389}