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}