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