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}