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