1 /*- 2 * See the file LICENSE for redistribution information. 3 * 4 * Copyright (c) 2002, 2013 Oracle and/or its affiliates. All rights reserved. 5 * 6 */ 7 8 package com.sleepycat.persist.model; 9 10 import java.io.File; 11 import java.io.FileInputStream; 12 import java.io.FileOutputStream; 13 import java.io.IOException; 14 import java.lang.instrument.ClassFileTransformer; 15 import java.lang.instrument.Instrumentation; 16 import java.security.ProtectionDomain; 17 import java.util.ArrayList; 18 import java.util.HashSet; 19 import java.util.List; 20 import java.util.Set; 21 import java.util.StringTokenizer; 22 23 import com.sleepycat.asm.ClassReader; 24 import com.sleepycat.asm.ClassVisitor; 25 import com.sleepycat.asm.ClassWriter; 26 27 /** 28 * Enhances the bytecode of persistent classes to provide efficient access to 29 * fields and constructors, and to avoid special security policy settings for 30 * accessing non-public members. Classes are enhanced if they are annotated 31 * with {@link Entity} or {@link Persistent}. 32 * 33 * <p>{@code ClassEnhancer} objects are thread-safe. Multiple threads may 34 * safely call the methods of a shared {@code ClassEnhancer} object.</p> 35 * 36 * <p>As described in the {@link <a 37 * href="../package-summary.html#bytecode">package summary</a>}, bytecode 38 * enhancement may be used either at runtime or offline (at build time).</p> 39 * 40 * <p>To use enhancement offline, this class may be used as a {@link #main main 41 * program}. 42 * </p> 43 * 44 * <p>For enhancement at runtime, this class provides the low level support 45 * needed to transform class bytes during class loading. To configure runtime 46 * enhancement you may use one of the following approaches:</p> 47 * <ol> 48 * <li>The BDB {@code je-<version>.jar} or {@code db.jar} file may be used as 49 * an instrumentation agent as follows: 50 * <pre class="code">{@literal java -javaagent:<BDB-JAR-FILE>=enhance:packageNames ...}</pre> 51 * {@code packageNames} is a comma separated list of packages containing 52 * persistent classes. Sub-packages of these packages are also searched. If 53 * {@code packageNames} is omitted then all packages known to the current 54 * classloader are searched. 55 * <p>The "-v" option may be included in the comma separated list to print the 56 * name of each class that is enhanced.</p></li> 57 * <br> 58 * <li>The {@link #enhance} method may be called to implement a class loader 59 * that performs enhancement. Using this approach, it is the developer's 60 * responsibility to implement and configure the class loader.</li> 61 * </ol> 62 * 63 * @author Mark Hayes 64 */ 65 public class ClassEnhancer implements ClassFileTransformer { 66 67 private static final String AGENT_PREFIX = "enhance:"; 68 69 private Set<String> packagePrefixes; 70 private boolean verbose; 71 72 /** 73 * Enhances classes in the directories specified. The class files are 74 * replaced when they are enhanced, without changing the file modification 75 * date. For example: 76 * 77 * <pre class="code">java -cp je-<version>.jar com.sleepycat.persist.model.ClassEnhancer ./classes</pre> 78 * 79 * <p>The "-v" argument may be specified to print the name of each class 80 * file that is enhanced. The total number of class files enhanced will 81 * always be printed.</p> 82 * 83 * @param args one or more directories containing classes to be enhanced. 84 * Subdirectories of these directories will also be searched. Optionally, 85 * -v may be included to print the name of every class file enhanced. 86 */ main(String[] args)87 public static void main(String[] args) throws Exception { 88 try { 89 boolean verbose = false; 90 List<File> fileList = new ArrayList<File>(); 91 for (int i = 0; i < args.length; i += 1) { 92 String arg = args[i]; 93 if (arg.startsWith("-")) { 94 if ("-v".equals(args[i])) { 95 verbose = true; 96 } else { 97 throw new IllegalArgumentException 98 ("Unknown arg: " + arg); 99 } 100 } else { 101 fileList.add(new File(arg)); 102 } 103 } 104 ClassEnhancer enhancer = new ClassEnhancer(); 105 enhancer.setVerbose(verbose); 106 int nFiles = 0; 107 for (File file : fileList) { 108 nFiles += enhancer.enhanceFile(file); 109 } 110 if (nFiles > 0) { 111 System.out.println("Enhanced: " + nFiles + " files"); 112 } 113 } catch (Exception e) { 114 e.printStackTrace(); 115 throw e; 116 } 117 } 118 119 /** 120 * Enhances classes as specified by a JVM -javaagent argument. 121 * 122 * @see java.lang.instrument.Instrumentation 123 */ premain(String args, Instrumentation inst)124 public static void premain(String args, Instrumentation inst) { 125 if (!args.startsWith(AGENT_PREFIX)) { 126 throw new IllegalArgumentException 127 ("Unknown javaagent args: " + args + 128 " Args must start with: \"" + AGENT_PREFIX + '"'); 129 } 130 args = args.substring(AGENT_PREFIX.length()); 131 Set<String> packageNames = null; 132 boolean verbose = false; 133 if (args.length() > 0) { 134 packageNames = new HashSet<String>(); 135 StringTokenizer tokens = new StringTokenizer(args, ","); 136 while (tokens.hasMoreTokens()) { 137 String token = tokens.nextToken(); 138 if (token.startsWith("-")) { 139 if (token.equals("-v")) { 140 verbose = true; 141 } else { 142 throw new IllegalArgumentException 143 ("Unknown javaagent arg: " + token); 144 } 145 } else { 146 packageNames.add(token); 147 } 148 } 149 } 150 ClassEnhancer enhancer = new ClassEnhancer(packageNames); 151 enhancer.setVerbose(verbose); 152 inst.addTransformer(enhancer); 153 } 154 155 /** 156 * Creates a class enhancer that searches all packages. 157 */ ClassEnhancer()158 public ClassEnhancer() { 159 } 160 161 /** 162 * Sets verbose mode. 163 * 164 * <p>True may be specified to print the name of each class file that is 165 * enhanced. This property is false by default.</p> 166 */ setVerbose(boolean verbose)167 public void setVerbose(boolean verbose) { 168 this.verbose = verbose; 169 } 170 171 /** 172 * Gets verbose mode. 173 * 174 * @see #setVerbose 175 */ getVerbose()176 public boolean getVerbose() { 177 return verbose; 178 } 179 180 /** 181 * Creates a class enhancer that searches a given set of packages. 182 * 183 * @param packageNames a set of packages to search for persistent 184 * classes. Sub-packages of these packages are also searched. If empty or 185 * null, all packages known to the current classloader are searched. 186 */ ClassEnhancer(Set<String> packageNames)187 public ClassEnhancer(Set<String> packageNames) { 188 if (packageNames != null) { 189 packagePrefixes = new HashSet<String>(); 190 for (String name : packageNames) { 191 packagePrefixes.add(name + '.'); 192 } 193 } 194 } 195 transform(ClassLoader loader, String className, Class<?> classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer)196 public byte[] transform(ClassLoader loader, 197 String className, 198 Class<?> classBeingRedefined, 199 ProtectionDomain protectionDomain, 200 byte[] classfileBuffer) { 201 className = className.replace('/', '.'); 202 byte[] bytes = enhance(className, classfileBuffer); 203 if (verbose && bytes != null) { 204 System.out.println("Enhanced: " + className); 205 } 206 return bytes; 207 } 208 209 /** 210 * Enhances the given class bytes if the class is annotated with {@link 211 * Entity} or {@link Persistent}. 212 * 213 * @param className the class name in binary format; for example, 214 * "my.package.MyClass$Name", or null if no filtering by class name 215 * should be performed. 216 * 217 * @param classBytes are the class file bytes to be enhanced. 218 * 219 * @return the enhanced bytes, or null if no enhancement was performed. 220 */ enhance(String className, byte[] classBytes)221 public byte[] enhance(String className, byte[] classBytes) { 222 if (className != null && packagePrefixes != null) { 223 for (String prefix : packagePrefixes) { 224 if (className.startsWith(prefix)) { 225 return enhanceBytes(classBytes); 226 } 227 } 228 return null; 229 } else { 230 return enhanceBytes(classBytes); 231 } 232 } 233 enhanceFile(File file)234 int enhanceFile(File file) 235 throws IOException { 236 237 int nFiles = 0; 238 if (file.isDirectory()) { 239 String[] names = file.list(); 240 if (names != null) { 241 for (int i = 0; i < names.length; i += 1) { 242 nFiles += enhanceFile(new File(file, names[i])); 243 } 244 } 245 } else if (file.getName().endsWith(".class")) { 246 byte[] newBytes = enhanceBytes(readFile(file)); 247 if (newBytes != null) { 248 long modified = file.lastModified(); 249 writeFile(file, newBytes); 250 file.setLastModified(modified); 251 nFiles += 1; 252 if (verbose) { 253 System.out.println("Enhanced: " + file); 254 } 255 } 256 } 257 return nFiles; 258 } 259 readFile(File file)260 private byte[] readFile(File file) 261 throws IOException { 262 263 byte[] bytes = new byte[(int) file.length()]; 264 FileInputStream in = new FileInputStream(file); 265 try { 266 in.read(bytes); 267 } finally { 268 in.close(); 269 } 270 return bytes; 271 } 272 writeFile(File file, byte[] bytes)273 private void writeFile(File file, byte[] bytes) 274 throws IOException { 275 276 FileOutputStream out = new FileOutputStream(file); 277 try { 278 out.write(bytes); 279 } finally { 280 out.close(); 281 } 282 } 283 enhanceBytes(byte[] bytes)284 private byte[] enhanceBytes(byte[] bytes) { 285 286 /* 287 * The writer is at the end of the visitor chain. Pass COMPUTE_FRAMES 288 * to calculate stack size, for safety. 289 */ 290 ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES); 291 ClassVisitor visitor = writer; 292 293 /* The enhancer is at the beginning of the visitor chain. */ 294 visitor = new BytecodeEnhancer(visitor); 295 296 /* The reader processes the class and invokes the visitors. */ 297 ClassReader reader = new ClassReader(bytes); 298 try { 299 300 /* 301 * Pass false for skipDebug since we are rewriting the class and 302 * should include all information. 303 */ 304 reader.accept(visitor, 0); 305 return writer.toByteArray(); 306 } catch (BytecodeEnhancer.NotPersistentException e) { 307 /* The class is not persistent and should not be enhanced. */ 308 return null; 309 } 310 } 311 } 312