1 5 package com.tc.object.tools; 6 7 import com.tc.logging.TCLogger; 8 import com.tc.logging.TCLogging; 9 import com.tc.object.NotInBootJar; 10 import com.tc.util.Assert; 11 import com.tc.util.ProductInfo; 12 13 import java.io.File ; 14 import java.io.FileNotFoundException ; 15 import java.io.FileOutputStream ; 16 import java.io.IOException ; 17 import java.util.Enumeration ; 18 import java.util.HashMap ; 19 import java.util.HashSet ; 20 import java.util.Iterator ; 21 import java.util.Map ; 22 import java.util.Set ; 23 import java.util.StringTokenizer ; 24 import java.util.jar.Attributes ; 25 import java.util.jar.JarEntry ; 26 import java.util.jar.JarFile ; 27 import java.util.jar.JarOutputStream ; 28 import java.util.jar.Manifest ; 29 30 public class BootJar { 31 private static final TCLogger logger = TCLogging.getLogger(BootJar.class); 32 33 private static final String DSO_BOOT_JAR_PATTERN = ".+dso-boot.*\\.jar$"; 34 static final String JAR_NAME_PREFIX = "dso-boot-"; 35 36 private static final String VERSION_1_1 = "1.1"; 37 38 private static final State STATE_OPEN = new State("OPEN"); 39 private static final State STATE_CLOSED = new State("CLOSED"); 40 41 public static final String PREINSTRUMENTED_NAME = "Preinstrumented"; 42 43 private final File file; 44 private final Map entries; 45 private final boolean openForWrite; 46 private final Manifest manifest; 47 private final BootJarMetaData metaData; 48 private final JarFile jarFileInput; 49 private final Set classes = new HashSet (); 50 private State state; 51 private boolean creationErrorOccurred; 52 53 private BootJar(File file, boolean openForWrite, BootJarMetaData metaData, JarFile jarFile) { 54 Assert.assertNotNull(file); 55 56 this.file = file; 57 this.openForWrite = openForWrite; 58 this.metaData = metaData; 59 this.jarFileInput = jarFile; 60 this.manifest = new Manifest (); 61 this.entries = new HashMap (); 62 63 this.state = STATE_OPEN; 64 this.creationErrorOccurred = false; 65 } 66 67 public static BootJar getBootJarForWriting(File bootJar) throws UnsupportedVMException { 68 String vmSignature = BootJarSignature.getSignatureForThisVM().getSignature(); 69 return getBootJarForWriting(bootJar, vmSignature); 70 } 71 72 public static BootJar getBootJarForReading(File bootJar) throws IOException , BootJarException { 73 return getBootJarForReading(bootJar, BootJarSignature.getSignatureForThisVM()); 74 } 75 76 static BootJar getBootJarForWriting(File bootJar, String vmSignature) { 77 return getBootJarForWriting(bootJar, vmSignature, VERSION_1_1); 78 } 79 80 static BootJar getBootJarForWriting(File bootJar, String vmSignature, String metaDataVersion) { 81 return new BootJar(bootJar, true, new BootJarMetaData(vmSignature, metaDataVersion), null); 82 } 83 84 static BootJar getBootJarForReading(File bootJar, BootJarSignature expectedSignature) throws IOException , 85 BootJarException { 86 87 if (!existingFileIsAccessible(bootJar)) throw new FileNotFoundException ("Cannot access file: " 88 + bootJar.getAbsolutePath()); 89 90 JarFile jarFile = new JarFile (bootJar, false); 91 Manifest manifest = jarFile.getManifest(); 92 BootJarMetaData metaData = new BootJarMetaData(manifest); 93 94 BootJarSignature signatureFromJar = new BootJarSignature(metaData.getVMSignature()); 96 97 if (!expectedSignature.isCompatibleWith(signatureFromJar)) { 98 throw new InvalidJVMVersionException( 100 "Incompatible boot jar JVM version; expected '" 101 + expectedSignature 102 + "' but was (in boot jar) '" 103 + signatureFromJar 104 + "'; Please regenerate the DSO boot jar to match this VM, or switch to a VM compatible with this boot jar"); 105 } 106 107 return new BootJar(bootJar, false, metaData, jarFile); 108 } 109 110 public static File findBootJar() throws FileNotFoundException { 111 return findBootJarByPattern(DSO_BOOT_JAR_PATTERN); 112 } 113 114 private static File findBootJarByPattern(String pattern) throws FileNotFoundException { 115 String bootClassPath = System.getProperty("sun.boot.class.path"); 116 StringTokenizer st = new StringTokenizer (bootClassPath, System.getProperty("path.separator")); 117 while (st.hasMoreTokens()) { 118 String element = st.nextToken(); 119 if (element.matches(pattern)) { return new File (element); } 120 } 121 throw new FileNotFoundException ("Can't find boot jar matching pattern (" + pattern + ") in boot classpath: " 122 + bootClassPath); 123 } 124 125 public static BootJar getDefaultBootJarForReading() throws BootJarException, IOException { 126 return getBootJarForReading(findBootJar()); 127 } 128 129 private static boolean existingFileIsAccessible(File file) { 130 return file.exists() || file.isFile() || file.canRead(); 131 } 132 133 public static String classNameToFileName(String className) { 134 return className.replace('.', '/') + ".class"; 135 } 136 137 public static String fileNameToClassName(String filename) { 138 if (!filename.endsWith(".class")) throw new AssertionError ("Invalid class file name: " + filename); 139 return filename.substring(0, filename.lastIndexOf('.')).replace('/', '.'); 140 } 141 142 public boolean classLoaded(String className) { 143 return classes.contains(className); 144 } 145 146 public void loadClassIntoJar(String className, byte[] data, boolean isPreinstrumented) { 147 boolean added = classes.add(className); 148 149 Assert.assertTrue("Duplicate class added " + className, added); 152 153 if (className.equals(NotInBootJar.class.getName())) { 154 throw new AssertionError ("Invalid class for boot jar: " + className); 156 } 157 158 String cn = classNameToFileName(className); 159 JarEntry jarEntry = new JarEntry (cn); 160 basicLoadClassIntoJar(jarEntry, data, isPreinstrumented); 161 } 162 163 private void assertWrite() { 164 if (!openForWrite) throw new AssertionError ("boot jar not open for writing"); 165 } 166 167 private synchronized void basicLoadClassIntoJar(JarEntry je, byte[] classBytes, boolean isPreinstrumented) { 168 assertWrite(); 169 170 Attributes attributes = manifest.getAttributes(je.getName()); 171 if (attributes == null) { 172 attributes = makeAttributesFor(je.getName()); 173 } 174 attributes.put(new Attributes.Name (PREINSTRUMENTED_NAME), Boolean.toString(isPreinstrumented)); 175 entries.put(new JarEntryWrapper(je, new Boolean (isPreinstrumented)), classBytes); 176 } 177 178 private Attributes makeAttributesFor(String resource) { 179 Attributes rv = new Attributes (); 180 manifest.getEntries().put(resource, rv); 181 return rv; 182 } 183 184 public synchronized Set getAllPreInstrumentedClasses() throws IOException { 185 assertOpen(); 186 Set rv = new HashSet (); 187 for (Enumeration e = jarFileInput.entries(); e.hasMoreElements();) { 188 JarEntry entry = (JarEntry ) e.nextElement(); 189 String entryName = entry.getName(); 190 191 if (entryName.toLowerCase().endsWith(".class")) { 195 if (isPreInstrumentedEntry(entry)) { 196 rv.add(fileNameToClassName(entry.getName())); 197 } 198 } 199 } 200 return rv; 201 } 202 203 private void assertOpen() { 204 if (state != STATE_OPEN) { throw new AssertionError ("boot jar not open: " + state); } 205 } 206 207 private boolean isPreInstrumentedEntry(JarEntry entry) throws IOException { 208 Attributes attributes = entry.getAttributes(); 209 if (attributes == null) throw new AssertionError ("Invalid jar file: No attributes for jar entry: " 210 + entry.getName()); 211 String value = attributes.getValue(PREINSTRUMENTED_NAME); 212 if (value == null) throw new AssertionError ("Invalid jar file: No " + PREINSTRUMENTED_NAME 213 + " attribute for jar entry: " + entry.getName()); 214 return Boolean.valueOf(value).booleanValue(); 215 } 216 217 private void writeEntries(JarOutputStream out) throws IOException { 218 for (Iterator i = entries.keySet().iterator(); i.hasNext();) { 219 JarEntryWrapper je = (JarEntryWrapper) i.next(); 220 byte[] classBytes = (byte[]) entries.get(je); 221 out.putNextEntry(je.getJarEntry()); 222 out.write(classBytes); 223 } 224 } 225 226 public void setCreationErrorOccurred(boolean errorOccurred) { 227 this.creationErrorOccurred = errorOccurred; 228 } 229 230 public synchronized void close() throws IOException { 231 if (state == STATE_OPEN) { 232 if (openForWrite && !this.creationErrorOccurred) { 233 metaData.write(manifest); 234 JarOutputStream out = new JarOutputStream (new FileOutputStream (file, false), manifest); 235 writeEntries(out); 236 out.flush(); 237 out.close(); 238 } 239 240 if (jarFileInput != null) { 241 jarFileInput.close(); 242 } 243 } 244 245 state = STATE_CLOSED; 246 } 247 248 253 public boolean checkSourceVersion() { 254 return false; 255 } 256 257 static class BootJarMetaData { 258 private static final String META_DATA_ATTRIBUTE_NAME = "DSO_BOOTJAR_METADATA"; 259 private static final String TC_VERSION = "TC_VERSION"; 260 private static final String TC_MONIKER = "TC_MONIKER"; 261 private static final String VERSION = "VERSION"; 262 private static final String VM_SIGNATURE = "VM_SIGNATURE"; 263 264 private final String vmSignature; 265 private final String version; 266 private final String tcversion; 267 private final String tcmoniker; 268 269 BootJarMetaData(String vmSignature, String version) { 270 Assert.assertNotNull(vmSignature); 271 Assert.assertNotNull(version); 272 this.vmSignature = vmSignature; 273 this.version = version; 274 this.tcversion = null; 275 this.tcmoniker = null; 276 } 277 278 BootJarMetaData(Manifest manifest) throws BootJarException { 279 Assert.assertNotNull(manifest); 280 Attributes attributes = (Attributes ) manifest.getEntries().get(META_DATA_ATTRIBUTE_NAME); 281 if (attributes == null) throw new InvalidBootJarMetaDataException( 282 "Missing attributes in jar manifest. Please regenerate boot jar"); 283 284 version = attributes.getValue(VERSION); 285 if (version == null) throw new InvalidBootJarMetaDataException("Missing metadata: version"); 286 287 String expect_version = VERSION_1_1; 288 if (expect_version.equals(version)) { 289 vmSignature = attributes.getValue(VM_SIGNATURE); 290 if (vmSignature == null) throw new InvalidJVMVersionException("Missing vm signature"); 291 } else { 292 throw new InvalidBootJarMetaDataException("Incompatible DSO meta data: version; expected '" + expect_version 293 + "' but was (in boot jar): '" + version 294 + "'; please regenerate the DSO boot jar"); 295 } 296 297 tcversion = attributes.getValue(TC_VERSION); 298 if (tcversion == null) throw new InvalidBootJarMetaDataException("Missing metadata: tcversion"); 299 300 tcmoniker = attributes.getValue(TC_MONIKER); 301 if (tcmoniker == null) throw new InvalidBootJarMetaDataException("Missing metadata: tcmoniker"); 302 303 ProductInfo productInfo = ProductInfo.getThisProductInfo(); 304 String expect_tcversion = productInfo.buildVersion(); 305 306 if (productInfo.isDevMode()) logger 307 .warn("The value for the DSO meta data, tcversion is: '" 308 + expect_tcversion 309 + "'; this might not be correct, this value is used only under development mode or when tests are being run."); 310 311 if (!productInfo.isDevMode() && !expect_tcversion.equals(tcversion)) throw new InvalidBootJarMetaDataException( 312 "Incompatible DSO meta data: tcversion; expected '" 313 + expect_tcversion 314 + "' but was (in boot jar): '" 315 + tcversion 316 + "'; please regenerate the DSO boot jar"); 317 } 318 319 public void write(Manifest manifest) { 320 if (VERSION_1_1.equals(version)) { 321 ProductInfo productInfo = ProductInfo.getThisProductInfo(); 322 Attributes attributes = new Attributes (); 323 attributes.put(new Attributes.Name (TC_MONIKER), productInfo.moniker()); 324 attributes.put(new Attributes.Name (TC_VERSION), productInfo.buildVersion()); 325 attributes.put(new Attributes.Name (VERSION), getVersion()); 326 attributes.put(new Attributes.Name (VM_SIGNATURE), getVMSignature()); 327 Object prev = manifest.getEntries().put(META_DATA_ATTRIBUTE_NAME, attributes); 328 Assert.assertNull(prev); 329 } else { 330 throw new AssertionError ("Unexptected metadata for version, expecting '" + VERSION_1_1 + "', but was '" + version + "'"); 331 } 332 } 333 334 public String getTCVersion() { 335 return this.tcversion; 336 } 337 338 public String getVMSignature() { 339 return this.vmSignature; 340 } 341 342 public String getVersion() { 343 return this.version; 344 } 345 } 346 347 private static class JarEntryWrapper { 348 private final JarEntry jarEntry; 349 private final Boolean isPreinstrumented; 350 351 private JarEntryWrapper(JarEntry jarEntry, Boolean isPreinstrumented) { 352 this.jarEntry = jarEntry; 353 this.isPreinstrumented = isPreinstrumented; 354 } 355 356 public JarEntry getJarEntry() { 357 return jarEntry; 358 } 359 360 public Boolean isPreistrumented() { 361 return isPreinstrumented; 362 } 363 } 364 365 private static class State { 366 private final String name; 367 368 private State(String name) { 369 this.name = name; 370 } 371 372 public String toString() { 373 return this.name; 374 } 375 } 376 } 377 | Popular Tags |