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);
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);
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+)\\.(-?.+))");
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, 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, DynamicConstants.REVISION);
237    bi.values.put(URL_REPOSITORY,
238            String.valueOf(DynamicConstants.URL_REPOSITORY));
239    bi.values.put(FIX_IDS, DynamicConstants.FIX_IDS);
240    bi.values.put(DEBUG_BUILD, String.valueOf(DynamicConstants.DEBUG_BUILD));
241    bi.values.put(BUILD_OS, DynamicConstants.BUILD_OS);
242    bi.values.put(BUILD_USER, DynamicConstants.BUILD_USER);
243    bi.values.put(BUILD_JAVA_VERSION, DynamicConstants.BUILD_JAVA_VERSION);
244    bi.values.put(BUILD_JAVA_VENDOR, DynamicConstants.BUILD_JAVA_VENDOR);
245    bi.values.put(BUILD_JVM_VERSION, DynamicConstants.BUILD_JVM_VERSION);
246    bi.values.put(BUILD_JVM_VENDOR, DynamicConstants.BUILD_JVM_VENDOR);
247
248    // Make sure we got values for important properties that are used
249    // in compareTo, equals, and hashCode
250    checkNotNull(bi.values,
251            NAME,
252            MAJOR_VERSION,
253            MINOR_VERSION,
254            POINT_VERSION,
255            REVISION);
256
257    return bi;
258  }
259
260  private Map<String, String> values = new HashMap<>();
261
262  /**
263   * Gets the name of this build.  This is the first line of the output
264   * from invoking start-ds -F.
265   * @return String representing the name of the build
266   */
267  public String getName() {
268    return values.get(NAME);
269  }
270
271  /**
272   * Gets the build ID which is the 14 digit number code like 20070420110336.
273   *
274   * @return String representing the build ID
275   */
276  public String getBuildId() {
277    return values.get(BUILD_ID);
278  }
279
280  /**
281   * Gets the major version.
282   *
283   * @return String representing the major version
284   */
285  public Integer getMajorVersion() {
286    return Integer.valueOf(values.get(MAJOR_VERSION));
287  }
288
289  /**
290   * Gets the minor version.
291   *
292   * @return String representing the minor version
293   */
294  public Integer getMinorVersion() {
295    return Integer.valueOf(values.get(MINOR_VERSION));
296  }
297
298  /**
299   * Gets the point version.
300   *
301   * @return String representing the point version
302   */
303  public Integer getPointVersion() {
304    return Integer.valueOf(values.get(POINT_VERSION));
305  }
306
307  /**
308   * Gets the VCS revision.
309   *
310   * @return String representing the VCS revision
311   */
312  public String getRevision() {
313    return values.get(REVISION);
314  }
315
316  /** {@inheritDoc} */
317  @Override
318  public String toString() {
319    StringBuilder sb = new StringBuilder();
320    sb.append(getName());
321    String id = getBuildId();
322    if (id != null) {
323      sb.append(" (")
324              .append(INFO_GENERAL_BUILD_ID.get())
325              .append(": ")
326              .append(id)
327              .append(")");
328    }
329    return sb.toString();
330  }
331
332  /** {@inheritDoc} */
333  @Override
334  public int compareTo(BuildInformation bi) {
335    if (getMajorVersion().equals(bi.getMajorVersion())) {
336      if (getMinorVersion().equals(bi.getMinorVersion())) {
337        if (getPointVersion().equals(bi.getPointVersion())) {
338          if (getRevision().equals(bi.getRevision())) {
339            return 0;
340          } else if (getRevision().compareTo(bi.getRevision()) < 0) {
341            return -1;
342          }
343        } else if (getPointVersion() < bi.getPointVersion()) {
344          return -1;
345        }
346      } else if (getMinorVersion() < bi.getMinorVersion()) {
347        return -1;
348      }
349    } else if (getMajorVersion() < bi.getMajorVersion()) {
350      return -1;
351    }
352    return 1;
353  }
354
355  /** {@inheritDoc} */
356  @Override
357  public boolean equals(Object o) {
358    if (this == o) {
359      return true;
360    }
361    return o != null
362        && getClass() == o.getClass()
363        && compareTo((BuildInformation)o) == 0;
364  }
365
366  /** {@inheritDoc} */
367  @Override
368  public int hashCode() {
369    int hc = 11;
370    hc = 31 * hc + getMajorVersion().hashCode();
371    hc = 31 * hc + getMinorVersion().hashCode();
372    hc = 31 * hc + getPointVersion().hashCode();
373    hc = 31 * hc + getRevision().hashCode();
374    return hc;
375  }
376
377  private static void checkNotNull(Map<?, ?> values, String... props)
378          throws ApplicationException {
379    for (String prop : props) {
380      if (null == values.get(prop)) {
381        throw new ApplicationException(
382                ReturnCode.TOOL_ERROR,
383                INFO_ERROR_PROP_VALUE.get(prop), null);
384      }
385    }
386  }
387
388}