001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * // Copyright (c) 2005, 2006, Oracle. All rights reserved.
005: *
006: *
007: * The contents of this file are subject to the terms of either the GNU
008: * General Public License Version 2 only ("GPL") or the Common Development
009: * and Distribution License("CDDL") (collectively, the "License"). You
010: * may not use this file except in compliance with the License. You can obtain
011: * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
012: * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
013: * language governing permissions and limitations under the License.
014: *
015: * When distributing the software, include this License Header Notice in each
016: * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
017: * Sun designates this particular file as subject to the "Classpath" exception
018: * as provided by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the License
020: * Header, with the fields enclosed by brackets [] replaced by your own
021: * identifying information: "Portions Copyrighted [year]
022: * [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * If you wish your version of this file to be governed by only the CDDL or
027: * only the GPL Version 2, indicate your decision by adding "[Contributor]
028: * elects to include this software in this distribution under the [CDDL or GPL
029: * Version 2] license." If you don't indicate a single choice of license, a
030: * recipient has the option to distribute your version of this file under
031: * either the CDDL, the GPL Version 2 or to extend the choice of license to
032: * its licensees as provided above. However, if you add GPL Version 2 code
033: * and therefore, elected the GPL Version 2 license, then the option applies
034: * only if the new code is made subject to such option by the copyright
035: * holder.
036: */
037: package oracle.toplink.essentials.internal.weaving;
038:
039: // J2SE imports
040: import java.lang.instrument.*;
041: import java.io.FileOutputStream;
042: import java.security.ProtectionDomain;
043: import java.util.Map;
044: import java.util.StringTokenizer;
045: import java.io.File;
046: import javax.persistence.spi.ClassTransformer;
047:
048: // ASM imports
049: import oracle.toplink.libraries.asm.*;
050: import oracle.toplink.libraries.asm.attrs.Attributes;
051:
052: // TopLink imports
053: import oracle.toplink.essentials.logging.SessionLog;
054: import oracle.toplink.essentials.sessions.Session;
055:
056: /**
057: * INTERNAL:
058: * This class performs dynamic bytecode weaving: for each attribute
059: * mapped with One To One mapping with Basic Indirection it substitutes the
060: * original attribute's type for ValueHolderInterface.
061: */
062: public class TopLinkWeaver implements ClassTransformer {
063:
064: public static final String WEAVING_OUTPUT_PATH = "toplink.weaving.output.path";
065: public static final String WEAVING_SHOULD_OVERWRITE = "toplink.weaving.overwrite.existing";
066: public static final String WEAVER_NOT_OVERWRITING = "weaver_not_overwriting";
067: public static final String WEAVER_COULD_NOT_WRITE = "weaver_could_not_write";
068: public static final String WEAVER_FAILED = "weaver_failed";
069: public static final String WEAVER_TRANSFORMED_CLASS = "weaver_transformed_class";
070:
071: protected Session session; // for logging
072: // Map<String, ClassDetails> where the key is className in JVM '/' format
073: protected Map classDetailsMap;
074:
075: public TopLinkWeaver(Session session, Map classDetailsMap) {
076: this .session = session;
077: this .classDetailsMap = classDetailsMap;
078: }
079:
080: public Map getClassDetailsMap() {
081: return classDetailsMap;
082: }
083:
084: // @Override: well, not precisely. I wanted the code to be 1.4 compatible,
085: // so the method is written without any Generic type <T>'s in the signature
086: public byte[] transform(ClassLoader loader, String className,
087: Class classBeingRedefined,
088: ProtectionDomain protectionDomain, byte[] classfileBuffer)
089: throws IllegalClassFormatException {
090:
091: /*
092: * The ClassFileTransformer callback - when called by the JVM's
093: * Instrumentation implementation - is invoked for every class loaded.
094: * Thus, we must check the classDetailsMap to see if we are 'interested'
095: * in the class.
096: *
097: * Note: when invoked by the OC4J wrapper class
098: * oracle.toplink.essentials.internal.ejb.cmp3.oc4j.OC4JClassTransformer,
099: * callbacks are made only for the 'interesting' classes
100: */
101: ClassDetails classDetails = (ClassDetails) classDetailsMap
102: .get(className);
103: if (classDetails != null) {
104: ClassReader cr = new ClassReader(classfileBuffer);
105: ClassWriter cw = new ClassWriter(true, true);
106: TopLinkClassWeaver tcw = new TopLinkClassWeaver(cw,
107: classDetails);
108: try {
109: cr
110: .accept(tcw, Attributes.getDefaultAttributes(),
111: false);
112: } catch (Throwable e) {
113: // RuntimeException or Error could be thrown from ASM
114: // log here because ClassLoader ignore any Throwable
115: log(SessionLog.SEVERE, WEAVER_FAILED, new Object[] {
116: className, e });
117: log(SessionLog.SEVERE, e);
118:
119: IllegalClassFormatException ex = new IllegalClassFormatException();
120: ex.initCause(e);
121: throw ex;
122: }
123: if (tcw.alreadyWeaved) {
124: return null;
125: }
126: byte[] bytes = cw.toByteArray();
127:
128: String outputPath = System.getProperty(WEAVING_OUTPUT_PATH,
129: "");
130:
131: if (!outputPath.equals("")) {
132: outputFile(className, bytes, outputPath);
133: }
134: if (tcw.weavedVH) {
135: log(SessionLog.FINER, WEAVER_TRANSFORMED_CLASS,
136: new Object[] { className });
137: return bytes;
138: }
139: }
140: return null; // returning null means 'use existing class bytes'
141: }
142:
143: protected void outputFile(String className, byte[] classBytes,
144: String outputPath) {
145: StringBuffer directoryName = new StringBuffer();
146: ;
147: StringTokenizer tokenizer = new StringTokenizer(className,
148: "\n\\/");
149: String token = null;
150: while (tokenizer.hasMoreTokens()) {
151: token = tokenizer.nextToken();
152: if (tokenizer.hasMoreTokens()) {
153: directoryName.append(token + File.separator);
154: }
155: }
156: try {
157: String usedOutputPath = outputPath;
158: if (!outputPath.endsWith(File.separator)) {
159: usedOutputPath = outputPath + File.separator;
160: }
161: File file = new File(usedOutputPath + directoryName);
162: file.mkdirs();
163: file = new File(file, token + ".class");
164: if (!file.exists()) {
165: file.createNewFile();
166: } else {
167: if (!System.getProperty(WEAVING_SHOULD_OVERWRITE,
168: "false").equalsIgnoreCase("true")) {
169: log(SessionLog.WARNING, WEAVER_NOT_OVERWRITING,
170: new Object[] { className });
171: return;
172: }
173: }
174: FileOutputStream fos = new FileOutputStream(file);
175: fos.write(classBytes);
176: fos.close();
177: } catch (Exception e) {
178: log(SessionLog.WARNING, WEAVER_COULD_NOT_WRITE,
179: new Object[] { className, e });
180: }
181: }
182:
183: // same as in oracle.toplink.essentials.internal.helper.Helper, but uses
184: // '/' slash as delimiter, not '.'
185: protected static String getShortName(String name) {
186: int pos = name.lastIndexOf('/');
187: if (pos >= 0) {
188: name = name.substring(pos + 1);
189: if (name.endsWith(";")) {
190: name = name.substring(0, name.length() - 1);
191: }
192: return name;
193: }
194: return "";
195: }
196:
197: protected void log(int level, String msg, Object[] params) {
198: ((oracle.toplink.essentials.internal.sessions.AbstractSession) session)
199: .log(level, SessionLog.WEAVER, msg, params);
200: }
201:
202: protected void log(int level, Throwable t) {
203: ((oracle.toplink.essentials.internal.sessions.AbstractSession) session)
204: .logThrowable(level, SessionLog.WEAVER, t);
205: }
206: }
|