All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.sleepycat.persist.model.ClassEnhancer Maven / Gradle / Ivy

The newest version!
/*-
 * Copyright (C) 2002, 2018, Oracle and/or its affiliates. All rights reserved.
 *
 * This file was distributed by Oracle as part of a version of Oracle Berkeley
 * DB Java Edition made available at:
 *
 * http://www.oracle.com/technetwork/database/database-technologies/berkeleydb/downloads/index.html
 *
 * Please see the LICENSE file included in the top-level directory of the
 * appropriate version of Oracle Berkeley DB Java Edition for a copy of the
 * license and additional information.
 */

package com.sleepycat.persist.model;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.instrument.ClassFileTransformer;
import java.lang.instrument.Instrumentation;
import java.security.ProtectionDomain;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.StringTokenizer;

import com.sleepycat.asm.ClassReader;
import com.sleepycat.asm.ClassVisitor;
import com.sleepycat.asm.ClassWriter;

/**
 * Enhances the bytecode of persistent classes to provide efficient access to
 * fields and constructors, and to avoid special security policy settings for
 * accessing non-public members.  Classes are enhanced if they are annotated
 * with {@link Entity} or {@link Persistent}.
 *
 * 

{@code ClassEnhancer} objects are thread-safe. Multiple threads may * safely call the methods of a shared {@code ClassEnhancer} object.

* *

As described in the package summary, bytecode * enhancement may be used either at runtime or offline (at build time).

* *

To use enhancement offline, this class may be used as a {@link #main main * program}. * * It may also be used via an {@link ClassEnhancerTask ant task}. * *

* *

For enhancement at runtime, this class provides the low level support * needed to transform class bytes during class loading. To configure runtime * enhancement you may use one of the following approaches:

*
    *
  1. The BDB {@code je-.jar} or {@code db.jar} file may be used as * an instrumentation agent as follows: *
    {@literal java -javaagent:=enhance:packageNames ...}
    * {@code packageNames} is a comma separated list of packages containing * persistent classes. Sub-packages of these packages are also searched. If * {@code packageNames} is omitted then all packages known to the current * classloader are searched. *

    The "-v" option may be included in the comma separated list to print the * name of each class that is enhanced.

  2. *
  3. The {@link #enhance} method may be called to implement a class loader * that performs enhancement. Using this approach, it is the developer's * responsibility to implement and configure the class loader.
  4. *
* * @author Mark Hayes */ public class ClassEnhancer implements ClassFileTransformer { private static final String AGENT_PREFIX = "enhance:"; private Set packagePrefixes; private boolean verbose; /** * Enhances classes in the directories specified. The class files are * replaced when they are enhanced, without changing the file modification * date. For example: * *
java -cp je-<version>.jar com.sleepycat.persist.model.ClassEnhancer ./classes
* *

The "-v" argument may be specified to print the name of each class * file that is enhanced. The total number of class files enhanced will * always be printed.

* * @param args one or more directories containing classes to be enhanced. * Subdirectories of these directories will also be searched. Optionally, * -v may be included to print the name of every class file enhanced. * * @throws Exception if a problem occurs. */ public static void main(String[] args) throws Exception { try { boolean verbose = false; List fileList = new ArrayList(); for (int i = 0; i < args.length; i += 1) { String arg = args[i]; if (arg.startsWith("-")) { if ("-v".equals(args[i])) { verbose = true; } else { throw new IllegalArgumentException ("Unknown arg: " + arg); } } else { fileList.add(new File(arg)); } } ClassEnhancer enhancer = new ClassEnhancer(); enhancer.setVerbose(verbose); int nFiles = 0; for (File file : fileList) { nFiles += enhancer.enhanceFile(file); } if (nFiles > 0) { System.out.println("Enhanced: " + nFiles + " files"); } } catch (Exception e) { e.printStackTrace(); throw e; } } /** * Enhances classes as specified by a JVM -javaagent argument. * * @param args see java.lang.instrument.Instrumentation. * * @param inst see java.lang.instrument.Instrumentation. * * @see java.lang.instrument.Instrumentation */ public static void premain(String args, Instrumentation inst) { if (!args.startsWith(AGENT_PREFIX)) { throw new IllegalArgumentException ("Unknown javaagent args: " + args + " Args must start with: \"" + AGENT_PREFIX + '"'); } args = args.substring(AGENT_PREFIX.length()); Set packageNames = null; boolean verbose = false; if (args.length() > 0) { packageNames = new HashSet(); StringTokenizer tokens = new StringTokenizer(args, ","); while (tokens.hasMoreTokens()) { String token = tokens.nextToken(); if (token.startsWith("-")) { if (token.equals("-v")) { verbose = true; } else { throw new IllegalArgumentException ("Unknown javaagent arg: " + token); } } else { packageNames.add(token); } } } ClassEnhancer enhancer = new ClassEnhancer(packageNames); enhancer.setVerbose(verbose); inst.addTransformer(enhancer); } /** * Creates a class enhancer that searches all packages. */ public ClassEnhancer() { } /** * Sets verbose mode. * *

True may be specified to print the name of each class file that is * enhanced. This property is false by default.

* * @param verbose whether to use verbose mode. */ public void setVerbose(boolean verbose) { this.verbose = verbose; } /** * Gets verbose mode. * * @return whether to use verbose mode. * * @see #setVerbose */ public boolean getVerbose() { return verbose; } /** * Creates a class enhancer that searches a given set of packages. * * @param packageNames a set of packages to search for persistent * classes. Sub-packages of these packages are also searched. If empty or * null, all packages known to the current classloader are searched. */ public ClassEnhancer(Set packageNames) { if (packageNames != null) { packagePrefixes = new HashSet(); for (String name : packageNames) { packagePrefixes.add(name + '.'); } } } public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) { className = className.replace('/', '.'); byte[] bytes = enhance(className, classfileBuffer); if (verbose && bytes != null) { System.out.println("Enhanced: " + className); } return bytes; } /** * Enhances the given class bytes if the class is annotated with {@link * Entity} or {@link Persistent}. * * @param className the class name in binary format; for example, * "my.package.MyClass$Name", or null if no filtering by class name * should be performed. * * @param classBytes are the class file bytes to be enhanced. * * @return the enhanced bytes, or null if no enhancement was performed. */ public byte[] enhance(String className, byte[] classBytes) { if (className != null && packagePrefixes != null) { for (String prefix : packagePrefixes) { if (className.startsWith(prefix)) { return enhanceBytes(classBytes); } } return null; } else { return enhanceBytes(classBytes); } } int enhanceFile(File file) throws IOException { int nFiles = 0; if (file.isDirectory()) { String[] names = file.list(); if (names != null) { for (int i = 0; i < names.length; i += 1) { nFiles += enhanceFile(new File(file, names[i])); } } } else if (file.getName().endsWith(".class")) { byte[] newBytes = enhanceBytes(readFile(file)); if (newBytes != null) { long modified = file.lastModified(); writeFile(file, newBytes); file.setLastModified(modified); nFiles += 1; if (verbose) { System.out.println("Enhanced: " + file); } } } return nFiles; } private byte[] readFile(File file) throws IOException { byte[] bytes = new byte[(int) file.length()]; FileInputStream in = new FileInputStream(file); try { in.read(bytes); } finally { in.close(); } return bytes; } private void writeFile(File file, byte[] bytes) throws IOException { FileOutputStream out = new FileOutputStream(file); try { out.write(bytes); } finally { out.close(); } } private byte[] enhanceBytes(byte[] bytes) { /* * The writer is at the end of the visitor chain. Pass COMPUTE_FRAMES * to calculate stack size, for safety. */ ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES); ClassVisitor visitor = writer; /* The enhancer is at the beginning of the visitor chain. */ visitor = new BytecodeEnhancer(visitor); /* The reader processes the class and invokes the visitors. */ ClassReader reader = new ClassReader(bytes); try { /* * Pass false for skipDebug since we are rewriting the class and * should include all information. */ reader.accept(visitor, 0); return writer.toByteArray(); } catch (BytecodeEnhancer.NotPersistentException e) { /* The class is not persistent and should not be enhanced. */ return null; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy