001: /*-
002: * See the file LICENSE for redistribution information.
003: *
004: * Copyright (c) 2002,2008 Oracle. All rights reserved.
005: *
006: * $Id: ClassEnhancer.java,v 1.14.2.3 2008/01/07 15:14:20 cwl Exp $
007: */
008:
009: package com.sleepycat.persist.model;
010:
011: import java.io.File;
012: import java.io.FileInputStream;
013: import java.io.FileOutputStream;
014: import java.io.IOException;
015: import java.lang.instrument.ClassFileTransformer;
016: import java.lang.instrument.Instrumentation;
017: import java.security.ProtectionDomain;
018: import java.util.ArrayList;
019: import java.util.HashSet;
020: import java.util.List;
021: import java.util.Set;
022: import java.util.StringTokenizer;
023:
024: import com.sleepycat.asm.ClassReader;
025: import com.sleepycat.asm.ClassVisitor;
026: import com.sleepycat.asm.ClassWriter;
027:
028: /**
029: * Enhances the bytecode of persistent classes to provide efficient access to
030: * fields and constructors, and to avoid special security policy settings for
031: * accessing non-public members. Classes are enhanced if they are annotated
032: * with {@link Entity} or {@link Persistent}.
033: *
034: * <p>{@code ClassEnhancer} objects are thread-safe. Multiple threads may
035: * safely call the methods of a shared {@code ClassEnhancer} object.</p>
036: *
037: * <p>As described in the {@link <a
038: * href="../package-summary.html#bytecode">package summary</a>}, bytecode
039: * enhancement may be used either at runtime or offline (at build time).</p>
040: *
041: * <p>To use enhancement offline, this class may be used as a {@link #main main
042: * program} or via an {@link ClassEnhancerTask ant task}.</p>
043: *
044: * <p>For enhancement at runtime, this class provides the low level support
045: * needed to transform class bytes during class loading. To configure runtime
046: * enhancement you may use one of the following approaches:</p>
047: * <ol>
048: * <li>For Java 1.5, the {@code je-<version>.jar} file may be used as an instrumentation
049: * agent as follows:
050: * <pre class="code">{@literal java -javaagent:lib/je-<version>.jar=enhance:packageNames ...}</pre>
051: * {@code packageNames} is a comma separated list of packages containing
052: * persistent classes. Sub-packages of these packages are also searched. If
053: * {@code packageNames} is omitted then all packages known to the current
054: * classloader are searched.
055: * <p>The "-v" option may be included in the comma separated list to print the
056: * name of each class that is enhanced.</p></li>
057: * <br>
058: * <li>The {@link #enhance} method may be called to implement a class loader
059: * that performs enhancement. Using this approach, it is the developer's
060: * responsibility to implement and configure the class loader.</li>
061: * </ol>
062: *
063: * @author Mark Hayes
064: */
065: public class ClassEnhancer implements ClassFileTransformer {
066:
067: private static final String AGENT_PREFIX = "enhance:";
068:
069: private Set<String> packagePrefixes;
070: private boolean verbose;
071:
072: /**
073: * Enhances classes in the directories specified. The class files are
074: * replaced when they are enhanced, without changing the file modification
075: * date. For example:
076: *
077: * <pre class="code">java -cp je-<version>.jar com.sleepycat.persist.model.ClassEnhancer ./classes</pre>
078: *
079: * <p>The "-v" argument may be specified to print the name of each class
080: * file that is enhanced. The total number of class files enhanced will
081: * always be printed.</p>
082: *
083: * @param args one or more directories containing classes to be enhanced.
084: * Subdirectories of these directories will also be searched. Optionally,
085: * -v may be included to print the name of every class file enhanced.
086: */
087: public static void main(String[] args) throws Exception {
088: try {
089: boolean verbose = false;
090: List<File> fileList = new ArrayList<File>();
091: for (int i = 0; i < args.length; i += 1) {
092: String arg = args[i];
093: if (arg.startsWith("-")) {
094: if ("-v".equals(args[i])) {
095: verbose = true;
096: } else {
097: throw new IllegalArgumentException(
098: "Unknown arg: " + arg);
099: }
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
123: */
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: \""
129: + 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, String className,
198: Class<?> classBeingRedefined,
199: ProtectionDomain protectionDomain, byte[] classfileBuffer) {
200: className = className.replace('/', '.');
201: byte[] bytes = enhance(className, classfileBuffer);
202: if (verbose && bytes != null) {
203: System.out.println("Enhanced: " + className);
204: }
205: return bytes;
206: }
207:
208: /**
209: * Enhances the given class bytes if the class is annotated with {@link
210: * Entity} or {@link Persistent}.
211: *
212: * @param className the class name in binary format; for example,
213: * "my.package.MyClass$Name", or null if no filtering by class name
214: * should be performed.
215: *
216: * @param classBytes are the class file bytes to be enhanced.
217: *
218: * @return the enhanced bytes, or null if no enhancement was performed.
219: */
220: public byte[] enhance(String className, byte[] classBytes) {
221: if (className != null && packagePrefixes != null) {
222: for (String prefix : packagePrefixes) {
223: if (className.startsWith(prefix)) {
224: return enhanceBytes(classBytes);
225: }
226: }
227: return null;
228: } else {
229: return enhanceBytes(classBytes);
230: }
231: }
232:
233: int enhanceFile(File file) throws IOException {
234:
235: int nFiles = 0;
236: if (file.isDirectory()) {
237: String[] names = file.list();
238: if (names != null) {
239: for (int i = 0; i < names.length; i += 1) {
240: nFiles += enhanceFile(new File(file, names[i]));
241: }
242: }
243: } else if (file.getName().endsWith(".class")) {
244: byte[] newBytes = enhanceBytes(readFile(file));
245: if (newBytes != null) {
246: long modified = file.lastModified();
247: writeFile(file, newBytes);
248: file.setLastModified(modified);
249: nFiles += 1;
250: if (verbose) {
251: System.out.println("Enhanced: " + file);
252: }
253: }
254: }
255: return nFiles;
256: }
257:
258: private byte[] readFile(File file) throws IOException {
259:
260: byte[] bytes = new byte[(int) file.length()];
261: FileInputStream in = new FileInputStream(file);
262: try {
263: in.read(bytes);
264: } finally {
265: in.close();
266: }
267: return bytes;
268: }
269:
270: private void writeFile(File file, byte[] bytes) throws IOException {
271:
272: FileOutputStream out = new FileOutputStream(file);
273: try {
274: out.write(bytes);
275: } finally {
276: out.close();
277: }
278: }
279:
280: private byte[] enhanceBytes(byte[] bytes) {
281:
282: /*
283: * The writer is at the end of the visitor chain. Pass true to
284: * calculate stack size, for safety.
285: */
286: ClassWriter writer = new ClassWriter(true);
287: ClassVisitor visitor = writer;
288:
289: /* The enhancer is at the beginning of the visitor chain. */
290: visitor = new BytecodeEnhancer(visitor);
291:
292: /* The reader processes the class and invokes the visitors. */
293: ClassReader reader = new ClassReader(bytes);
294: try {
295:
296: /*
297: * Pass false for skipDebug since we are rewriting the class and
298: * should include all information.
299: */
300: reader.accept(visitor, false);
301: return writer.toByteArray();
302: } catch (BytecodeEnhancer.NotPersistentException e) {
303: /* The class is not persistent and should not be enhanced. */
304: return null;
305: }
306: }
307: }
|